OkHttp3入门介绍之Cookie持久化

版权所有,转载请注明出处:linzhiyong https://juejin.im/post/5b596f315188251aef4e683c https://www.jianshu.com/p/23b35d403148
相关文章
1、OkHttp3入门介绍
2、OkHttp3入门介绍之Cookie持久化

前面文章介绍了OkHttp3的基本用法,GET/PST请求、上传下载文件等等,本章节主要介绍基于内存和本地缓存的Cookie管理。 官网:http://square.github.io/okhttp/
Github:https://github.com/square/okhttp
OkHttp3Demo传送门:https://github.com/linzhiyong/OkHttp3Demo
服务端Demo传送门:https://github.com/linzhiyong/SpringMVCDemo
目录 本章主要从以下几个方面介绍:
1、OkHttp3 Cookie内置管理机制介绍
2、基于本地存储的Cookie管理
3、基于内存存储的Cookie管理
4、总结
1、OkHttp3 Cookie内置管理机制介绍 OkHttp提供了用于管理Cookie的接口CookieJar,看一下接口的内部结构:
public interface CookieJar { /** 内部默认实现,不做任何操作. */ CookieJar NO_COOKIES = new CookieJar() { @Override public void saveFromResponse(HttpUrl url, List cookies) { }@Override public List loadForRequest(HttpUrl url) { return Collections.emptyList(); } }; /** 调用网络请求,获取到cookie相关信息后,okhttp会回调该方法,此处可以缓存或者持久化cookie */ void saveFromResponse(HttpUrl url, List cookies); /** 请求时,okhttp会通过该方法,获取对应的cookie */ List loadForRequest(HttpUrl url); } 复制代码

从上面可以看出,CookieJar接口提供了saveFromResponseloadForRequest 两个方法,还有一个内部类默认实现NO_COOKIES。 1)saveFromResponse方法:当网络请求返回结果后,内部会解析Header并获取cookie相关信息,同时回调该方法,此处可以缓存或者持久化cookie,下面看一下调用源码:
public final class BridgeInterceptor implements Interceptor { // 此处省略 @Override public Response intercept(Chain chain) throws IOException { // 此处省略...// 通过cookieJar接口的loadForRequest方法获取url对应的cookie List cookies = cookieJar.loadForRequest(userRequest.url()); if (!cookies.isEmpty()) { // 如果获取到的cookie不为空,则设置到请求头中 requestBuilder.header("Cookie", cookieHeader(cookies)); } // 此处省略 } } 复制代码

2)loadForRequest 方法:当网络请求时,okhttp会通过该方法,获取对应cookie,下面看一下调用源码:
public final class BridgeInterceptor implements Interceptor { // 此处省略 @Override public Response intercept(Chain chain) throws IOException { // 此处省略...// 此处获取请求的响应对象 Response networkResponse = chain.proceed(requestBuilder.build()); // 解析响应头里的信息 HttpHeaders.receiveHeaders(cookieJar, userRequest.url(), networkResponse.headers()); } }public final class HttpHeaders {public static void receiveHeaders(CookieJar cookieJar, HttpUrl url, Headers headers) { if (cookieJar == CookieJar.NO_COOKIES) return; // 解析响应头里的信息 List cookies = Cookie.parseAll(url, headers); if (cookies.isEmpty()) return; // 调用cookieJar接口的saveFromResponse方法,下发cookie cookieJar.saveFromResponse(url, cookies); }} 复制代码

3)如果开发者在初始化OkHtpClient时没有自定义CookieJar,默认不会进行cookie操作,看一下OkHttpClient的构造器实现;
public static final class Builder { CookieJar cookieJar; public Builder() { // 默认使用自带cookie管理器,没有做任何cookie处理 cookieJar = CookieJar.NO_COOKIES; } } 复制代码

2、基于本地存储的Cookie管理 这里我仿照 android-async-http 的Cookie管理机制PersistentCookieStore 进行改造;
实现逻辑 1、定义用于管理Cookie的接口CookieStore; 2、定义CookieJarImpl类实现CookieJar接口,然后用CookieStore去接管saveFromResponseloadForRequest 这两个方法; 3、定义PersistentCookieStore类实现CookieStore接口,用于管理Cookie; 4、将PersistentCookieStore对象设置到OkHttpClient中;
具体实现 1、定义CookieStore接口:
/** * Cookie缓存接口 * * @author linzhiyong * @email wflinzhiyong@163.com * @blog https://blog.csdn.net/u012527802 * @desc */ public interface CookieStore {/**添加cookie */ void add(HttpUrl httpUrl, Cookie cookie); /** 添加指定httpurl cookie集合 */ void add(HttpUrl httpUrl, List cookies); /** 根据HttpUrl从缓存中读取cookie集合 */ List get(HttpUrl httpUrl); /** 获取全部缓存cookie */ List getCookies(); /**移除指定httpurl cookie集合 */ boolean remove(HttpUrl httpUrl, Cookie cookie); /** 移除所有cookie */ boolean removeAll(); } 复制代码

2、定义CookieJarImpl类实现CookieJar接口,然后用CookieStore去接管saveFromResponseloadForRequest 这两个方法:
/** * CookieJarImpl * * @author linzhiyong * @email wflinzhiyong@163.com * @blog https://blog.csdn.net/u012527802 * @time 2018/7/20 * @desc */ public class CookieJarImpl implements CookieJar {private CookieStore cookieStore; public CookieJarImpl(CookieStore cookieStore) { if(cookieStore == null) { throw new IllegalArgumentException("cookieStore can not be null."); } this.cookieStore = cookieStore; }@Override public synchronized void saveFromResponse(HttpUrl url, List cookies) { this.cookieStore.add(url, cookies); }@Override public synchronized List loadForRequest(HttpUrl url) { return this.cookieStore.get(url); }public CookieStore getCookieStore() { return this.cookieStore; } } 复制代码

3、定义PersistentCookieStore类实现CookieStore接口,用于管理Cookie; (注:这里仿照android-async-http库里的 PersistentCookieStore 实现)
/** * Cookie缓存持久化实现类 * * @author linzhiyong * @email wflinzhiyong@163.com * @blog https://blog.csdn.net/u012527802 * @time 2018/7/20 * @desc */ public class PersistentCookieStore implements CookieStore {private static final String LOG_TAG = "PersistentCookieStore"; private static final String COOKIE_PREFS = "CookiePrefsFile"; private static final String HOST_NAME_PREFIX = "host_"; private static final String COOKIE_NAME_PREFIX = "cookie_"; private final HashMap> cookies; private final SharedPreferences cookiePrefs; private boolean omitNonPersistentCookies = false; /** Construct a persistent cookie store.*/ public PersistentCookieStore(Context context) { this.cookiePrefs = context.getSharedPreferences(COOKIE_PREFS, 0); this.cookies = new HashMap>(); Map tempCookieMap = new HashMap(cookiePrefs.getAll()); for (Object key : tempCookieMap.keySet()) { if (!(key instanceof String) || !((String) key).contains(HOST_NAME_PREFIX)) { continue; }String cookieNames = (String) tempCookieMap.get(key); if (TextUtils.isEmpty(cookieNames)) { continue; }if (!this.cookies.containsKey(key)) { this.cookies.put((String) key, new ConcurrentHashMap()); }String[] cookieNameArr = cookieNames.split(","); for (String name : cookieNameArr) { String encodedCookie = this.cookiePrefs.getString("cookie_" + name, null); if (encodedCookie == null) { continue; }Cookie decodedCookie = this.decodeCookie(encodedCookie); if (decodedCookie != null) { this.cookies.get(key).put(name, decodedCookie); } } } tempCookieMap.clear(); clearExpired(); }/** 移除失效cookie */ private void clearExpired() { SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); for (String key : this.cookies.keySet()) { boolean changeFlag = false; for (ConcurrentHashMap.Entry entry : cookies.get(key).entrySet()) { String name = entry.getKey(); Cookie cookie = entry.getValue(); if (isCookieExpired(cookie)) { // Clear cookies from local store cookies.get(key).remove(name); // Clear cookies from persistent store prefsWriter.remove(COOKIE_NAME_PREFIX + name); changeFlag = true; } }// Update names in persistent store if (changeFlag) { prefsWriter.putString(key, TextUtils.join(",", cookies.keySet())); } }prefsWriter.apply(); }@Override public void add(HttpUrl httpUrl, Cookie cookie) { if (omitNonPersistentCookies && !cookie.persistent()) { return; }String name = this.cookieName(cookie); String hostKey = this.hostName(httpUrl); // Save cookie into local store, or remove if expired if(!this.cookies.containsKey(hostKey)) { this.cookies.put(hostKey, new ConcurrentHashMap()); } cookies.get(hostKey).put(name, cookie); // Save cookie into persistent store SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); // 保存httpUrl对应的所有cookie的name prefsWriter.putString(hostKey, TextUtils.join(",", cookies.get(hostKey).keySet())); // 保存cookie prefsWriter.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableCookie(cookie))); prefsWriter.apply(); }@Override public void add(HttpUrl httpUrl, List cookies) { for (Cookie cookie : cookies) { if (isCookieExpired(cookie)) { continue; } this.add(httpUrl, cookie); } }@Override public List get(HttpUrl httpUrl) { return this.get(this.hostName(httpUrl)); }@Override public List getCookies() { ArrayList result = new ArrayList(); for (String hostKey : this.cookies.keySet()) { result.addAll(this.get(hostKey)); } return result; }/** 获取cookie集合 */ private List get(String hostKey) { ArrayList result = new ArrayList(); if (this.cookies.containsKey(hostKey)) { Collection cookies = this.cookies.get(hostKey).values(); for (Cookie cookie : cookies) { if (isCookieExpired(cookie)) { this.remove(hostKey, cookie); } else { result.add(cookie); } } } return result; }@Override public boolean remove(HttpUrl httpUrl, Cookie cookie) { return this.remove(this.hostName(httpUrl), cookie); }/** 从缓存中移除cookie */ private boolean remove(String hostKey, Cookie cookie) { String name = this.cookieName(cookie); if (this.cookies.containsKey(hostKey) && this.cookies.get(hostKey).containsKey(name)) { // 从内存中移除httpUrl对应的cookie this.cookies.get(hostKey).remove(name); SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); // 从本地缓存中移出对应cookie prefsWriter.remove(COOKIE_NAME_PREFIX + name); // 保存httpUrl对应的所有cookie的name prefsWriter.putString(hostKey, TextUtils.join(",", this.cookies.get(hostKey).keySet())); prefsWriter.apply(); return true; } return false; }@Override public boolean removeAll() { SharedPreferences.Editor prefsWriter = cookiePrefs.edit(); prefsWriter.clear(); prefsWriter.apply(); this.cookies.clear(); return true; }public void setOmitNonPersistentCookies(boolean omitNonPersistentCookies) { this.omitNonPersistentCookies = omitNonPersistentCookies; }/** 判断cookie是否失效*/ private boolean isCookieExpired(Cookie cookie) { return cookie.expiresAt() < System.currentTimeMillis(); }private String hostName(HttpUrl httpUrl) { return httpUrl.host().startsWith(HOST_NAME_PREFIX) ? httpUrl.host() : HOST_NAME_PREFIX + httpUrl.host(); }private String cookieName(Cookie cookie) { return cookie == null ? null : cookie.name() + cookie.domain(); }protected String encodeCookie(SerializableCookie 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()); }protected Cookie decodeCookie(String cookieString) { byte[] bytes = hexStringToByteArray(cookieString); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); Cookie cookie = null; try { ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); cookie = ((SerializableCookie) 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; }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); }protected byte[] hexStringToByteArray(String hexString) { int len = hexString.length(); byte[] data = https://www.it610.com/article/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; } } 复制代码

这里面用到了SerializableCookie,主要用于序列表cookie对象到对象流中:
/** * 仿照android-async-http的SerializableCookie实现,用处是cookie对象与对象流的互转,保存和读取cookie * * @author linzhiyong * @email wflinzhiyong@163.com * @blog https://blog.csdn.net/u012527802 * @time 2018/7/20 * @desc */ public class SerializableCookie implements Serializable { private static final long serialVersionUID = 6374381828722046732L; private transient final Cookie cookie; private transient Cookie clientCookie; public SerializableCookie(Cookie cookie) { this.cookie = cookie; }public Cookie getCookie() { Cookie bestCookie = cookie; if (this.clientCookie != null) { bestCookie = this.clientCookie; } return bestCookie; }/** 将cookie写到对象流中 */ private void writeObject(ObjectOutputStream out) throws IOException { out.writeObject(this.cookie.name()); out.writeObject(this.cookie.value()); out.writeLong(this.cookie.expiresAt()); out.writeObject(this.cookie.domain()); out.writeObject(this.cookie.path()); out.writeBoolean(this.cookie.secure()); out.writeBoolean(this.cookie.httpOnly()); out.writeBoolean(this.cookie.hostOnly()); out.writeBoolean(this.cookie.persistent()); }/** 从对象流中构建cookie对象 */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { String name = (String) in.readObject(); String value = https://www.it610.com/article/(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() .name(name) .value(value) .expiresAt(expiresAt) .path(path); builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain); builder = secure ? builder.secure() : builder; builder = httpOnly ? builder.httpOnly() : builder; this.clientCookie = builder.build(); } } 复制代码

4、将PersistentCookieStore对象设置到OkHttpClient中;
OkHttpClient.Builder builder = new OkHttpClient.Builder() .cookieJar(new CookieJarImpl(new PersistentCookieStore(context))); 复制代码

3、基于内存存储的Cookie管理 实现逻辑跟PersistentCookieStore类似,只是对于Cookie的存储放在了Map中。
/** * Cookie内存缓存实现 * * @author linzhiyong * @email wflinzhiyong@163.com * @blog https://blog.csdn.net/u012527802 * @time 2018/7/20 * @desc */ public class MemoryCookieStore implements CookieStore {private static final String HOST_NAME_PREFIX = "host_"; private static final String COOKIE_NAME_PREFIX = "cookie_"; private final HashMap> cookies; public MemoryCookieStore() { this.cookies = new HashMap>(); }@Override public void add(HttpUrl httpUrl, Cookie cookie) { if (!cookie.persistent()) { return; }String name = this.cookieName(cookie); String hostKey = this.hostName(httpUrl); if(!this.cookies.containsKey(hostKey)) { this.cookies.put(hostKey, new ConcurrentHashMap()); } cookies.get(hostKey).put(name, cookie); }@Override public void add(HttpUrl httpUrl, List cookies) { for (Cookie cookie : cookies) { if (isCookieExpired(cookie)) { continue; } this.add(httpUrl, cookie); } }@Override public List get(HttpUrl httpUrl) { return this.get(this.hostName(httpUrl)); }@Override public List getCookies() { ArrayList result = new ArrayList(); for (String hostKey : this.cookies.keySet()) { result.addAll(this.get(hostKey)); }return result; }/** 获取cookie集合 */ private List get(String hostKey) { ArrayList result = new ArrayList(); if (this.cookies.containsKey(hostKey)) { Collection cookies = this.cookies.get(hostKey).values(); for (Cookie cookie : cookies) { if (isCookieExpired(cookie)) { this.remove(hostKey, cookie); } else { result.add(cookie); } } } return result; }@Override public boolean remove(HttpUrl httpUrl, Cookie cookie) { return this.remove(this.hostName(httpUrl), cookie); }/** 从缓存中移除cookie */ private boolean remove(String hostKey, Cookie cookie) { String name = this.cookieName(cookie); if (this.cookies.containsKey(hostKey) && this.cookies.get(hostKey).containsKey(name)) { // 从内存中移除httpUrl对应的cookie this.cookies.get(hostKey).remove(name); return true; } return false; }@Override public boolean removeAll() { this.cookies.clear(); return true; }/** 判断cookie是否失效 */ private boolean isCookieExpired(Cookie cookie) { return cookie.expiresAt() < System.currentTimeMillis(); }private String hostName(HttpUrl httpUrl) { return httpUrl.host().startsWith(HOST_NAME_PREFIX) ? httpUrl.host() : HOST_NAME_PREFIX + httpUrl.host(); }private String cookieName(Cookie cookie) { return cookie == null ? null : cookie.name() + cookie.domain(); } } 复制代码

4、总结 今天的主要就是介绍了Cookie的管理,就是从CookieJar接口的两个方法入手,然后做了进一步的封装处理,PersistentCookieStoreMemoryCookieStore这两个类的逻辑实现基本一致,喜欢动手的小伙伴完全可以进一步抽象一下。
【OkHttp3入门介绍之Cookie持久化】转载于:https://juejin.im/post/5b596f315188251aef4e683c

    推荐阅读