美文网首页我爱编程
Android HttpURLConnection简易框架

Android HttpURLConnection简易框架

作者: 高丕基 | 来源:发表于2018-05-27 13:25 被阅读206次

    1、概述

    封装一个简易的HttpURLConnection简易框架,内部通过线程池来进行网络请求。同时实现了请求返回数据的缓存和对Cookie的处理。具体代码地址如下:网络框架代码

    2、使用方法

    ① 导入demo中的httplib这个moudle。

    ② 配置url,在httplib这个moudle下面的res/xml/url.xml中配置需要调用的url及相关参数,如下所示:

    配置url

    其中Key是查找这个url的关键字;Expires 是get请求回调内容缓存的时间,单位为秒;NetType是请求方法,目前支持GET和POST;Url是请求的url。

    ③ 需要调用该框架的Activity需要继承BaseActivity这个类。如果不想继承的话,请在onStop()中实现BaseActivity中所实现的内容即在Activity销毁时取消所有请求。

    ④ 实现RequestCallback.class回调类,其中onSuccess(String content)方法是请求成功的回调,参数是请求成功的数据。onFail(String content)方法是请求失败时回调,参数是失败原因。这两个方法都在主线程中执行,可进行UI操作,但不要做耗时操作。

    ⑤ 调用getMyHttpClient()方法获得MyHttpClient.class类。通过该类的public void invokeGet(String urlKey, RequestCallback callback) 方法调用http请求,其中urlKey需要传入的是第②步中配置里的Key,callback是第④步中的RequestCallback对象。

    
    public class MainActivity extends BaseActivity {
    
        private TextView textView;
    
        private TextView textView2;
    
        private Button refresh;
    
        @Override
    
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_main);
    
            textView = findViewById(R.id.text);
    
            textView2 = findViewById(R.id.text2);
    
            refresh = findViewById(R.id.refresh);
    
            getMyHttpClient().invokeGet("getWeatherInfo",  new RequestCallback() {
    
                @Override
    
                public void onSuccess(String content) {
    
                    textView.setText(content);
    
                }
    
                @Override
    
                public void onFail(String content) {
    
                    textView.setText(content);
    
                }
    
            });
    
            refresh.setOnClickListener(new View.OnClickListener() {
    
                @Override
    
                public void onClick(View v) {
    
                    getMyHttpClient().invoke("getWeatherInfo", null, new RequestCallback() {
    
                        @Override
    
                        public void onSuccess(String content) {
    
                            textView2.setText(content);
    
                        }
    
                        @Override
    
                        public void onFail(String errorMessage) {
    
                            textView2.setText(errorMessage);
    
                        }
    
                    });
    
                }
    
            });
    
        }
    
    }
    
    

    3、框架详细解说

    3.1 MyHttpClient

    这个类是整个框架的核心类,用到了一些设计模式和面向对象的思想。首先我把它做成了一个单例。整个应用中只可能存在一个该对象,即节省资源,同时也方便管理。

    
    private MyHttpClient(){};
    
        private static class RequestManagerHolder{
    
            private static final MyHttpClient INSTANCE = new MyHttpClient();
    
        }
    
        public static final MyHttpClient getInstance(){
    
            return MyHttpClient.RequestManagerHolder.INSTANCE;
    
        }
    
    public void init(Context context){
    
            if(appContext==null) {
    
                appContext = context.getApplicationContext();
    
            }
    
            if(mHandler ==null){
    
                mHandler = new Handler(context.getMainLooper());
    
            }
    
            if(mCacheManager==null){
    
                mCacheManager = new CacheManager();
    
            }
    
            if(mCookieManager ==null){
    
                mCookieManager = new CookieManager();
    
            }
    
        }
    
    

    同时该对象也是一个门面类,客户端对这个框架的所有调用都封装在这个类里面,这样做的目的是方便使用者,让使用者尽可能少的需要知道框架内部各种类的信息,就可以完成操作。

    
    // 取消所有请求
    
        public void cancelAllRequest(){
    
            if(requestList!=null&& requestList.size()>0){
    
                for(HttpRequest request:requestList){
    
                    request.abort();
    
                }
    
                requestList.clear();
    
            }
    
        }
    
        // get 请求
    
        // @param urlKey url.xml配置中的Key
    
        // @param callback 请求回调函数,在主线程中执行
    
        public void invokeGet(String urlKey, RequestCallback callback){
    
            URLData data=UrlConfigManager.findURL(urlKey,appContext);
    
            data.netType = "GET";
    
            invoke(data, null,  callback);
    
        }
    
    // post 请求
    
     // @param urlKey url.xml配置中的Key
    
     // @param params post 请求需要传入的参数 
    
     // @param callback 请求回调函数,在主线程中执行 
    
     public void invokePost(String urlKey,HashMap params, RequestCallback callback){
    
            URLData data=UrlConfigManager.findURL(urlKey,appContext);
    
            data.netType = "POST";
    
            invoke(data, params,  callback);
    
        }
    
    

    这个类由于需要暴露给用户,内部需要用到几乎所有其他类的功能,所以我也将这个类作为一个中介类,由于目前这个框架相对来说不是很复杂,所以这样设计比较方便,当框架变的更加复杂时,可以将中介类抽取出来单独成类。同时由于该类是单例,所以如果用户只是通过该类操作,一些不需要暴露给用户的类就可以不用设计成单例,只要这个中介类单例持有,那么全局也只会有一份。

    
     private Handler mHandler; // 持有主线程Lopper,用于回调 
    
     private Context appContext; // application的 Context 
    
     private List requestList = new LinkedList<>(); // 持有的所有请求队列
    
     private ExecutorService executorService; // 用于网络请求的线程池
    
     CacheManager mCacheManager; // 缓存管理
    
     CookieManager mCookieManager; // Cookie管理
    
    

    该类中持有了一个线程池:

    
    private synchronized ExecutorService executorService() 
    
    {
    
        if (executorService == null) { 
    
             executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60,             TimeUnit.SECONDS, new SynchronousQueue());
    
            }
    
            return executorService;
    
        }
    
    

    这个线程的核心线程数是0,并且没有设置最大线程,比较时候处理那些多而小的事物,这点正符合这个网络框架的需求。如果后期需要下载图片,或者上传较大文件,那可能需要另一个线程池来进行处理。

    3.2 UrlConfigManager

    这个类主要用来管理Url的配置信息,即在前面说的在res/xml/url.xml中配置进去的各个请求信息。

    为了不频繁读取url.xml文件,这边做了一个缓存mUrlList,在MyHttpClient初始化时候对其进行赋值,同时在每次通过mUrlList寻找URLData是进行判断,若mUrlList被垃圾回收器回收,则进行重新赋值。

    
        private static Map mUrlList;
    
        public static URLData findURL(String key, Context context){
    
            URLData data = null;
    
            init(context);
    
            data = mUrlList.get(key);
    
            return  data;
    
        }
    
        public static void init(Context context){
    
            if(mUrlList ==null || mUrlList.isEmpty()) {
    
                mUrlList = new HashMap<>();
    
                fetchUrlDataFromXml(context);
    
            }
    
        }
    
    

    UrlConfigManager 中的fetchUrlDataFromXml()是用来从xml数据读取出来如下所示:

    private static void fetchUrlDataFromXml(Context context){

        XmlResourceParser xrp = context.getResources().getXml(R.xml.url);
    
        try {
    
            int event = xrp.getEventType();  //先获取当前解析器光标在哪
    
            while (event != XmlPullParser.END_DOCUMENT){    //如果还没到文档的结束标志,那么就继续往下处理
    
                switch (event){
    
                    case XmlPullParser.START_DOCUMENT:
    
                        break;
    
                    case XmlPullParser.START_TAG:
    
                        //一般都是获取标签的属性值,所以在这里数据你需要的数据
    
                        if (xrp.getName().equals(URLData.NODE)){
    
                            URLData data = new URLData();
    
                                data.key = xrp.getAttributeValue(null,URLData.KEY);
    
                                int expires = Integer.parseInt(xrp.getAttributeValue(null,URLData.EXPIRES));
    
                                data.expires = expires;
    
                                data.netType = xrp.getAttributeValue(null,URLData.NET_TYPE);
    
                                data.url = xrp.getAttributeValue(null,URLData.URL);
    
                            mUrlList.put(data.key,data);
    
                        }
    
                        break;
    
                    case XmlPullParser.TEXT:
    
                        break;
    
                    case XmlPullParser.END_TAG:
    
                        break;
    
                    default:
    
                        break;
    
                }
    
                event = xrp.next();  //将当前解析器光标往下一步移
    
            }
    
        } catch (XmlPullParserException e) {
    
            e.printStackTrace();
    
        } catch (IOException e) {
    
            e.printStackTrace();
    
        }
    
    }
    

    3.3 HttpRequest

    该类继承自Runnable用于根据请求来获取数据的操作。

    持有一个Handler的弱引用,用来进行返回结果的回调,持有弱引用是为了防止内存泄漏。

    
    private WeakReference<Handler> mHandler;
    
    if (mHandler != null && !isCancel && mCallback != null) {
    
                mHandler.get().post(new Runnable() {
    
                    @Override
    
                    public void run() {
    
                        if (response.isError()) {
    
                            mCallback.onFail(response.getErrorMessage());
    
                        } else {
    
                            mCallback.onSuccess(response.getResult());
    
                        }
    
                    }
    
                });
    
            }
    
    

    该请求会先从缓存中查找是否有符合的结果,如果没有再到网络中获取。

    
    response = null;
    
            if (mUrlData.netType.toUpperCase().equals("GET")) {
    
              CacheManager.CacheBean bean= mCacheManager.getStringFromMemory(mUrlData.url);
    
              if(bean!=null&& mUrlData.expires>0  && System.currentTimeMillis()<=bean.time ){
    
                  response = new Response();
    
                  response.setError(false);
    
                  response.setResult(bean.context);
    
                  Log.d(TAG,"调取缓存中数据:"+mUrlData.url);
    
              }
    
            }
    
            if (response == null) {
    
                Log.d(TAG,"从网络拉取数据:"+mUrlData.url);
    
                response = netRequest();
    
            }
    
    

    网络请求中会设置相关参数,包括Cookies,同时连接上服务器后会存储Cookies。

    
    private Response netRequest() { 
    
         Response response = new Response(); 
    
         HttpURLConnection connection = null; 
    
         String strResponse = null; 
    
         try { 
    
             // 调用URL对象的openConnection方法获取HttpURLConnection的实例 
    
             URL url = new URL(mUrlData.url); 
    
             connection = (HttpURLConnection) url.openConnection(); 
    
             // 设置连接超时、读取超时的时间,单位为毫秒(ms)
    
             connection.setConnectTimeout(8000); 
    
             connection.setReadTimeout(8000); // 设置Cookie 
    
            mCookieManager.setCookies(connection); 
    
             // 设置请求方式,GET或POST 
    
             connection.setRequestMethod(mUrlData.netType);
    
             if (mParams != null && mUrlData.netType.toUpperCase().equals("POST")) {
    
                 DataOutputStream data = new DataOutputStream(connection.getOutputStream());             StringBuilder sb = new StringBuilder(); Iterator> iterable = mParams.entrySet().iterator();
    
                    if (iterable.hasNext()) {
    
                        Map.Entry e = iterable.next();
    
                        sb.append(e.getKey()).append("=").append(e.getValue());
    
                    }
    
                    while (iterable.hasNext()) {
    
                        Map.Entry e = iterable.next();
    
                        sb.append("&").append(e.getKey()).append("=").append(e.getValue());
    
                    }
    
                    data.writeBytes(sb.toString());
    
                }
    
                //连接
    
                connection.connect();
    
                // 存储Cookies
    
                mCookieManager.storeCookies(connection);
    
                //得到响应码
    
                int responseCode = connection.getResponseCode();
    
                if (responseCode == HttpURLConnection.HTTP_OK) {
    
                    // getInputStream方法获取服务器返回的输入流
    
                    InputStream in = connection.getInputStream();
    
                    // 使用BufferedReader对象读取返回的数据流
    
                    // 按行读取,存储在StringBuider对象response中
    
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in));
    
                    StringBuilder responseBuilder = new StringBuilder();
    
                    String line;
    
                    while ((line = reader.readLine()) != null) {
    
                        responseBuilder.append(line);
    
                    }
    
                    strResponse = responseBuilder.toString();
    
                }
    
            } catch (Exception e) {
    
                response.setErrorType(-1);
    
                response.setError(true);
    
                response.setErrorMessage(e.getMessage());
    
            } finally {
    
                if (connection != null) {
    
                    // 结束后,关闭连接
    
                    connection.disconnect();
    
                }
    
            }
    
            if (strResponse == null) {
    
                response.setErrorType(-1);
    
                response.setError(true);
    
                response.setErrorMessage("网络异常,返回空值");
    
            } else {
    
                response.setError(false);
    
                response.setResult(strResponse);
    
                if(mUrlData.expires>0) {
    
                    CacheManager.CacheBean bean = new CacheManager.CacheBean();
    
                    bean.time = System.currentTimeMillis()+mUrlData.expires*1000;
    
                    bean.context = strResponse;
    
                    mCacheManager.setStringToMemory(mUrlData.url,bean);
    
                }
    
            }
    
            return response;
    
        }
    
    

    3.4 CacheManager

    这个类是用来管理有效请求的缓存。目前里面内容不多,主要核心的是一个LruCache,Lru的全称是Least Recently Used ,近期最少使用。其把近期最少使用的数据从缓存中移除,保留使用最频繁的数据。

    
    private LruCache mLruCache ;
    
        public CacheManager(){
    
            // maxMemory 是允许的最大值 ,超过这个最大值,则会回收
    
            long maxMemory = Runtime.getRuntime().maxMemory()/8; // 获取最大的可用内存 一般使用可用内存的1 / 8
    
            mLruCache = new LruCache<>((int)maxMemory);
    
        }
    
        public CacheBean getStringFromMemory(String url){
    
            CacheBean bean = mLruCache.get(url);
    
            return bean;
    
        }
    
        public void setStringToMemory(String url,CacheBean bean){
    
            mLruCache.put(url,bean);
    
        }
    
    

    3.5 CookieManager

    该类用来管理Cookie。用一个Map来存储Cookie,具体实现是ConcurrentHashMap。这是一个线程安全的Map类,相比于Hashtable,ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求。

    里面主要有两个方法:

    void storeCookies(URLConnection conn),用于 检索并存储由打开的URLConnection另一端的主机返回的cookie。必须使用connect()方法打开连接后调用,不然会抛IOException异常。

    
    public void storeCookies(URLConnection conn) throws IOException { 
    
         // 确认这些cookie是从哪边发送来的 
    
         String domain = getDomainFromHost(conn.getURL().getHost()); 
    
         // 为domain存储cookies 
    
         Map> domainStore; 
    
         synchronized (store) { 
    
             if (store.containsKey(domain)) { 
    
                 domainStore = store.get(domain); 
    
              } else { 
    
                 domainStore = new HashMap<>();
    
                 store.put(domain, domainStore); 
    
                     } 
    
             } 
    
               // 从URLConnection中获取cookies 
    
                 String headerName = null; 
    
                 for (int i = 1; (headerName = conn.getHeaderFieldKey(i)) != null; i++) { 
    
                     if (headerName.equalsIgnoreCase(SET_COOKIE)) { 
    
                     Map cookie = new HashMap<>();
    
                    StringTokenizer st = new StringTokenizer(conn.getHeaderField(i), COOKIE_VALUE_DELIMITER);
    
                    // http协议规范规定字符串中的第一个键值对是cookie键值,因此将它们作为特殊情况处理:
    
                    if (st.hasMoreTokens()) {
    
                        String token = st.nextToken();
    
                        String name = token.substring(0, token.indexOf(NAME_VALUE_SEPARATOR));
    
                        String value = token.substring(token.indexOf(NAME_VALUE_SEPARATOR) + 1, token.length());
    
                        domainStore.put(name, cookie);
    
                        cookie.put(name, value);
    
                    }
    
                    while (st.hasMoreTokens()) {
    
                        String token = st.nextToken();
    
                        cookie.put(token.substring(0,token.indexOf(NAME_VALUE_SEPARATOR)).toLowerCase(),
    
                                token.substring(token.indexOf(NAME_VALUE_SEPARATOR) + 1, token.length()));
    
                    }
    
                }
    
            }
    
            Log.d(TAG,"storeCookies : "+toString());
    
        }
    
    

    void setCookies(URLConnection conn) 此方法将设置全部未过期的cookie匹配路径或子路径。必须使用connect()方法打开连接前调用,不然会抛IOException异常。

    
    public void setCookies(URLConnection conn) throws IOException { 
    
         // 确定检索适当cookie的域和路径 URL url = conn.getURL(); 
    
         String domain = getDomainFromHost(url.getHost()); 
    
         String path = url.getPath(); Map> domainStore = store.get(domain); 
    
         if (domainStore == null) return; 
    
          StringBuffer cookieStringBuffer = new StringBuffer(); IteratorcookieNames = domainStore.keySet().iterator(); 
    
            while (cookieNames.hasNext()) { 
    
                 String cookieName = cookieNames.next(); 
    
                 Map cookie = domainStore.get(cookieName);
    
                // 检查cookie以确保路径匹配,并且cookie没有过期,将cookie添加到标题字符串
    
                if (comparePaths( cookie.get(PATH), path) && isNotExpired((String) cookie.get(EXPIRES))) {
    
                    cookieStringBuffer.append(cookieName);
    
                    cookieStringBuffer.append("=");
    
                    cookieStringBuffer.append( cookie.get(cookieName));
    
                    if (cookieNames.hasNext())
    
                        cookieStringBuffer.append(SET_COOKIE_SEPARATOR);
    
                }
    
            }
    
            try {
    
                Log.d(TAG,"setCookies : "+cookieStringBuffer.toString());
    
                conn.setRequestProperty(COOKIE, cookieStringBuffer.toString());
    
            } catch (java.lang.IllegalStateException ise) {
    
                IOException ioe = new IOException(
    
                        "无效的状态! 无法在已连接的URLConnection上设置Cookie。"
    
                                + " 只调用setCookies(java.net.URLConnection)后调用java.net.URLConnection.connect()。");
    
                throw ioe;
    
            }
    
        }
    
    

    4、总结

    总体该框架介绍完毕,该框架主要通过配置url.xml来配置参数。同时用一个线程池来处理请求,内置了缓存和对于Cookie的管理。宿主可以很方便的通过MyHttpClient类来调用这个框架。

    相关文章

      网友评论

        本文标题:Android HttpURLConnection简易框架

        本文链接:https://www.haomeiwen.com/subject/jdnsjftx.html