急速開發(fā)系列——Retrofit實(shí)現(xiàn)持久化Cookie的三種方案

Retrofit實(shí)戰(zhàn)技巧中上荡,我們提到retrofit 2.0中如何實(shí)現(xiàn)非持久化cookie的兩種方案,但并未做過(guò)深的解釋沪猴。
現(xiàn)在我們重點(diǎn)關(guān)注JavaNetCookieJar實(shí)現(xiàn)非持久化cookie背后的原理掌腰。

話不多說(shuō)作喘,步入正題。


非持久化Cookie實(shí)現(xiàn)分析

首先來(lái)看上文中提到的非持久化cookie的實(shí)現(xiàn):

 public void setCookies(OkHttpClient.Builder builder) {
        CookieManager cookieManager = new CookieManager();
        cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
        builder.cookieJar(new JavaNetCookieJar(cookieManager));
    }

現(xiàn)在我們就以這段代碼為起點(diǎn)來(lái)研究為什么這幾行代碼就實(shí)現(xiàn)了非持久化cookie呢霞势?不難先發(fā)現(xiàn)此處實(shí)現(xiàn)cookie的關(guān)鍵就是cookieJar(),我們就以該方法切入烹植。
首先來(lái)看該方法的源碼:

     //設(shè)置cookie處理器,如果沒(méi)設(shè)置愕贡,則使用CookieJar.NO_COOKIES作為默認(rèn)的處理器
    public Builder cookieJar(CookieJar cookieJar) {
      if (cookieJar == null) throw new NullPointerException("cookieJar == null");
      this.cookieJar = cookieJar;
      return this;
    }

通過(guò)該方法我們知道這里的關(guān)鍵就在于CookieJar接口草雕,來(lái)看看這個(gè)接口的定義:

public interface CookieJar {
 //默認(rèn)的cookie處理器,不接受任何cookie
  CookieJar NO_COOKIES = new CookieJar() {
    @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    }

    @Override public List<Cookie> loadForRequest(HttpUrl url) {
      return Collections.emptyList();
    }
  };

 
   //根絕cookie處理策略固以,保存響應(yīng)中的cookie
  void saveFromResponse(HttpUrl url, List<Cookie> cookies);

  //為請(qǐng)求添加cookie 
  List<Cookie> loadForRequest(HttpUrl url);
}

該接口非常簡(jiǎn)單墩虹,提供了保存和加載cookie的兩個(gè)方法loadForRequest(HttpUrl url)saveFromResponse(HttpUrl url,List<Cookie> cookies)憨琳,這就意味這我們可以自行實(shí)現(xiàn)接口來(lái)實(shí)現(xiàn)對(duì)cookie管理诫钓。到這里想必各位可能已經(jīng)在想“我們自行實(shí)現(xiàn)該接口來(lái)將cookie保存到本地,使用的時(shí)候再?gòu)谋镜刈x取篙螟,這樣不久實(shí)現(xiàn)cookie持久化了么菌湃?”,這當(dāng)然沒(méi)問(wèn)題闲擦,但我們先繼續(xù)往下看慢味。

除此之外,該接口中存在一個(gè)默認(rèn)的實(shí)現(xiàn)NO_COOKIES墅冷。

接下來(lái)纯路,我們來(lái)看看該接口的另外一個(gè)實(shí)現(xiàn)類JavaNetCookieJar,它本質(zhì)上只是java.net.CookieHandler的代理類寞忿。同樣驰唬,來(lái)看一下JavaNetCookieJar中的源碼:

public final class JavaNetCookieJar implements CookieJar {
  private final CookieHandler cookieHandler;

  public JavaNetCookieJar(CookieHandler cookieHandler) {
    this.cookieHandler = cookieHandler;
  }


  @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
    if (cookieHandler != null) {
      List<String> cookieStrings = new ArrayList<>();
      for (Cookie cookie : cookies) {//遍歷cookie集合
        cookieStrings.add(cookie.toString());
      }
      Map<String, List<String>> multimap = Collections.singletonMap("Set-Cookie", cookieStrings);
      try {
      //具體的保存工作交給cookieHandler去處理
        cookieHandler.put(url.uri(), multimap);
      } catch (IOException e) {
        Internal.logger.log(WARNING, "Saving cookies failed for " + url.resolve("/..."), e);
      }
    }
  }

  @Override public List<Cookie> loadForRequest(HttpUrl url) {
    // The RI passes all headers. We don't have 'em, so we don't pass 'em!
    Map<String, List<String>> headers = Collections.emptyMap();
    Map<String, List<String>> cookieHeaders;
    try {
      //從cookieHandler中取出cookie集合。
      cookieHeaders = cookieHandler.get(url.uri(), headers);
    } catch (IOException e) {
      Internal.logger.log(WARNING, "Loading cookies failed for " + url.resolve("/..."), e);
      return Collections.emptyList();
    }

    List<Cookie> cookies = null;
    for (Map.Entry<String, List<String>> entry : cookieHeaders.entrySet()) {
      String key = entry.getKey();
      if (("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key))
          && !entry.getValue().isEmpty()) {
        for (String header : entry.getValue()) {
          if (cookies == null) cookies = new ArrayList<>();
          cookies.addAll(decodeHeaderAsJavaNetCookies(url, header));
        }
      }
    }

    return cookies != null
        ? Collections.unmodifiableList(cookies)
        : Collections.<Cookie>emptyList();
  }


   //將請(qǐng)求Header轉(zhuǎn)為OkHttp中HttpCookie
  private List<Cookie> decodeHeaderAsJavaNetCookies(HttpUrl url, String header) {
    List<Cookie> result = new ArrayList<>();
    for (int pos = 0, limit = header.length(), pairEnd; pos < limit; pos = pairEnd + 1) {
      //具體轉(zhuǎn)換過(guò)程在此不做展示
    }
    return result;
  }
}

上面的代碼非常簡(jiǎn)單腔彰,其核心無(wú)非在于通過(guò)CookieHandler實(shí)現(xiàn)對(duì)cookie的保存和取值叫编。既然,真正的工作類是CookieHandler霹抛,那我們就重點(diǎn)關(guān)注CookieHandler.

public abstract class CookieHandler {

    private static CookieHandler cookieHandler;

    public synchronized static CookieHandler getDefault() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SecurityConstants.GET_COOKIEHANDLER_PERMISSION);
        }
        return cookieHandler;
    }

    public synchronized static void setDefault(CookieHandler cHandler) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(SecurityConstants.SET_COOKIEHANDLER_PERMISSION);
        }
        cookieHandler = cHandler;
    }

    public abstract Map<String, List<String>>
        get(URI uri, Map<String, List<String>> requestHeaders)
        throws IOException;

    public abstract void
        put(URI uri, Map<String, List<String>> responseHeaders)
        throws IOException;
}

我們發(fā)現(xiàn)CookieHandler是個(gè)抽象類搓逾,其實(shí)現(xiàn)類就是我們上面在非持久化Cookie中用到的CookieManager,沒(méi)辦法杯拐,我們?cè)诖宿D(zhuǎn)移焦點(diǎn)至CookieManager:

public class CookieManager extends CookieHandler
{
    private CookiePolicy policyCallback;//cookie處理策略
    private CookieStore cookieJar = null;//cookie存儲(chǔ)

    public CookieManager() {
        this(null, null);
    }

    //在這里我們可以指定cookie存儲(chǔ)方式及cookie處理策略
    public CookieManager(CookieStore store,
                         CookiePolicy cookiePolicy)
    {
        // use default cookie policy if not specify one
        policyCallback = (cookiePolicy == null) ? CookiePolicy.ACCEPT_ORIGINAL_SERVER
                                                : cookiePolicy;

        // 如果沒(méi)有指定持久化方式霞篡,則默認(rèn)使用內(nèi)存持久化世蔗。
        if (store == null) {
            cookieJar = new InMemoryCookieStore();
        } else {
            cookieJar = store;
        }
    }

    //指定cookie處理策略
    public void setCookiePolicy(CookiePolicy cookiePolicy) {
        if (cookiePolicy != null) policyCallback = cookiePolicy;
    }

    public CookieStore getCookieStore() {
        return cookieJar;
    }

    
    //獲取cookie
    public Map<String, List<String>>
        get(URI uri, Map<String, List<String>> requestHeaders)
        throws IOException
    {
       //... 省略多行代碼

        Map<String, List<String>> cookieMap =
                        new java.util.HashMap<String, List<String>>();
      //... 省略多行代碼
        for (HttpCookie cookie : cookieJar.get(uri)) {
            //... 省略多行代碼
        }

        // apply sort rule (RFC 2965 sec. 3.3.4)
        List<String> cookieHeader = sortByPath(cookies);

        cookieMap.put("Cookie", cookieHeader);
        return Collections.unmodifiableMap(cookieMap);
    }

    //保存cookie
    public void
        put(URI uri, Map<String, List<String>> responseHeaders)
        throws IOException
    {
        //... 省略多行代碼
    PlatformLogger logger = PlatformLogger.getLogger("java.net.CookieManager");
        for (String headerKey : responseHeaders.keySet()) {
            // RFC 2965 3.2.2, key must be 'Set-Cookie2'
            // we also accept 'Set-Cookie' here for backward compatibility
            if (headerKey == null
                || !(headerKey.equalsIgnoreCase("Set-Cookie2")
                     || headerKey.equalsIgnoreCase("Set-Cookie")
                    )
                )
            {
                continue;
            }

            for (String headerValue : responseHeaders.get(headerKey)) {
                try {
                    List<HttpCookie> cookies;
                    try {
                        cookies = HttpCookie.parse(headerValue);
                    } catch (IllegalArgumentException e) {
                        // Bogus header, make an empty list and log the error
                        cookies = java.util.Collections.emptyList();
                        if (logger.isLoggable(PlatformLogger.Level.SEVERE)) {
                            logger.severe("Invalid cookie for " + uri + ": " + headerValue);
                        }
                    }
                    for (HttpCookie cookie : cookies) {
                        if (cookie.getPath() == null) {
                            //... 省略多行代碼
                            cookie.setPath(path);
                        }

                        if (cookie.getDomain() == null) {
                            //... 省略多行代碼
                            cookie.setDomain(host);
                        }
                        
                        //... 省略多行代碼
                            if (shouldAcceptInternal(uri, cookie)) {
                                cookieJar.add(uri, cookie);
                            }
                    }
                } catch (IllegalArgumentException e) {
                    // invalid set-cookie header string
                    // no-op
                }
            }
        }
    }



}

在CacheManager中,需要重點(diǎn)關(guān)注的便是policyCallback和cookieJar兩個(gè)成員變量朗兵。分別來(lái)看看這二者做了什么污淋?

cookie處理策略
cookie的處理策略由CookiePolicy接口確定,該接口中預(yù)置了三種處理策略:ACCEPT_ALL,ACCEPT_NONE及ACCEPT_SERVER,分別用于接受所有cookie余掖,不接受cookie寸爆,只接受初始服務(wù)器的cookie。

cookie存儲(chǔ)
cookie的存儲(chǔ)則由CookieStore接口負(fù)責(zé)盐欺,該接口目前存在唯一的實(shí)現(xiàn)類InMemoryCookieStore赁豆,通過(guò)該實(shí)現(xiàn)類的名字也大概猜的出它將cookie存在內(nèi)存中了,同樣我們簡(jiǎn)單的瀏覽一下其源碼:

class InMemoryCookieStore implements CookieStore {
    private List<HttpCookie> cookieJar = null;
    private Map<String, List<HttpCookie>> domainIndex = null;
    private Map<URI, List<HttpCookie>> uriIndex = null;

    // use ReentrantLock instead of syncronized for scalability
    private ReentrantLock lock = null;


    /**
     * The default ctor
     */
    public InMemoryCookieStore() {
        cookieJar = new ArrayList<HttpCookie>();
        domainIndex = new HashMap<String, List<HttpCookie>>();
        uriIndex = new HashMap<URI, List<HttpCookie>>();

        lock = new ReentrantLock(false);
    }

   //保存cookie
    public void add(URI uri, HttpCookie cookie) {
        // pre-condition : argument can't be null
        if (cookie == null) {
            throw new NullPointerException("cookie is null");
        }


        lock.lock();
        try {
            // 首先移除已經(jīng)存在的cookie
            cookieJar.remove(cookie);

            // 緩存時(shí)間不為0
            if (cookie.getMaxAge() != 0) {
                cookieJar.add(cookie);
                // 保存到域索引中
                if (cookie.getDomain() != null) {
                    addIndex(domainIndex, cookie.getDomain(), cookie);
                }
                //保存到url索引中
                if (uri != null) {
                    // add it to uri index, too
                    addIndex(uriIndex, getEffectiveURI(uri), cookie);
                }
            }
        } finally {
            lock.unlock();
        }
    }

}

到現(xiàn)在我們已經(jīng)弄明白非持久化Cookie的原理冗美,這里用一張類圖來(lái)概括一下:


此處輸入圖片的描述
此處輸入圖片的描述

從設(shè)計(jì)的來(lái)說(shuō)歌憨,這里也充分體現(xiàn)了面向抽象編程,單一職責(zé)理念等墩衙。對(duì)這些設(shè)計(jì)原則不明白的可以看我以前寫的文章务嫡。


持久化Cookie實(shí)現(xiàn)

自定義InDiskCookieStore實(shí)現(xiàn)持久化Cookie

通過(guò)上面的分析,我們最終知道非持久化Cookie的存儲(chǔ)靠InMemoryCookieStore實(shí)現(xiàn)漆改,除此之外心铃,我們發(fā)現(xiàn)CookieManager中也可以指定另外的CookieStore的實(shí)現(xiàn)。這樣看來(lái)挫剑,我們只需要自定義InDiskCookieStore實(shí)現(xiàn)CookieStore去扣,并將其設(shè)置給CookieManager就可以實(shí)現(xiàn)Cookie的持久化了。接下來(lái)樊破,我們仿照inMemoryCookieStore來(lái)實(shí)現(xiàn)自己的InDiskCookieStore的即可愉棱。

不難發(fā)現(xiàn)這里的重點(diǎn)在于如何持久化Cookie到本地。我們可以從響應(yīng)頭中讀取set-cookie(可能有多個(gè))哲戚,然后拼接存儲(chǔ)在相關(guān)的配置文件中奔滑,也可以直接序列化HttpCookie對(duì)象到本地。這里我們采用序列化的方式實(shí)現(xiàn)顺少,對(duì)Java序列化和反序列化不熟的同學(xué)可以自行補(bǔ)充朋其。

查看HttpCookie發(fā)現(xiàn),該類并不支持序列化脆炎,哪該怎么辦呢梅猿?最簡(jiǎn)單的方式是為該類創(chuàng)建輔助類DiskHttpCookie,用來(lái)實(shí)現(xiàn)序列化和反序列化的過(guò)程:

public class DiskHttpCookie implements Serializable {

    private static final long serialVersionUID = -6370968478600008500L;
    private transient final HttpCookie mCookie;
    private transient HttpCookie mClientCookie;

    public DiskHttpCookie(HttpCookie cookie) {
        mCookie = cookie;
    }

    public HttpCookie getCookie() {
        HttpCookie cookie = mCookie;
        if (mClientCookie != null) {
            cookie = mClientCookie;
        }

        return cookie;
    }

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.writeObject(mCookie.getName());
        out.writeObject(mCookie.getValue());
        out.writeObject(mCookie.getComment());
        out.writeObject(mCookie.getCommentURL());
        out.writeObject(mCookie.getDomain());
        out.writeLong(mCookie.getMaxAge());
        out.writeObject(mCookie.getPath());
        out.writeObject(mCookie.getPortlist());
        out.writeBoolean(mCookie.getSecure());
        out.writeBoolean(mCookie.getDiscard());
        out.writeInt(mCookie.getVersion());
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        String name = (String) in.readObject();
        String value = (String) in.readObject();
        mClientCookie = new HttpCookie(name, value);
        mClientCookie.setComment((String) in.readObject());
        mClientCookie.setCommentURL((String) in.readObject());
        mClientCookie.setDomain((String) in.readObject());
        mClientCookie.setMaxAge(in.readLong());
        mClientCookie.setPath((String) in.readObject());
        mClientCookie.setPortlist((String) in.readObject());
        mClientCookie.setSecure(in.readBoolean());
        mClientCookie.setDiscard(in.readBoolean());
        mClientCookie.setVersion(in.readInt());

    }
}

在DiskHttpCookie中秒裕,通過(guò)writeObject()和readObject()方法實(shí)現(xiàn)自定義序列化的過(guò)程袱蚓。下面我們正式來(lái)看InDiskCookieStore的實(shí)現(xiàn),此次我們參考了async-http-client的相關(guān)源碼:

public class InDiskCookieStore implements CookieStore {
    private static final String LOG_TAG = "InDiskCookieStore";
    private static final String COOKIE_PREFS = "CookiePrefsFile";
    private static final String COOKIE_NAME_PREFIX = "cookie_";
    
    //在內(nèi)存中几蜻,緩存cookie喇潘,加快訪問(wèn)速度
    private final HashMap<String, ConcurrentHashMap<String, HttpCookie>> cookies;
    //存儲(chǔ)cookie到本地
    private final SharedPreferences cookiePrefs;

    private ReentrantLock mLock = null;

    public InDiskCookieStore(Context context) {
        cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);
        mLock = new ReentrantLock(false);
        cookies = new HashMap<>();
        initMemoryCookie();


    }

    private void initMemoryCookie() {
        // 加載本地cookie到內(nèi)存中
        Map<String, ?> prefsMap = cookiePrefs.getAll();
        for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {
            if (((String) entry.getValue()) != null && !((String) entry.getValue()).startsWith(COOKIE_NAME_PREFIX)) {
                String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");
                for (String name : cookieNames) {
                    String encodedCookie = cookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
                    if (encodedCookie != null) {
                        HttpCookie decodedCookie = decodeCookie(encodedCookie);
                        if (decodedCookie != null) {
                            if (!cookies.containsKey(entry.getKey()))
                                cookies.put(entry.getKey(), new ConcurrentHashMap<String, HttpCookie>());
                            cookies.get(entry.getKey()).put(name, decodedCookie);
                        }
                    }
                }

            }
        }
    }

    @Override
    public void add(URI uri, HttpCookie cookie) {
        mLock.lock();
        try {
            String name = getCookieToken(uri, cookie);

            if (!cookie.hasExpired()) {
                if (!cookies.containsKey(uri.getHost()))
                    cookies.put(uri.getHost(), new ConcurrentHashMap<String, HttpCookie>());
                cookies.get(uri.getHost()).put(name, cookie);
            } else {//清除過(guò)期cookie
                if (cookies.containsKey(uri.toString()))
                    cookies.get(uri.getHost()).remove(name);
            }

            // Save cookie into persistent store
            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
            prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
            prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new DiskHttpCookie(cookie)));
            prefsWriter.commit();
        } finally {
            mLock.unlock();
        }
    }

    protected String getCookieToken(URI uri, HttpCookie cookie) {
        return cookie.getName() + cookie.getDomain();
    }

    @Override
    public List<HttpCookie> get(URI uri) {
        mLock.lock();
        ArrayList<HttpCookie> ret = new ArrayList<HttpCookie>();
        if (cookies.containsKey(uri.getHost()))
            ret.addAll(cookies.get(uri.getHost()).values());
        mLock.unlock();
        return ret;
    }

    @Override
    public boolean removeAll() {
        mLock.lock();
        try {
            SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
            prefsWriter.clear();
            prefsWriter.commit();
            cookies.clear();
        } finally {
            mLock.unlock();
        }
        return true;
    }


    @Override
    public boolean remove(URI uri, HttpCookie cookie) {
        mLock.lock();
        try {
            String name = getCookieToken(uri, cookie);
            if (cookies.containsKey(uri.getHost()) && cookies.get(uri.getHost()).containsKey(name)) {
                cookies.get(uri.getHost()).remove(name);

                SharedPreferences.Editor prefsWriter = cookiePrefs.edit();
                if (cookiePrefs.contains(COOKIE_NAME_PREFIX + name)) {
                    prefsWriter.remove(COOKIE_NAME_PREFIX + name);
                }
                prefsWriter.putString(uri.getHost(), TextUtils.join(",", cookies.get(uri.getHost()).keySet()));
                prefsWriter.commit();

                return true;
            } else {
                return false;
            }
        } finally {
            mLock.unlock();
        }
    }

    @Override
    public List<HttpCookie> getCookies() {
        mLock.lock();

        ArrayList<HttpCookie> ret = new ArrayList<HttpCookie>();
        for (String key : cookies.keySet())
            ret.addAll(cookies.get(key).values());
        mLock.unlock();
        return ret;
    }

    @Override
    public List<URI> getURIs() {
        mLock.lock();
        ArrayList<URI> ret = new ArrayList<URI>();
        for (String key : cookies.keySet())
            try {
                ret.add(new URI(key));
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }

        mLock.unlock();
        return ret;
    }


    //序列化DiskHttpCookie爽撒,并將其轉(zhuǎn)為字符串
    protected String encodeCookie(DiskHttpCookie cookie) {
        if (cookie == null)
            return null;
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(os);
            outputStream.writeObject(cookie);
        } catch (IOException e) {
            Log.d(LOG_TAG, "IOException in encodeCookie", e);
            return null;
        }

        return byteArrayToHexString(os.toByteArray());
    }

    //通過(guò)cookie字符串反序列化為HttpCookie對(duì)象
    protected HttpCookie decodeCookie(String cookieString) {
        byte[] bytes = hexStringToByteArray(cookieString);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
        HttpCookie cookie = null;
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            cookie = ((DiskHttpCookie) objectInputStream.readObject()).getCookie();
        } catch (IOException e) {
            Log.d(LOG_TAG, "IOException in decodeCookie", e);
        } catch (ClassNotFoundException e) {
            Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
        }

        return cookie;
    }

    
    //字節(jié)轉(zhuǎn)16進(jìn)制
    protected String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte element : bytes) {
            int v = element & 0xff;
            if (v < 16) {
                sb.append('0');
            }
            sb.append(Integer.toHexString(v));
        }
        return sb.toString().toUpperCase(Locale.US);
    }

    //16進(jìn)制轉(zhuǎn)字節(jié)
    protected byte[] hexStringToByteArray(String hexString) {
        int len = hexString.length();
        byte[] data = new byte[len / 2];
        for (int i = 0; i < len; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
        }
        return data;
    }
}

此類一方面將cookie暫存在內(nèi)存中,以便實(shí)現(xiàn)快速的訪問(wèn)响蓉,另一方面會(huì)將cookie序列化在本地。在每次創(chuàng)建InDiskCookieStore時(shí)哨毁,會(huì)用本地的cookie初始化到緩存中枫甲。

那么如何使用呢?其實(shí)用和我們使用JavaNetCookieJar非常相似:

 public void setCookies(OkHttpClient.Builder builder) {
         CookieManager cookieManager = new java.net.CookieManager(new InDiskCookieStore(this),CookiePolicy.ACCEPT_ORIGINAL_SERVER);
         builder.cookieJar(new JavaNetCookieJar(cookieManager));
    }

通過(guò)這種方式扼褪,無(wú)需做其他的變化就實(shí)現(xiàn)了持久化Cookie想幻。

自定義攔截器實(shí)現(xiàn)持久化Cookie

通過(guò)自定義攔截器實(shí)現(xiàn)Cookie持久化的思路我們已經(jīng)在上一篇文章說(shuō)過(guò),就不多做解釋了话浇,直接來(lái)看代碼即可:

首先定義響應(yīng)攔截器脏毯,該攔截器實(shí)現(xiàn)從response獲取set-cookie字段的值,并將其保存在本地幔崖。

public class SaveCookiesInterceptor implements Interceptor {
    private static final String COOKIE_PREF = "cookies_prefs";
    private Context mContext;

    public SaveCookiesInterceptor(Context context) {
        mContext = context;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Response response = chain.proceed(request);
        //set-cookie可能為多個(gè)
        if (!response.headers("set-cookie").isEmpty()) {
            List<String> cookies = response.headers("set-cookie");
            String cookie = encodeCookie(cookies);
            saveCookie(request.url().toString(),request.url().host(),cookie);
        }

        return response;
    }
    
    //整合cookie為唯一字符串
    private String encodeCookie(List<String> cookies) {
        StringBuilder sb = new StringBuilder();
        Set<String> set=new HashSet<>();
        for (String cookie : cookies) {
            String[] arr = cookie.split(";");
            for (String s : arr) {
                if(set.contains(s))continue;
                set.add(s);

            }
        }

        Iterator<String> ite = set.iterator();
        while (ite.hasNext()) {
            String cookie = ite.next();
            sb.append(cookie).append(";");
        }

        int last = sb.lastIndexOf(";");
        if (sb.length() - 1 == last) {
            sb.deleteCharAt(last);
        }

        return sb.toString();
    }

    //保存cookie到本地食店,這里我們分別為該url和host設(shè)置相同的cookie,其中host可選
    //這樣能使得該cookie的應(yīng)用范圍更廣
    private void saveCookie(String url,String domain,String cookies) {
        SharedPreferences sp = mContext.getSharedPreferences(COOKIE_PREF, Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = sp.edit();

        if (TextUtils.isEmpty(url)) {
            throw new NullPointerException("url is null.");
        }else{
            editor.putString(url, cookies);
        }

        if (!TextUtils.isEmpty(domain)) {
            editor.putString(domain, cookies);
        }

        editor.apply();

    }
}

其次定義請(qǐng)求攔截器赏寇,如果該請(qǐng)求存在cookie吉嫩,則為其添加到Header的Cookie中,代碼如下:

public class AddCookiesInterceptor implements Interceptor {
    private static final String COOKIE_PREF = "cookies_prefs";
    private Context mContext;

    public AddCookiesInterceptor(Context context) {
        mContext = context;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request request = chain.request();
        Request.Builder builder = request.newBuilder();
        String cookie = getCookie(request.url().toString(), request.url().host());
        if (!TextUtils.isEmpty(cookie)) {
            builder.addHeader("Cookie", cookie);
        }

        return chain.proceed(builder.build());
    }

    private String getCookie(String url, String domain) {
        SharedPreferences sp = mContext.getSharedPreferences(COOKIE_PREF, Context.MODE_PRIVATE);
        if (!TextUtils.isEmpty(url)&&sp.contains(url)&&!TextUtils.isEmpty(sp.getString(url,""))) {
            return sp.getString(url, "");
        }
        if (!TextUtils.isEmpty(domain)&&sp.contains(domain) && !TextUtils.isEmpty(sp.getString(domain, ""))) {
            return sp.getString(domain, "");
        }

        return null;
    }
}

最后我們將這兩個(gè)攔截器設(shè)置到OkHttpClient即可:

 public void setCookies(OkHttpClient.Builder builder) {
     builder.addInterceptor(new AddCookiesInterceptor(this));
     builder.addInterceptor(new SaveCookiesInterceptor(this));
   }

這樣嗅定,我們也實(shí)現(xiàn)了Cookie的持久化自娩,再次證明了OkHttp的攔截器機(jī)制是如此方便。
和上面的方案相比渠退,該方案需要額外添加兩個(gè)攔截器忙迁。對(duì)某些有代碼潔癖的人來(lái)說(shuō),可能并不喜歡這種方案碎乃。

自定義CookieJar實(shí)現(xiàn)持久化Cookie

到現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了持久化Cookie的兩種方案姊扔。自定義CookieJar來(lái)實(shí)現(xiàn)Cookie持久化和以上兩種方案并無(wú)太大的區(qū)別,結(jié)合上一篇文章中通過(guò)CookieJar實(shí)現(xiàn)非持久化Cookie梅誓,你也可以很容易的搞定持久化Cookie旱眯,故不做詳細(xì)的說(shuō)明了。


總結(jié)

今天我們從頭到位了解了Cookie持久化技術(shù)证九,并對(duì)不同的方案做了簡(jiǎn)單的說(shuō)明删豺,希望對(duì)各位有所幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末愧怜,一起剝皮案震驚了整個(gè)濱河市呀页,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拥坛,老刑警劉巖蓬蝶,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尘分,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡丸氛,警方通過(guò)查閱死者的電腦和手機(jī)培愁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缓窜,“玉大人定续,你說(shuō)我怎么就攤上這事『檀福” “怎么了私股?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)恩掷。 經(jīng)常有香客問(wèn)我倡鲸,道長(zhǎng),這世上最難降的妖魔是什么黄娘? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任峭状,我火速辦了婚禮,結(jié)果婚禮上逼争,老公的妹妹穿的比我還像新娘宁炫。我一直安慰自己,他們只是感情好氮凝,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布羔巢。 她就那樣靜靜地躺著,像睡著了一般罩阵。 火紅的嫁衣襯著肌膚如雪竿秆。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天稿壁,我揣著相機(jī)與錄音幽钢,去河邊找鬼。 笑死傅是,一個(gè)胖子當(dāng)著我的面吹牛匪燕,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喧笔,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼帽驯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了书闸?” 一聲冷哼從身側(cè)響起尼变,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎浆劲,沒(méi)想到半個(gè)月后嫌术,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哀澈,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年度气,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了割按。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡磷籍,死狀恐怖适荣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤晒旅,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布栅盲,位于F島的核電站废恋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鱼鼓。R本人自食惡果不足惜拟烫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一迄本、第九天 我趴在偏房一處隱蔽的房頂上張望硕淑。 院中可真熱鬧,春花似錦嘉赎、人聲如沸置媳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拇囊。三九已至,卻和暖如春靶橱,著一層夾襖步出監(jiān)牢的瞬間寥袭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工关霸, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留传黄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓队寇,卻偏偏與公主長(zhǎng)得像尝江,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子英上,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 又是一年中秋佳節(jié)炭序,祝各位中秋節(jié)快樂(lè)啤覆。 今天我們來(lái)聊聊這個(gè)最近很火的網(wǎng)絡(luò)請(qǐng)求庫(kù)retrofit,在此基礎(chǔ)上會(huì)延伸出一...
    涅槃1992閱讀 7,785評(píng)論 13 133
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,111評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理惭聂,服務(wù)發(fā)現(xiàn)窗声,斷路器,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在辜纲,面了一些公司笨觅,掛了不少,但最終還是拿到小米耕腾、百度见剩、阿里、京東扫俺、新浪苍苞、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,246評(píng)論 11 349
  • 參考:android WebView的cookie機(jī)制Android中Cookie獲取狼纬、保存以及同步OkHttp3...
    liwei_happyman閱讀 6,415評(píng)論 1 11