OkHttp3實現Cookies管理及持久化

kermitco635 8年前發布 | 242K 次閱讀 OkHttp Android開發 移動開發

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(); }</pre>

public void readResponse() throws IOException {
  //例行省略....

receiveHeaders(networkResponse.headers());

//例行省略.... }</pre>

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; }</pre>

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();

}</pre>

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);

}</pre>

通過以上幾個關鍵方法,可以很明顯的感覺到作者的意圖了,為了更加自由定制化的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); }</pre>

    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();</pre>

    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(); } }</pre>

    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;
}

}</pre>

最終效果

完成對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;
    }
}</pre> 

Tips

在這樣做之前,嘗試了使用 Interceptor 和 NetWorkInterceptor 在Http請求request和response時,攔截響應鏈,加入對Cookie的管理。so!接下來可能會詳細介紹下 Interceptor 這個非常酷的實現。

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

 本文由用戶 kermitco635 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!