美文网首页网络请求Android知识Android技术知识
NoHttpConnecter——基于 NoHttp 的封装

NoHttpConnecter——基于 NoHttp 的封装

作者: 李世德Str | 来源:发表于2017-03-03 17:02 被阅读396次

    基于 NoHttp 的封装,主要包括字符串、Bitmap、JsonArray 等的 GET 和 POST 请求、文件上传下载方法的简单封装,以及五种缓存模式的使用。

    首先对 NoHttp 网络框架做一个简介

    Nohttp 是一个 Android Http 标准框架,底层可动态切换 OkHttp、URLConnection,与 RxJava 完美结合,支持缓存数据到数据库或 SD 卡(缓存数据自动加密),支持请求 Restful 风格的接口,比 Retrofit 更简单易用。

    Nohttp 框架特性

    • 动态配置底层框架为 OkHttp、HttpURLConnection
    • 支持异步请求、支持同步请求
    • 多文件上传,支持大文件上传,表单提交数据
    • 文件下载、上传下载、上传和下载的进度回调、错误回调
    • 支持 Json、xml、Map、List 的提交
    • 完美的 Http 缓存模式,可指定缓存到数据库、SD 卡,缓存数据已安全加密
    • 自定义 Request,直接请求 JsonObject、JavaBean 等
    • Cookie 的自动维持,App 重启、关开机后还持续维持
    • http 301 302 303 304 307 重定向,支持多层嵌套重定向
    • Https、自签名网站 Https 的访问、支持双向验证
    • 失败重试机制,支持请求优先级
    • GET、POST、PUT、PATCH、HEAD、DELETE、OPTIONS、TRACE 等请求协议
    • 用队列保存请求,平均分配多线程的资源,支持多个请求并发
    • 支持取消某个请求、取消指定多个请求、取消所有请求

    这么多好用的功能,难道你不想试试?

    NoHttp 开源框架地址:https://github.com/yanzhenjie/NoHttp

    使用方法

    1. Gradle添加依赖(推荐)

    compile 'com.yanzhenjie.nohttp:okhttp:1.1.2' (可能非最新版)
    

    2. 需要的权限

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    

    3. 初始化

    初始化 NoHttp,并设置 NoHttp 底层采用那种网络框架去请求,建议把初始化方法放到 ApplicationonCreate 生命周期方法里面。还有别忘了在manifest.xml中注册Application

    //初始化 NoHttp
    NoHttp.initialize(this, new NoHttp.Config()
            .setConnectTimeout(30 * 1000)  // 设置全局连接超时时间,单位毫秒,默认10s。
            .setReadTimeout(30 * 1000)  // 设置全局服务器响应超时时间,单位毫秒,默认10s。
            // 配置缓存,默认保存数据库DBCacheStore,保存到SD卡使用DiskCacheStore。
            .setCacheStore(
                    new DBCacheStore(this).setEnable(true) // 如果不使用缓存,设置setEnable(false)禁用。
            )
            // 配置Cookie,默认保存数据库DBCookieStore,开发者可以自己实现。
            .setCookieStore(
                    new DBCookieStore(this).setEnable(true) // 如果不维护cookie,设置false禁用。
            )
            // 配置网络层,默认使用URLConnection,如果想用OkHttp:OkHttpNetworkExecutor。
            .setNetworkExecutor(new OkHttpNetworkExecutor())
    );
    

    4.接下来,你就可以愉快的进行网络请求了:

    • new 队列
    RequestQueue requestQueue = NoHttp.newRequestQueue();
    
    • new 请求

    比如这样,

    Request<String> request = NoHttp.createStringRequest(url, RequestMethod.GET);
    

    或者这样,

    Request<JSONObject> objRequest = NoHttp.createJsonObjectRequest(url, RequestMethod.POST);
    

    ...等等(支持更多,如 JsonArray、Bitmap、byte[] 或自定义请求类型)。然后把需要的请求参数添加进来:

    .add("name", "name") // String类型
    ...
    
    • 把请求添加到队列,完成请求
    requestQueue.add(what, request, responseListener);
    
    • 回调对象,接受请求结果

    处理成功、失败等方法的回调,实现当前界面的业务和逻辑。

    • 添加请求到队列时有一个what,这个what会在responseLisetener响应时回调回来,所以可以用一个responseLisetener接受多个请求的响应,用 what 来区分结果。
    • 强烈建议把生成队列写成懒汉单例模式,因为每新建队列就会 new 出相应个数的线程来,同时只有线程数固定了,队列的作用才会发挥到最大。
    • 取消请求

    在组件销毁的时候(onDestroy())调用队列的按照 sign 取消的方法即可取消

    这时我们发现有很多重复的操作,每个 Activity 和 Fragment 都这么写就显得有点麻烦了,再加上上面的两条重要提示,所以我们这里把队列进行单例模式封装,并把这些操作封装在 BaseActivityBaseFragment 中。

    以下是 NoHttp 简单的封装,方便在项目中使用。

    1. NoHttp 请求
    public class HttpResponseListener<T> implements OnResponseListener<T> {
    
        private Activity mActivity;
        /**
         * Dialog
         */
        private WaitDialog mWaitDialog;
        /**
         * Request
         */
        private Request<?> mRequest;
        /**
         * 结果回调
         */
        private HttpListener<T> callback;
    
        /**
         * @param activity     context用来实例化dialog
         * @param request      请求对象
         * @param httpCallback 回调对象
         * @param canCancel    是否允许用户取消请求
         * @param isLoading    是否显示dialog
         */
        public HttpResponseListener(Activity activity, Request<?> request, HttpListener<T> httpCallback,
                                    boolean canCancel, boolean isLoading) {
            this.mActivity = activity;
            this.mRequest = request;
            if (activity != null && isLoading) {
                mWaitDialog = new WaitDialog(activity);
                mWaitDialog.setCancelable(canCancel);
                mWaitDialog.setOnCancelListener(dialog -> mRequest.cancel());
            }
            this.callback = httpCallback;
        }
    
        /**
         * 开始请求, 这里显示一个dialog
         */
        @Override
        public void onStart(int what) {
            if (mWaitDialog != null && !mActivity.isFinishing() && !mWaitDialog.isShowing())
                mWaitDialog.show();
        }
    
        /**
         * 结束请求, 这里关闭dialog
         */
        @Override
        public void onFinish(int what) {
            if (mWaitDialog != null && mWaitDialog.isShowing())
                mWaitDialog.dismiss();
        }
    
        /**
         * 成功回调
         */
        @Override
        public void onSucceed(int what, Response<T> response) {
            if (callback != null) {
                // 这里判断一下http响应码,这个响应码问下你们的服务端你们的状态有几种,一般是200成功。
                // w3c标准http响应码:http://www.w3school.com.cn/tags/html_ref_httpmessages.asp
    
                callback.onSucceed(what, response);
            }
        }
    
        /**
         * 失败回调
         */
        @Override
        public void onFailed(int what, Response<T> response) {
            Exception exception = response.getException();
            if (exception instanceof NetworkError) {// 网络不好
                Toast.show(mActivity, R.string.error_please_check_network);
            } else if (exception instanceof TimeoutError) {// 请求超时
                Toast.show(mActivity, R.string.error_timeout);
            } else if (exception instanceof UnKnownHostError) {// 找不到服务器
                Toast.show(mActivity, R.string.error_not_found_server);
            } else if (exception instanceof URLError) {// URL是错的
                Toast.show(mActivity, R.string.error_url_error);
            } else if (exception instanceof NotFoundCacheError) {
                // 这个异常只会在仅仅查找缓存时没有找到缓存时返回
                Toast.show(mActivity, R.string.error_not_found_cache);
            } else {
                Toast.show(mActivity, R.string.error_unknow);
            }
            Logger.e("错误:" + exception.getMessage());
            if (callback != null)
                callback.onFailed(what, response);
        }
    
    }
    
    2. 接受回调结果
    public interface HttpListener<T> {
    
        void onSucceed(int what, Response<T> response);
    
        void onFailed(int what, Response<T> response);
    
    }
    
    3. 队列的单例模式封装 -- 提供外部调用进行网络请求
    public class CallServer {
    
        private static CallServer instance;
    
        /**
         * 请求队列
         */
        private RequestQueue requestQueue;
    
        private CallServer() {
            // 初始化请求队列,传入的参数是请求并发值
            requestQueue = NoHttp.newRequestQueue(3);
        }
    
        /**
         * 请求队列
         */
        public synchronized static CallServer getInstance() {
            if (instance == null)
                synchronized (CallServer.class) {
                    if (instance == null)
                        instance = new CallServer();
                }
            return instance;
        }
    
        /**
         * 添加一个请求到请求队列
         *
         * @param activity     Context
         * @param what         用来标志请求, 当多个请求使用同一个Listener时, 在回调方法中会返回这个what。
         * @param request      请求对象
         * @param httpCallback 回调函数
         * @param canCancel    是否能被用户取消
         * @param isLoading    是否显示加载框
         * @param <T>
         */
        public <T> void add(Activity activity, int what, Request<T> request,
                            HttpListener<T> httpCallback, boolean canCancel, boolean isLoading) {
            requestQueue.add(what, request, new HttpResponseListener<>(activity, request, httpCallback,
                    canCancel, isLoading));
        }
    
        /**
         * 取消这个sign标记的所有请求
         *
         * @param sign 请求的取消标志
         */
        public void cancelBySign(Object sign) {
            requestQueue.cancelBySign(sign);
        }
    
        /**
         * 取消队列中所有请求
         */
        public void cancelAll() {
            requestQueue.cancelAll();
        }
    
        /**
         * 停止队列
         */
        public void stop() {
            requestQueue.stop();
        }
    }
    
    4. 在 BaseActivity、BaseFragment 中发起请求,并设置标记
    ...
        /**
         * 用来标记取消
         */
        private Object object = new Object();
    
        /**
         * 发起请求
         *
         * @param what      what.
         * @param request   请求对象。
         * @param callback  回调函数。
         * @param canCancel 是否能被用户取消。
         * @param isLoading 实现显示加载框。
         * @param <T>       想请求到的数据类型。
         */
        public <T> void request(int what, Request<T> request, HttpListener<T> callback,
                                boolean canCancel, boolean isLoading) {
            // 这里设置一个sign给这个请求
            request.setCancelSign(object);
            CallServer.getInstance().add(context, what, request, callback, canCancel, isLoading);
        }
    
        @Override
        protected void onDestroy() {
            // 和声明周期绑定,退出时取消这个队列中的所有请求,当然可以在你想取消的时候取消也可以,不一定和声明周期绑定。
            CallServer.getInstance().cancelBySign(object);
            super.onDestroy();
        }
    
    5. 其它界面继承 BaseActivity 或 BaseFragment,请求网络的方式跟上面的类似,多了几个参数而已。
    ...
    Request<String> request = NoHttp.createStringRequest(url, RequestMethod.GET);
    request.add("name", "name")
    request(0, request, httpListener, true, true);
    
    附 0. 封装中使用到的自定义控件及引用文字
    // 加载框
    public class WaitDialog extends ProgressDialog {
    
        public WaitDialog(Context context) {
            super(context);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            setCanceledOnTouchOutside(false);
            setProgressStyle(STYLE_SPINNER);
            setMessage(context.getText(R.string.wait_dialog_title));
        }
    
    }
    
    //代码中的引用字符串
    <string name="wait_dialog_title">正在请求,请稍候…</string>
    
    <!-- error -->
    <string name="error_please_check_network">请检查网络</string>
    <string name="error_timeout">请求超时,网络不好或者服务器不稳定</string>
    <string name="error_not_found_server">未发现指定服务器,请切换网络后重试</string>
    <string name="error_url_error">URL错误</string>
    <string name="error_not_found_cache">没有找到缓存</string>
    <string name="error_unknow">未知错误</string>
    <string name="error_response_code">服务器响应码%1$d</string>
    <string name="error_response_code_dex">服务器响应码%1$d,如果你们服务器在这种情况下也返回数据你可以处理</string>
    

    现已将此封装发布到 JitPack ,可以直接使用如下方式引入。

    [图片上传失败...(image-8ba3f4-1520919194946)]

    JitPack 引入方法

    1. 在 Project 下的 build.gradle 添加
    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
    
    2. 在 Module 下的 build.gradle 添加
    dependencies {
        compile 'com.github.lishide:NoHttpConnecter:v+latest version'
        //latest version 见上方 JitPack 图标所示,如:
        compile 'com.github.lishide:NoHttpConnecter:v1.0.2'
    }
    

    五大缓存模式

    • 1、Default 模式,实现 http304 重定向缓存
    request.setCacheMode(CacheMode.DEFAULT);
    
    • 2、请求网络失败返回缓存
    request.setCacheMode(CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE);
    
    • 3、没有缓存才去请求网络
    request.setCacheMode(CacheMode.NONE_CACHE_REQUEST_NETWORK);
    
    • 4、仅仅请求网络
    request.setCacheMode(CacheMode.ONLY_REQUEST_NETWORK);
    
    • 5、仅仅读取缓存
    request.setCacheMode(CacheMode.ONLY_READ_CACHE);
    

    文件下载

    • 1、单个文件下载
    • 2、多个文件下载

    文件下载也是队列,队列和开头所说的请求的队列是一样的。

    • 发起下载请求
    mDownloadRequest = NoHttp.createDownloadRequest(url, path, filename, true, true);
    downloadQueue.add(0, mDownloadRequest, downloadListener);
    
    • 暂停或者停止下载
    mDownloadRequest.cancel();
    
    • 监听下载过程
    private DownloadListener downloadListener = new DownloadListener() {
    
      // 下载开始
      @Override
      public void onStart(int what, boolean isResume, long beforeLength, Headers headers, long allCount) {
    
      }
    
      // 下载发生错误
      @Override
      public void onDownloadError(int what, Exception exception) {
    
      }
    
      // 更新下载进度和下载网速
      @Override
      public void onProgress(int what, int progress, long fileCount, long speed) {
    
      }
    
      // 下载完成
      @Override
      public void onFinish(int what, String filePath) {
    
      }
    
      // 下载被取消或者暂停
      @Override
      public void onCancel(int what) {
    
      }
    };
    

    关于文件下载,具体的请参考 Demo。

    文件上传

    • 1、单个文件上传
    Request<String> request = NoHttp.createStringRequest(url, RequestMethod.POST);
    request.add("file", new FileBinary(file));
    
    • 2、多个文件上传(这里可以添加各种形式的文件,File、Bitmap、InputStream、ByteArray。)

      • 多个Key多个文件形式
      Request<String> request = ...
      request.add("file1", new FileBinary(File));
      request.add("file2", new FileBinary(File));
      request.add("file3", new InputStreamBinary(InputStream));
      request.add("file4", new ByteArrayBinary(byte[]));
      request.add("file5", new BitmapBinary(Bitmap));
      
      • 一个Key多个文件形式
      Request<String> request = ...
      fileList.add("image", new FileBinary(File));
      fileList.add("image", new InputStreamBinary(InputStream));
      fileList.add("image", new ByteArrayBinary(byte[]));
      fileList.add("image", new BitmapBinary(Bitmap));
      

      或者:

      Request<String> request = ...
      List<Binary> fileList = ...
      fileList.add(new FileBinary(File));
      fileList.add(new InputStreamBinary(InputStream));
      fileList.add(new ByteArrayBinary(byte[]));
      fileList.add(new BitmapStreamBinary(Bitmap));
      request.add("file_list", fileList);
      

    ......


    本人仅是简单地对 NoHttp 网络请求框架进行轻量级的封装,后期还会进行持续维护,更多关于 NoHttp 的使用可直接查看原作。

    NoHttp —— 一个有情怀的网络框架 ,让你的网络请求更简单。

    像上面说的一样,NoHttp 真的很强大、很好用,嗯,没错。

    Demo 源码请看 GitHub: NoHttpConnecter

    相关文章

      网友评论

      • 4e78da03a0b7:然而怎么封装还是没讲
        李世德Str:@Al_国建 好吧,文章中确实没提到 Https,不过已经在 simple 中有相关示例,推荐下载 demo 看代码,demo 里面的例子和注释非常全了。
        李世德Str:文章已更新,欢迎继续支持:heart:
        李世德Str:文章写得比较急,还不够完善,后期还会继续补充,感谢阅读,封装源码请先移步GitHub查看。

      本文标题:NoHttpConnecter——基于 NoHttp 的封装

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