版權全部,轉載請註明出處:linzhiyong https://juejin.im/post/5b596f315188251aef4e683c https://www.jianshu.com/p/23b35d403148java
相關文章
一、OkHttp3入門介紹
二、OkHttp3入門介紹之Cookie持久化android
前面文章介紹了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/SpringMVCDemogit
本章主要從如下幾個方面介紹:
一、OkHttp3 Cookie內置管理機制介紹
二、基於本地存儲的Cookie管理
三、基於內存存儲的Cookie管理
四、總結github
OkHttp提供了用於管理Cookie的接口CookieJar
,看一下接口的內部結構:緩存
public interface CookieJar {
/** 內部默認實現,不作任何操做. */
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相關信息後,okhttp會回調該方法,此處能夠緩存或者持久化cookie */
void saveFromResponse(HttpUrl url, List<Cookie> cookies);
/** 請求時,okhttp會經過該方法,獲取對應的cookie */
List<Cookie> loadForRequest(HttpUrl url);
}
複製代碼
從上面能夠看出,CookieJar
接口提供了saveFromResponse
、loadForRequest
兩個方法,還有一個內部類默認實現NO_COOKIES
。 1)saveFromResponse
方法:當網絡請求返回結果後,內部會解析Header並獲取cookie相關信息,同時回調該方法,此處能夠緩存或者持久化cookie,下面看一下調用源碼:bash
public final class BridgeInterceptor implements Interceptor {
// 此處省略
@Override public Response intercept(Chain chain) throws IOException {
// 此處省略...
// 經過cookieJar接口的loadForRequest方法獲取url對應的cookie
List<Cookie> cookies = cookieJar.loadForRequest(userRequest.url());
if (!cookies.isEmpty()) {
// 若是獲取到的cookie不爲空,則設置到請求頭中
requestBuilder.header("Cookie", cookieHeader(cookies));
}
// 此處省略
}
}
複製代碼
2)loadForRequest
方法:當網絡請求時,okhttp會經過該方法,獲取對應cookie,下面看一下調用源碼: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<Cookie> 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;
}
}
複製代碼
這裏我仿照 android-async-http 的Cookie管理機制PersistentCookieStore
進行改造;app
實現邏輯 一、定義用於管理Cookie的接口CookieStore
; 二、定義CookieJarImpl
類實現CookieJar
接口,而後用CookieStore
去接管saveFromResponse
、loadForRequest
這兩個方法; 三、定義PersistentCookieStore
類實現CookieStore
接口,用於管理Cookie; 四、將PersistentCookieStore
對象設置到OkHttpClient
中;dom
具體實現 一、定義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<Cookie> cookies);
/** 根據HttpUrl從緩存中讀取cookie集合 */
List<Cookie> get(HttpUrl httpUrl);
/** 獲取所有緩存cookie */
List<Cookie> getCookies();
/** 移除指定httpurl cookie集合 */
boolean remove(HttpUrl httpUrl, Cookie cookie);
/** 移除全部cookie */
boolean removeAll();
}
複製代碼
二、定義CookieJarImpl
類實現CookieJar
接口,而後用CookieStore
去接管saveFromResponse
、loadForRequest
這兩個方法:
/**
* 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<Cookie> cookies) {
this.cookieStore.add(url, cookies);
}
@Override
public synchronized List<Cookie> loadForRequest(HttpUrl url) {
return this.cookieStore.get(url);
}
public CookieStore getCookieStore() {
return this.cookieStore;
}
}
複製代碼
三、定義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<String, ConcurrentHashMap<String, Cookie>> 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<String, ConcurrentHashMap<String, Cookie>>();
Map tempCookieMap = new HashMap<Object, Object>(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, Cookie>());
}
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<String, Cookie> 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<String, Cookie>());
}
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<Cookie> cookies) {
for (Cookie cookie : cookies) {
if (isCookieExpired(cookie)) {
continue;
}
this.add(httpUrl, cookie);
}
}
@Override
public List<Cookie> get(HttpUrl httpUrl) {
return this.get(this.hostName(httpUrl));
}
@Override
public List<Cookie> getCookies() {
ArrayList<Cookie> result = new ArrayList<Cookie>();
for (String hostKey : this.cookies.keySet()) {
result.addAll(this.get(hostKey));
}
return result;
}
/** 獲取cookie集合 */
private List<Cookie> get(String hostKey) {
ArrayList<Cookie> result = new ArrayList<Cookie>();
if (this.cookies.containsKey(hostKey)) {
Collection<Cookie> 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 = 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 = (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();
}
}
複製代碼
四、將PersistentCookieStore
對象設置到OkHttpClient
中;
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.cookieJar(new CookieJarImpl(new PersistentCookieStore(context)));
複製代碼
實現邏輯跟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<String, ConcurrentHashMap<String, Cookie>> cookies;
public MemoryCookieStore() {
this.cookies = new HashMap<String, ConcurrentHashMap<String, Cookie>>();
}
@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<String, Cookie>());
}
cookies.get(hostKey).put(name, cookie);
}
@Override
public void add(HttpUrl httpUrl, List<Cookie> cookies) {
for (Cookie cookie : cookies) {
if (isCookieExpired(cookie)) {
continue;
}
this.add(httpUrl, cookie);
}
}
@Override
public List<Cookie> get(HttpUrl httpUrl) {
return this.get(this.hostName(httpUrl));
}
@Override
public List<Cookie> getCookies() {
ArrayList<Cookie> result = new ArrayList<Cookie>();
for (String hostKey : this.cookies.keySet()) {
result.addAll(this.get(hostKey));
}
return result;
}
/** 獲取cookie集合 */
private List<Cookie> get(String hostKey) {
ArrayList<Cookie> result = new ArrayList<Cookie>();
if (this.cookies.containsKey(hostKey)) {
Collection<Cookie> 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();
}
}
複製代碼
今天的主要就是介紹了Cookie的管理,就是從CookieJar
接口的兩個方法入手,而後作了進一步的封裝處理,PersistentCookieStore
和MemoryCookieStore
這兩個類的邏輯實現基本一致,喜歡動手的小夥伴徹底能夠進一步抽象一下。