关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »OkHttp3实现Cookies管理及持久化

    OkHttp3实现Cookies管理及持久化

    2016-01-22 00:00:00 出处:crazy__chen
    分享

    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();
    }
    public void readResponse() throws IOException {
      //例行省略....
    
      receiveHeaders(networkResponse.headers());
    
      //例行省略....
    }
    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;
    }
    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();
      }
    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);
      }

    通过以上几个关键方法,可以很明显的感觉到作者的意图了,为了更加自由定制化的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);
    }

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

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

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

    最终效果

    完成对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;
            }
        }

    Tips

    在这样做之前,尝试了使用 Interceptor 和 NetWorkInterceptor 在Http请求request和response时,拦截响应链,加入对Cookie的管理。so!接下来可能会详细介绍下 Interceptor 这个非常酷的实现。

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    正版 Windows 11产品密钥怎么查找/查看?

    还有3个月,微软将停止 Windows 10 的更新

    Windows 10 终止支持后,企业为何要立即升级?

    Windows 10 将于 2025年10 月终止技术支持,建议迁移到 Windows 11

    Windows 12 发布推迟,微软正全力筹备Windows 11 25H2更新

    Linux 退出 mail的命令是什么

    Linux 提醒 No space left on device,但我的空间看起来还有不少空余呢

    hiberfil.sys文件可以删除吗?了解该文件并手把手教你删除C盘的hiberfil.sys文件

    Window 10和 Windows 11哪个好?答案是:看你自己的需求

    盗版软件成公司里的“隐形炸弹”?老板们的“法务噩梦” 有救了!

    IT头条

    公安部:我国在售汽车搭载的“智驾”系统都不具备“自动驾驶”功能

    02:03

    液冷服务器概念股走强,博汇、润泽等液冷概念股票大涨

    01:17

    亚太地区的 AI 驱动型医疗保健:2025 年及以后的下一步是什么?

    16:30

    智能手机市场风云:iPhone领跑销量榜,华为缺席引争议

    15:43

    大数据算法和“老师傅”经验叠加 智慧化收储粮食尽显“科技范”

    15:17

    技术热点

    SQL汉字转换为拼音的函数

    windows 7系统无法运行Photoshop CS3的解决方法

    巧用MySQL加密函数对Web网站敏感数据进行保护

    MySQL基础知识简介

    Windows7和WinXP下如何实现不输密码自动登录系统的设置方法介绍

    windows 7系统ip地址冲突怎么办?windows 7系统IP地址冲突问题的

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。