OkHttp3实现Cookies管理及持久化

kermitco635 5年前

okHttp3正式版刚发布了没几天,正好重构之前的代码,于是第一时间入坑了。对okHttp3的一些改变,会陆续写下来,这是第一篇Cookies管理及持久化。

Cookies管理

OkHttp的源码过于复杂,感兴趣的同学可以自行阅读,这里只针对 HttpEngineer 类进行分析,从字面意思即可看出这个类负责http请求的request、response等等操作的处理,而cookies管理也是随着http请求的request、response来处理。

3.0之前

先看networkRequest方法,在里面通过client.getCookieHandler()函数获得了CookieHandler对象,通过该对象拿到cookie并设置到请求头里,请求结束后取得响应后通过networkResponse.headers()函数将请求头获得传入receiveHeaders函数,并将取得的cookie存入getCookieHandler得到的一个CookieHandler对象中去

private Request networkRequest(Request request) throws IOException {    Request.Builder result = request.newBuilder();      //例行省略....      CookieHandler cookieHandler = client.getCookieHandler();    if (cookieHandler != null) {      // Capture the request headers added so far so that they can be offered to the CookieHandler.      // This is mostly to stay close to the RI; it is unlikely any of the headers above would      // affect cookie choice besides "Host".      Map<String, List<String>> headers = OkHeaders.toMultimap(result.build().headers(), null);        Map<String, List<String>> cookies = cookieHandler.get(request.uri(), headers);        // Add any new cookies to the request.      OkHeaders.addCookies(result, cookies);    }      //例行省略....      return result.build();  }
public void readResponse() throws IOException {    //例行省略....      receiveHeaders(networkResponse.headers());      //例行省略....  }
public void receiveHeaders(Headers headers) throws IOException {    CookieHandler cookieHandler = client.getCookieHandler();    if (cookieHandler != null) {      cookieHandler.put(userRequest.uri(), OkHeaders.toMultimap(headers, null));    }  }

CookieHandler对象是OkHttpClient类中的一个属性,传入了这个对象,那么OkHttp就会对cookie进行自动管理

private CookieHandler cookieHandler;  public OkHttpClient setCookieHandler(CookieHandler cookieHandler) {    this.cookieHandler = cookieHandler;    return this;  }    public CookieHandler getCookieHandler() {    return cookieHandler;  }
OkHttpClient client = new OkHttpClient();  client.setCookieHandler(CookieHandler cookieHanlder);

3.0之后

而在OkHttp3中,对cookie而言,新增了两个类 Cookiejar 、 Cookie 两个类,在了解这两个类之前,先去看一下 HttpEngine 关于cookie管理的变化

private Request networkRequest(Request request) throws IOException {      Request.Builder result = request.newBuilder();        //例行省略....            List<Cookie> cookies = client.cookieJar().loadForRequest(request.url());      if (!cookies.isEmpty()) {        result.header("Cookie", cookieHeader(cookies));      }        //例行省略....        return result.build();    }
private String cookieHeader(List<Cookie> cookies) {      StringBuilder cookieHeader = new StringBuilder();      for (int i = 0, size = cookies.size(); i < size; i++) {        if (i > 0) {          cookieHeader.append("; ");        }        Cookie cookie = cookies.get(i);        cookieHeader.append(cookie.name()).append('=').append(cookie.value());      }      return cookieHeader.toString();    }
public void receiveHeaders(Headers headers) throws IOException {      if (client.cookieJar() == CookieJar.NO_COOKIES) return;        List<Cookie> cookies = Cookie.parseAll(userRequest.url(), headers);      if (cookies.isEmpty()) return;        client.cookieJar().saveFromResponse(userRequest.url(), cookies);    }

通过以上几个关键方法,可以很明显的感觉到作者的意图了,为了更加自由定制化的cookie管理。其中 loadForRequest() 、 saveFromResponse() 这两个方法最为关键,分别是在发送时向request header中加入cookie,在接收时,读取response header中的cookie。现在再去看 Cookiejar 这个类,就很好理解了

public interface CookieJar {    /** A cookie jar that never accepts any cookies. */    CookieJar NO_COOKIES = new CookieJar() {      @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {      }        @Override public List<Cookie> loadForRequest(HttpUrl url) {        return Collections.emptyList();      }    };      /**     * Saves {@code cookies} from an HTTP response to this store according to this jar's policy.     *     * <p>Note that this method may be called a second time for a single HTTP response if the response     * includes a trailer. For this obscure HTTP feature, {@code cookies} contains only the trailer's     * cookies.     */    void saveFromResponse(HttpUrl url, List<Cookie> cookies);      /**     * Load cookies from the jar for an HTTP request to {@code url}. This method returns a possibly     * empty list of cookies for the network request.     *     * <p>Simple implementations will return the accepted cookies that have not yet expired and that     * {@linkplain Cookie#matches match} {@code url}.     */    List<Cookie> loadForRequest(HttpUrl url);  }

so!在OkHttpClient创建时,传入这个CookieJar的实现,就能完成对Cookie的自动管理了

OkHttpClient client = new OkHttpClient.Builder()      .cookieJar(new CookieJar() {          private final HashMap<HttpUrl, List<Cookie>> cookieStore = new HashMap<>();            @Override          public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {              cookieStore.put(url, cookies);          }            @Override          public List<Cookie> loadForRequest(HttpUrl url) {              List<Cookie> cookies = cookieStore.get(url);              return cookies != null ? cookies : new ArrayList<Cookie>();          }      })      .build();

Cookies持久化

对Cookies持久化的方案,与之前版本并无很大区别,还是参考 android-async-http 这个库,主要参考其中两个类:

  • PersistentCookieStore

  • SerializableHttpCookie
    与之前版本的区别是要将对 java.net.HttpCookie 这个类的缓存处理换成对 okhttp3.Cookie 的处理,其他方面几乎一样。

废话不多说了,直接上代码

SerializableOkHttpCookies

主要做两件事:

  • 将Cookie对象输出为ObjectStream

  • 将ObjectStream序列化成Cookie对象

public class SerializableOkHttpCookies implements Serializable {        private transient final Cookie cookies;      private transient Cookie clientCookies;        public SerializableOkHttpCookies(Cookie cookies) {          this.cookies = cookies;      }        public Cookie getCookies() {          Cookie bestCookies = cookies;          if (clientCookies != null) {              bestCookies = clientCookies;          }          return bestCookies;      }        private void writeObject(ObjectOutputStream out) throws IOException {          out.writeObject(cookies.name());          out.writeObject(cookies.value());          out.writeLong(cookies.expiresAt());          out.writeObject(cookies.domain());          out.writeObject(cookies.path());          out.writeBoolean(cookies.secure());          out.writeBoolean(cookies.httpOnly());          out.writeBoolean(cookies.hostOnly());          out.writeBoolean(cookies.persistent());      }        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {          String name = (String) in.readObject();          String value = (String) in.readObject();          long expiresAt = in.readLong();          String domain = (String) in.readObject();          String path = (String) in.readObject();          boolean secure = in.readBoolean();          boolean httpOnly = in.readBoolean();          boolean hostOnly = in.readBoolean();          boolean persistent = in.readBoolean();          Cookie.Builder builder = new Cookie.Builder();          builder = builder.name(name);          builder = builder.value(value);          builder = builder.expiresAt(expiresAt);          builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);          builder = builder.path(path);          builder = secure ? builder.secure() : builder;          builder = httpOnly ? builder.httpOnly() : builder;          clientCookies =builder.build();      }  }

PersistentCookieStore

根据一定的规则去缓存或者获取Cookie:

public class PersistentCookieStore {      private static final String LOG_TAG = "PersistentCookieStore";      private static final String COOKIE_PREFS = "Cookies_Prefs";        private final Map<String, ConcurrentHashMap<String, Cookie>> cookies;      private final SharedPreferences cookiePrefs;          public PersistentCookieStore(Context context) {          cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0);          cookies = new HashMap<>();            //将持久化的cookies缓存到内存中 即map cookies          Map<String, ?> prefsMap = cookiePrefs.getAll();          for (Map.Entry<String, ?> entry : prefsMap.entrySet()) {              String[] cookieNames = TextUtils.split((String) entry.getValue(), ",");              for (String name : cookieNames) {                  String encodedCookie = cookiePrefs.getString(name, null);                  if (encodedCookie != null) {                      Cookie decodedCookie = decodeCookie(encodedCookie);                      if (decodedCookie != null) {                          if (!cookies.containsKey(entry.getKey())) {                              cookies.put(entry.getKey(), new ConcurrentHashMap<String, Cookie>());                          }                          cookies.get(entry.getKey()).put(name, decodedCookie);                      }                  }              }          }      }        protected String getCookieToken(Cookie cookie) {          return cookie.name() + "@" + cookie.domain();      }        public void add(HttpUrl url, Cookie cookie) {          String name = getCookieToken(cookie);            //将cookies缓存到内存中 如果缓存过期 就重置此cookie          if (!cookie.persistent()) {              if (!cookies.containsKey(url.host())) {                  cookies.put(url.host(), new ConcurrentHashMap<String, Cookie>());              }              cookies.get(url.host()).put(name, cookie);          } else {              if (cookies.containsKey(url.host())) {                  cookies.get(url.host()).remove(name);              }          }            //讲cookies持久化到本地          SharedPreferences.Editor prefsWriter = cookiePrefs.edit();          prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));          prefsWriter.putString(name, encodeCookie(new SerializableOkHttpCookies(cookie)));          prefsWriter.apply();      }        public List<Cookie> get(HttpUrl url) {          ArrayList<Cookie> ret = new ArrayList<>();          if (cookies.containsKey(url.host()))              ret.addAll(cookies.get(url.host()).values());          return ret;      }        public boolean removeAll() {          SharedPreferences.Editor prefsWriter = cookiePrefs.edit();          prefsWriter.clear();          prefsWriter.apply();          cookies.clear();          return true;      }        public boolean remove(HttpUrl url, Cookie cookie) {          String name = getCookieToken(cookie);            if (cookies.containsKey(url.host()) && cookies.get(url.host()).containsKey(name)) {              cookies.get(url.host()).remove(name);                SharedPreferences.Editor prefsWriter = cookiePrefs.edit();              if (cookiePrefs.contains(name)) {                  prefsWriter.remove(name);              }              prefsWriter.putString(url.host(), TextUtils.join(",", cookies.get(url.host()).keySet()));              prefsWriter.apply();                return true;          } else {              return false;          }      }        public List<Cookie> getCookies() {          ArrayList<Cookie> ret = new ArrayList<>();          for (String key : cookies.keySet())              ret.addAll(cookies.get(key).values());            return ret;      }        /**       * cookies 序列化成 string       *       * @param cookie 要序列化的cookie       * @return 序列化之后的string       */      protected String encodeCookie(SerializableOkHttpCookies 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());      }        /**       * 将字符串反序列化成cookies       *       * @param cookieString cookies string       * @return cookie object       */      protected Cookie decodeCookie(String cookieString) {          byte[] bytes = hexStringToByteArray(cookieString);          ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);          Cookie cookie = null;          try {              ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);              cookie = ((SerializableOkHttpCookies) objectInputStream.readObject()).getCookies();          } catch (IOException e) {              Log.d(LOG_TAG, "IOException in decodeCookie", e);          } catch (ClassNotFoundException e) {              Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);          }            return cookie;      }        /**       * 二进制数组转十六进制字符串       *       * @param bytes byte array to be converted       * @return string containing hex values       */      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);      }        /**       * 十六进制字符串转二进制数组       *       * @param hexString string of hex-encoded values       * @return decoded byte array       */      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持久化之后,就可以对Cookiejar进行进一步修改了,最终效果:

/**       * 自动管理Cookies       */      private class CookiesManager implements CookieJar {          private final PersistentCookieStore cookieStore = new PersistentCookieStore(getApplicationContext());            @Override          public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {              if (cookies != null && cookies.size() > 0) {                  for (Cookie item : cookies) {                      cookieStore.add(url, item);                  }              }          }            @Override          public List<Cookie> loadForRequest(HttpUrl url) {              List<Cookie> cookies = cookieStore.get(url);              return cookies;          }      }

Tips

在这样做之前,尝试了使用 Interceptor 和 NetWorkInterceptor 在Http请求request和response时,拦截响应链,加入对Cookie的管理。so!接下来可能会详细介绍下 Interceptor 这个非常酷的实现。

</div>

来自: http://segmentfault.com/a/1190000004345545