现在很多APP开发都采用native + web的方式,这样就会遇到需要共享Cookie的情况。试想用户已经在app里面登陆过了,不可能进了web页面让用户再次登录,这样肯定不合理。
一般的做法是在app里面收到含有Set-Cookie(Set-Cookie2)的响应时,手动取出这个head,保存在ram或者disk里面,之后发起请求时在Webview里面拦截url,然后把之前保存的Cookie取出来,塞到这个请求的headers里面去。这种方法可行,但是略显麻烦,每次发起请求都要手动去添加Cookie,这里我要说的是另外一种。
java里面自带有管理Cookie的工具java.net.CookieManager,它可以管理所有通过HttpUrlConnection(HttpClient没有试过)发起的会话中的Cookie,只要我们初始化之后,它就会自动的保存Cookie,然后在通过HttpUrlConnection发起请求的时候会自动将之前保存的Cookie添加到请求的headers里面去,不用我们手动来做保存和添加Cookies的操作。
其初始化方法:
//这是最简单的写法
CookieHandler.setDefault(new java.net.CookieManager());
而Webview也有一个管理Cookie的工具类android.webkit.CookieManager,我们也可以简单地设置后就能达到自动保存和添加Cookie的效果,但是它管理的是webview里面的会话,无法管理HttpUrlConnection会话的Cookie。
这里我们需要做的操作就是让两个CookieManager里面的内容保持一致。
由于我们一般需要共享Cookie大多都是登录状态,而这个登录发生在App内部(通过web登陆,并将Cookie共享到Native的情况还没遇到过,这里没有考虑这种情况),故而我们只需要在java.net.CookieManager进行SetCookie操作的时候把这个Cookie set到android.webkit.CookieManager就行了。
下一步要做的操作就是找到java.net.CookieManager是在哪里进行的set操作,通过其源代码我们可以很清楚的看到java.net.CookieManager在管理Cookie时的核心是java.net.CookieStore这个接口,java里面已经有一个默认的实现CookieStoreImpl,CookieStoreImpl里面只是用了一个HashMap来保存Cookie,整个类非常简单。通过上面的分析我们知道,只要在add方法里面将Cookie同时set到android.webkit.CookieManager里面就行了。下面上代码
//这个类是直接将CookieStoreImpl这个类的内容copy了出来,然后加入了android.webkit.CookieManager这个变量,并在add操作时,将Cookie添加到android.webkit.CookieManager中去,这样就实现了Cookie的共享,如果你要实现将Cookie保存到本地,可以自己实现CookieStore接口,只要记住在add方法里面将Cookie set到android.webkit.CookieManager里面就行了
class CookieStore_ implements CookieStore{
/** this map may have null keys! */
private final Map<URI, List<HttpCookie>> map = new HashMap<URI, List<HttpCookie>>();
private android.webkit.CookieManager manager;
public CookieStore_() {
manager = android.webkit.CookieManager.getInstance();
}
public synchronized void add(URI uri, HttpCookie cookie) {
if (cookie == null) {
throw new NullPointerException("cookie == null");
}
uri = cookiesUri(uri);
manager.setCookie(uri.toString(),cookie.toString());
List<HttpCookie> cookies = map.get(uri);
if (cookies == null) {
cookies = new ArrayList<HttpCookie>();
map.put(uri, cookies);
} else {
logger.printS("remove old one -> " + cookie.toString());
cookies.remove(cookie);
}
cookies.add(cookie);
logger.printS("current size -> " + map.get(uri).size());
}
private URI cookiesUri(URI uri) {
if (uri == null) {
return null;
}
try {
return new URI("http", uri.getHost(), null, null);
} catch (URISyntaxException e) {
return uri; // probably a URI with no host
}
}
public synchronized List<HttpCookie> get(URI uri) {
if (uri == null) {
throw new NullPointerException("uri == null");
}
List<HttpCookie> result = new ArrayList<HttpCookie>();
// get cookies associated with given URI. If none, returns an empty list
List<HttpCookie> cookiesForUri = map.get(uri);
if (cookiesForUri != null) {
for (Iterator<HttpCookie> i = cookiesForUri.iterator(); i.hasNext(); ) {
HttpCookie cookie = i.next();
if (cookie.hasExpired()) {
i.remove(); // remove expired cookies
} else {
result.add(cookie);
}
}
}
// get all cookies that domain matches the URI
for (Map.Entry<URI, List<HttpCookie>> entry : map.entrySet()) {
if (uri.equals(entry.getKey())) {
continue; // skip the given URI; we've already handled it
}
List<HttpCookie> entryCookies = entry.getValue();
for (Iterator<HttpCookie> i = entryCookies.iterator(); i.hasNext(); ) {
HttpCookie cookie = i.next();
if (!HttpCookie.domainMatches(cookie.getDomain(), uri.getHost())) {
continue;
}
if (cookie.hasExpired()) {
i.remove(); // remove expired cookies
} else if (!result.contains(cookie)) {
result.add(cookie);
}
}
}
logger.printS(String.format(Locale.US,"get cookie for %s, result size is %d",uri.toString(),result.size()));
return Collections.unmodifiableList(result);
}
public synchronized List<HttpCookie> getCookies() {
List<HttpCookie> result = new ArrayList<HttpCookie>();
for (List<HttpCookie> list : map.values()) {
for (Iterator<HttpCookie> i = list.iterator(); i.hasNext(); ) {
HttpCookie cookie = i.next();
if (cookie.hasExpired()) {
i.remove(); // remove expired cookies
} else if (!result.contains(cookie)) {
result.add(cookie);
}
}
}
return Collections.unmodifiableList(result);
}
public synchronized List<URI> getURIs() {
List<URI> result = new ArrayList<URI>(map.keySet());
result.remove(null); // sigh
return Collections.unmodifiableList(result);
}
public synchronized boolean remove(URI uri, HttpCookie cookie) {
if (cookie == null) {
throw new NullPointerException("cookie == null");
}
List<HttpCookie> cookies = map.get(cookiesUri(uri));
if (cookies != null) {
return cookies.remove(cookie);
} else {
return false;
}
}
public synchronized boolean removeAll() {
boolean result = !map.isEmpty();
map.clear();
return result;
}
public void clearCookies(){
map.clear();
}
}
//CookieStore_的用法,只需要在发起网络操作之前调用就行,求简便的话可以放在Application的onCreate函数里面
CookieHandler.setDefault(new java.net.CookieManager(cookieStore_, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
android的webview管理Cookie在Android lollipop之前和之后是不一样的,在Android L之前CookieManager需要和CookieSyncManager这个类一起使用,不然在调用CookieManager的setCookie方法时程序会崩溃,但是在L以及之后的版本中这个类已经Deprecated了,有些API发生了变动,因此在写法上就有些不同了,下面是我自己定义的一个工具类。
//这是一个自定义的工具类,基本的操作都定义在里面了。
public class CookieUtil {
private CookieManager manager;
private CookieStore_ cookieStore_;
private CookieSyncManager syncManager;
private static CookieUtil cookieUtil;
private boolean isInitialed = false;
private CookieUtil(Context context) {
manager = CookieManager.getInstance();
if (!Util.hasLollipop()){
syncManager = CookieSyncManager.createInstance(context);
}
cookieStore_ = new CookieStore_();
}
public void clearCookies(){
if (manager.hasCookies()){
if (Util.hasLollipop()){
manager.removeAllCookies(null);
}else {
manager.removeAllCookie();
}
}
cookieStore_.clearCookies();
}
public static CookieUtil getCookieUtil(Context context){
if (cookieUtil == null)
cookieUtil = new CookieUtil(context);
return cookieUtil;
}
public void sync(){
if (Util.hasLollipop()){
manager.flush();
}else {
syncManager.sync();
}
}
public void setThirdPartyCookieAcceptable(WebView webView){
if (Util.hasLollipop()){
manager.setAcceptThirdPartyCookies(webView,true);
}
}
public void initCookieHandler(){
if (isInitialed)
return;
isInitialed = true;
CookieHandler.setDefault(new java.net.CookieManager(cookieStore_, CookiePolicy.ACCEPT_ORIGINAL_SERVER));
}
}
没有特殊需求的话,只需要一句话就可以完成所有的Cookie管理操作
CookieUtil.getCookieUtil(context).initCookieHandler();//只需要调用一次就够了
如果你的程序里面有退出登录的操作的话,不要忘记调用clearCookies()来清空缓存。