在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)概括一下:
![此處輸入圖片的描述](http://img.my.csdn.net/uploads/201609/16/1474016608_7911.jpg)
從設(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ì)各位有所幫助。