造轮子--撸一个网络请求框架LSHttp

前言

为何自己动手撸了一个呢,不使用流行的Retrofit+RxJava + OkHttp 方式。首先是因为对RxJava不了解,在平常项目中没有使用过,其次就为了一个网络框架引入RxJava RxAndroid 等,得不尝试,增加apk体积。以上观点仅代表个人观点,不喜勿喷。

介绍

LSHttp库,就仅仅是对OkHttp进行了一些封装,采用链式调用的方法,一行代码实现网络请求,支持绑定当前Activity、Fragment,在销毁时自动取消请求,让开发者使用起来更方便。

使用

compile 'com.lishang.http:LSHttp:1.0.1'

支持以下操作

  • get请求
  • post表单、json
  • 上传(单文件或多文件,支持进度监听)
  • 下载(断点下载)
  • 页面销毁时自动取消请求
  • 自定义转换器,默认提供了StringCallBack,JsonCallBack

看到这里有些小伙伴就要说了,put、delete、head、options、trace、patch 这些请求为何不支持呢?这里分实话假话,实话就是水平有限我不会,工作了几年了没有用过。假话就是,在实际开发中Get/Post两种请求就可以满足99%的场景了。当然如果你的业务需求要用到的话,也可以继承BaseRequest,实现generateRequest方法就可以了。

各位大佬注意了,接下来就要开始介绍使用方法了

配置 在Application里面:

LSHttp.init(this); //初始化,使用默认配置

//配置自己的OkHttpClient
OkHttpClient mClient = new OkHttpClient.Builder()
            .writeTimeout(15, TimeUnit.SECONDS)
            .readTimeout(15, TimeUnit.SECONDS)
            .connectTimeout(15, TimeUnit.SECONDS)
            .addInterceptor(new LSHttpLoggingInterceptor().setLevel(LSHttpLoggingInterceptor.Level.BODY))
            //其它配置
            .build();

LSHttp.init(this, mClient)
            .showLog(true) //是否显示日志
            .addHeader("key", "value") //全局添加header
            .addHeaders(map) //添加多个headers
            .baseUrl("https://wanandroid.com/"); //baseurl

baseUrl说明:

如果配置了baseUrl,在request时可以直接传入路径,例如:user/login,如果某次请求的baseUrl不是当前配置的,直接传入完整的url就可以了,例如:https://www.33lc.com/article/UploadPic/2012-7/201272510182494484.jpg

如何做到Activity、Fragment销毁,请求自动取消

LSHttp.xxx(url).
execute(this); //this表示Activity或者Fragment

LSHttp.xxx(url).
    execute(); //不传 Activity或者Fragment,在页面销毁时请求不会取消

CallBack介绍,打交道最多的,最终请求数据都会回到CallBack,默认提供了两种CallBack:

StringCallBack 请求返回字符串

 LSHttp.xxx(url).
       .callback(new StringCallBack(){
            @Override
            public void onSuccess(String string) {
                Log.e("onSuccess", string);
            

            }

            @Override
            public void onFail(LSHttpException e) {
                              

            }
       });

JsonCallBack 返回实体类 内部使用了Gson

LSHttp.xxx(url).
       .callback(new JsonCallBack<JsonData>(){
            @Override
            public void onSuccess(JsonData data) {
                Log.e("onSuccess", data.toString());
            

            }

            @Override
            public void onFail(LSHttpException e) {
                              

            }
       });

如果StringCallBack和JsonCallBack不能满足你的使用,还可以自定义转换器,需要三步:

1.定义接口,继承ResponseCallBack

public interface BitmapCallBack extends ResponseCallBack {
    void onSuccess(Bitmap bitmap);
}

2.创建Class,实现接口IConvertResponse

public class BitmapConvertResponse implements IConvertResponse {
    BitmapCallBack callBack;

    @Override
    public void convert(Response response) {
        InputStream inputStream = response.body().byteStream();//得到图片的流
        final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
        //子线程中
        LSHttp.getInstance().runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (callBack != null) {
                    callBack.onSuccess(bitmap);
                }
            }
        });
    }

    @Override
    public void setCallBack(ResponseCallBack callBack) {
        this.callBack = (BitmapCallBack) callBack;
    }
}

3.请求

 LSHttp.get("https://www.33lc.com/article/UploadPic/2012-7/201272510182494484.jpg")
            .convert(new BitmapConvertResponse())
            .callback(new BitmapCallBack() {
                @Override
                public void onSuccess(Bitmap bitmap) {
                    if (bitmap != null) {
                        mImage.setImageBitmap(bitmap);
                    }
                }

                @Override
                public void onFail(LSHttpException e) {

                }
            }).execute(this);

4.如何知道,请求是onSuccess或onFail

@Override
public void onResponse(Call call, final Response response) throws IOException {

     if (response.isSuccessful()) {
        //请求成功
        onConvertCallBack(response);
    } else {
        runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (callBack != null) {
                    callBack.onFail(new LSHttpException(LSHttpException.ERROR.HTTP_ERROR, "请求失败,服务器开小差...", response.code()));
                }
            }
        });
    }
    removeLifecycle(obj, call);
}
  • response.isSuccessful() 判断服务端返回的status是否在200~300,如果是,进行onConvertCallBack,将结果转化成对应的数据。

  • 如果status没有在200~300之间,就进行onFail处理,将status抛给上层。

  • onConvertCallBack方法,进行区分使用自定义的Convert还是默认的JSONCallBack/StringCallBack

      public void onConvertCallBack(final Response response) {
          //用户是否需要自定义Convert
          if (convertResponse != null) {
              convertResponse.setCallBack(callBack);
              convertResponse.convert(response);
          } else if (callBack != null) {
              
              if (callBack instanceof JsonCallBack) {
                  JsonConvertResponse convertResponse = new JsonConvertResponse();
                  convertResponse.setCallBack(callBack);
                  convertResponse.convert(response);
              } else {
                  StringConvertResponse convertResponse = new StringConvertResponse();
                  convertResponse.setCallBack(callBack);
                  convertResponse.convert(response);
              }
          }
      }
    

如何发起一个请求呢?

LSHttp.xxx(url) //请求方法,get/post/json/download/multipart
      .xxx()//不同请求支持的方法
      .addHeader(key,value)//添加header
      .addHeaders(map)//添加多个header
      .tag(tag)//指定tag,用于取消
      .convert(IConvertResponse)//转换器,使用方法上面有介绍
      .callback(ResponseCallBack)//请求回调
      .execute(null/Activity/Fragment);//开始执行,传入Activity和Fragment,就会在页面销毁时自动取消请求

Get请求

    LSHttp.get("wxarticle/chapters/json") 
            .callback(new JsonCallBack<JsonData>() {
                @Override
                public void onFail(LSHttpException e) {
                    mText.setText("请求失败:\n" + e.message);
                }

                @Override
                public void onSuccess(JsonData data) {
                    Log.e("onSuccess", data.toString());
                    mText.setText("请求成功:\n" + data.toString());

                }
            }).execute();

Post请求

//表单请求  application/x-www-form-urlencoded
LSHttp.post("user/login")
            .addParams("username", "xxxx") //请求参数
            .addParams("password", "xxxxx")
            .callback(new StringCallBack() {
                @Override
                public void onSuccess(String string) {
                    Log.e("onSuccess", string);
                    mText.setText("请求成功:\n" + string);

                }

                @Override
                public void onFail(LSHttpException e) {
                    mText.setText("请求失败:\n" + e.message);

                }
            }).execute();

//json请求   json application/json; charset=utf-8
LSHttp.json("https://www.wanandroid.com/user/login")
            .setJson(json) //json字符串
            .callback(new StringCallBack() {
                @Override
                public void onSuccess(String string) {
                    Log.e("onSuccess", string);
                    mText.setText("请求成功:\n" + string);

                }

                @Override
                public void onFail(LSHttpException e) {
                    mText.setText("请求失败:\n" + e.message);

                }
            }).execute();

下载

String url = "https://6082fcfd683b09af1a65ea78561240f8.dd.cdntips.com/download.sj.qq.com/upload/connAssitantDownload/upload/MobileAssistant_1.apk";

                String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/abcd/apk.apk";
                LSHttp.download(url).path(path).tag("down").callback(new DownloadCallBack() {
                    @Override
                    public void onStart() {
                        Log.e("onStart", "开始下载");
                        mTxtResult.setText("开始下载...");
                    }

                    @Override
                    public void onLoading(int progress) {
                        Log.e("onLoading", "下载中" + progress);
                        mProgressHorizontal.setProgress(progress);
                        mTxtProgress.setText("" + progress + "%");
                    }

                    @Override
                    public void onSuccess(String path) {
                        Log.e("onSuccess", "下载成功" + path);
                        mTxtResult.setText("下载成功" + path);
                    }

                    @Override
                    public void onFail(LSHttpException e) {
                        mTxtResult.setText("请求失败:\n" + e.message);
                    }
                }).execute(DownloadActivity.this);
            
//取消下载
LSHttp.cancel("down");

上传

//替换成自己的
String url = "https://www.wanandroid.com/image/upload";


 String path = Environment.getExternalStorageDirectory().getAbsolutePath();

LSHttp.multipart(url).addFile("image_1", new File(path + "/123.jpg"))
                    .addFile("image_2", new File(path + "/456.jpg"))
                    .addHeader("Authorization", "3c4dfe6f7a7caaa7ccca39ec157554f5")
                    .progress(new UploadCallBack() {
                            @Override
                            public void onLoading(int progress) {
                                mProgressHorizontal.setProgress(progress);
                                mTxtProgress.setText("" + progress + "%");
                            }
                        })
                    .callback(new StringCallBack() {
                            @Override
                            public void onSuccess(String string) {
                                Log.e("onSuccess", string);
                                mTxtResult.setText("请求成功:\n" + string);

                            }

                            @Override
                            public void onFail(LSHttpException e) {
                                mTxtResult.setText("请求失败:\n" + e.message);

                            }
                    }).execute(UploadFragment.this);

上传下载需要读写权限,推荐 LSPermissions

如何扩展请求,例如put请求

1.继承BaseRequest
public class PutRequest extends BaseRequest <PutRequest>{
    @Override
    public Request generateRequest(Request.Builder builder) {
        
        return builder.put(requestbody).build();
    }
}
2.执行
new PutRequest().url()
                .xxx()
                .xxx()
                .execute();

以上使用方法已经介绍完毕,接下来开始看下源码

源码

BaseRequest

定义了请求需要的公用参数,以及请求逻辑,绑定Acitivy、Fragment生命周期

public abstract class BaseRequest<T extends BaseRequest> {
    protected OkHttpClient mClient;
    protected LSHttpActivityLifecycleCallBacks lifecycleCallBacks;
    protected String url; //请求url
    protected Map<String, String> headers = new HashMap<>(); //请求头参数
    protected String tag;//请求tag
    protected ResponseCallBack callBack;//请求回调
    protected IConvertResponse convertResponse; //用于自定义转换器

    public BaseRequest() {
        mClient = LSHttp.getInstance().getClient();
        lifecycleCallBacks = LSHttp.getInstance().getLifecycleCallBacks();
    }

    public ResponseCallBack getCallBack() {
        return callBack;
    }

    //添加请求header
    public T addHeader(String key, String value) {
        headers.put(key, value);
        return (T) this;
    }

    //添加多个header
    public T addHeaders(Map<String, String> map) {
        if (map != null) {
            headers.putAll(map);
        }
        return (T) this;
    }

    //url
    public T url(String url) {
        this.url = url;
        return (T) this;
    }

    //tag
    public T tag(String tag) {
        this.tag = tag;
        return (T) this;
    }

    //请求结果返回
    public T callback(ResponseCallBack callBack) {
        this.callBack = callBack;
        return (T) this;
    }

    //转换器
    public T convert(IConvertResponse convertResponse) {
        this.convertResponse = convertResponse;
        return (T) this;
    }

    //创建headers
    public Headers createHeaders() {
        Headers.Builder builder = new Headers.Builder();
        for (String key : headers.keySet()) {
            builder.add(key, headers.get(key));
        }
        return builder.build();
    }

    //构建基础request.builder
    public Request.Builder request() {
        Request.Builder builder = new Request.Builder()
                .url(url)
                .tag(tag)
                .headers(createHeaders());
        return builder;
    }

    //用于定制request 差异
    public abstract Request generateRequest(Request.Builder builder);

    //异步执行网络请求
    public void execute() {
        execute(null);
    }


    //异步执行网络请求 obj 支持Activity、Fragment
    public void execute(final Object obj) {
        checkUrl();

        OkHttpClient client = mClient;


        Call call = client.newCall(generateRequest(request()));
        //与当前Acitivy、Fragment绑定关系
        bindLifecycle(obj, call);


        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, final IOException e) {
                e.printStackTrace();
                if (call.isCanceled()) {
                    System.out.println("call is canceled");
                    return;
                }
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        if (callBack != null) {
                            callBack.onFail(LSHttpException.handleException(e));
                        }
                    }
                });
                //请求结束,移除绑定关系
                removeLifecycle(obj, call);

            }

            @Override
            public void onResponse(Call call, final Response response) throws IOException {

                if (response.code() == 200) {
                    //请求成功
                    if (convertResponse != null) {
                        convertResponse.setCallBack(callBack);
                        convertResponse.convert(response);
                    } else if (callBack != null) {

                        if (callBack instanceof JsonCallBack) {
                            JsonConvertResponse convertResponse = new JsonConvertResponse();
                            convertResponse.setCallBack(callBack);
                            convertResponse.convert(response);
                        } else {
                            StringConvertResponse convertResponse = new StringConvertResponse();
                            convertResponse.setCallBack(callBack);
                            convertResponse.convert(response);
                        }
                    }
                } else {

                    runOnMainThread(new Runnable() {
                        @Override
                        public void run() {
                            if (callBack != null) {
                                callBack.onFail(new LSHttpException(LSHttpException.ERROR.HTTP_ERROR, "请求失败,服务器开小差..." + response.code()));
                            }
                        }
                    });
                }
                //请求结束,移除绑定关系
                removeLifecycle(obj, call);
            }
            });
    }

    //检查url是否合法
    public void checkUrl() {
        if (TextUtils.isEmpty(this.url)) {
            throw new NullPointerException("url is null");
        }
        String baseUrl = LSHttp.getInstance().getBaseUrl();
        if (!TextUtils.isEmpty(baseUrl)) {
            Uri uri = Uri.parse(url);
            if ("http".equalsIgnoreCase(uri.getScheme()) || "https".equalsIgnoreCase(uri.getScheme())) {

            } else {
                url = baseUrl + url;
            }
        }
    }

    //运行到主线程
    public void runOnMainThread(Runnable runnable) {
        LSHttp.getInstance().runOnMainThread(runnable);
    }

    //绑定Activity、Fragment
    public void bindLifecycle(Object obj, Call call) {
        if (lifecycleCallBacks != null && obj != null) {
            if (obj instanceof Activity || obj instanceof Fragment)
                lifecycleCallBacks.put(obj.getClass().getName(), call);
        }
    }

    //移除绑定
    public void removeLifecycle(Object obj, Call call) {
        if (lifecycleCallBacks != null && obj != null) {
            lifecycleCallBacks.remove(obj.getClass().getName(), call);
        }
    }

    public OkHttpClient getClient() {
        return mClient;
    }

    public LSHttpActivityLifecycleCallBacks getLifecycleCallBacks() {
        return lifecycleCallBacks;
    }

    public String getUrl() {
        return url;
    }

    public Map<String, String> getHeaders() {
        return headers;
    }

    public String getTag() {
        return tag;
    }

    public IConvertResponse getConvertResponse() {
        return convertResponse;
    }
}

IConvertResponse

请求结果转换接口,可以根据自己需求,实现该接口,进行定制化

public interface IConvertResponse {
/**
 * 转换 
 * @param response
 */
void convert(Response response);

/**
 * 回调
 * @param callBack
 */
void setCallBack(ResponseCallBack callBack);
}

ResponseCallBack

和上面的 IConvertResponse 配合使用,只定义了失败方法,成功方法根据转换自己定义对应的Success

public interface ResponseCallBack {

    void onFail(LSHttpException e);

}

LSHttpException,定义了些常见的错误处理,在三种情况下会收到

1-请求失败在如,SockteTimeOutException,等IO异常时,即OkHttp返回onFailure。

2-请求成功,但是服务端返回http status 不在200~300之间,如404,500
3-请求成功,并且http status在200~300之间,但是convert时出错。

public class LSHttpException extends Exception {

    /**
    * 约定异常
    */
    public static class ERROR {
        /**
        * 未知错误
        */
        public static final int UNKNOWN = 1000;
        /**
        * 连接超时
        */
        public static final int TIMEOUT_ERROR = 1001;
        /**
        * 空指针错误
        */
        public static final int NULL_POINTER_EXCEPTION = 1002;

        /**
        * 证书出错
        */
        public static final int SSL_ERROR = 1003;

        /**
        * 类转换错误
        */
        public static final int CAST_ERROR = 1004;

        /**
        * 解析错误
        */
        public static final int PARSE_ERROR = 1005;

        /**
        * 非法数据异常
        */
        public static final int ILLEGAL_STATE_ERROR = 1006;

        /**
        * 服务端异常 http code >= 200 && <300
        */
        public static final int HTTP_ERROR = 1007;


    }


    public final int code; //错误Code
    public String message; //描述
    public int status; //当code == 1007 时 http status;

    public LSHttpException(Throwable throwable, int code) {
        this(throwable, code, -1);
    }

    public LSHttpException(Throwable throwable, int code, int status) {
        super(throwable);
        this.code = code;
        this.message = throwable.getMessage();
        this.status = status;
    }

    public LSHttpException(int code, String message) {
        this(code, message, -1);
    }

    public LSHttpException(int code, String message, int status) {
        this.code = code;
        this.message = message;
        this.status = status;
    }


    public static LSHttpException handleException(Throwable e) {
        LSHttpException ex;
        if (e instanceof SocketTimeoutException) {
            ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "网络不可用,请稍后再试";
            return ex;
        } else if (e instanceof ConnectException) {
            ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "网络不可用,请稍后再试";
            return ex;
        } else if (e instanceof ConnectTimeoutException) {
            ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "网络不可用,请稍后再试";
            return ex;
        } else if (e instanceof UnknownHostException) {
            ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "网络不可用,请稍后再试";
            return ex;
        } else if (e instanceof NullPointerException) {
            ex = new LSHttpException(e, ERROR.NULL_POINTER_EXCEPTION);
            ex.message = "空指针异常";
            return ex;
        } else if (e instanceof javax.net.ssl.SSLHandshakeException) {
            ex = new LSHttpException(e, ERROR.SSL_ERROR);
            ex.message = "证书验证失败";
            return ex;
        } else if (e instanceof ClassCastException) {
            ex = new LSHttpException(e, ERROR.CAST_ERROR);
            ex.message = "类型转换错误";
            return ex;
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof JsonSerializer
                || e instanceof NotSerializableException
                || e instanceof ParseException) {
            ex = new LSHttpException(e, ERROR.PARSE_ERROR);
            ex.message = "解析错误";
            return ex;
        } else if (e instanceof IllegalStateException) {
            ex = new LSHttpException(e, ERROR.ILLEGAL_STATE_ERROR);
            ex.message = e.getMessage();
            return ex;
        } else {
            ex = new LSHttpException(e, ERROR.UNKNOWN);
            ex.message = "未知错误";
            return ex;
        }
    }

}

请求如何绑定Activity、Fragment

这里主要使用了 ActivityLifecycleCallbacks 和 FragmentManager.FragmentLifecycleCallbacks ,监听页面销毁时,将请求取消

LSHttpLifecycleCallBacks

/**
* 绑定生命周期基类、对外提供put remove
*/
public class LSHttpLifecycleCallBacks {

    private ConcurrentMap<String, SparseArray<Call>> map = new ConcurrentHashMap<>();

    public void put(String key, Call call) {
        SparseArray<Call> calls = map.get(key);
        if (calls == null) {
            calls = new SparseArray<>();
        }
        calls.put(call.hashCode(), call);

        map.put(key, calls);

        LSLog.i("LifecycleCallBacks class:" + key + "call bind success");

    }

    public void remove(String key, Call call) {
        SparseArray<Call> calls = map.get(key);
        if (call != null) {
            calls.delete(call.hashCode());
            LSLog.i("LifecycleCallBacks class:" + key + "  call " + call.request().url().toString() + " remove success");
        }


    }

    public void destroyed(String key) {
        SparseArray<Call> calls = map.get(key);
        if (calls != null) {
            for (int i = 0; i < calls.size(); i++) {
                Call call = calls.valueAt(i);
                if (call != null && !call.isCanceled()) {
                    call.cancel();
                    LSLog.i("LifecycleCallBacks class:" + key + "  destroyed call " + call.request().url().toString() + " cancel success");
                }
            }
            calls.clear();
            map.remove(key);

            LSLog.i("LifecycleCallBacks class:" + key + "  destroyed call remove success");

        }
    }

}

LSHttpActivityLifecycleCallBacks

/**
* request 绑定生命周期 Activity销毁时自动取消
*/
public class LSHttpActivityLifecycleCallBacks extends LSHttpLifecycleCallBacks implements Application.ActivityLifecycleCallbacks {


    private LSHttpFragmentLifecycleCallBacks fragmentLifecycleCallBacks;

    public LSHttpActivityLifecycleCallBacks() {
        this.fragmentLifecycleCallBacks = new LSHttpFragmentLifecycleCallBacks(this);
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle bundle) {

        if (activity instanceof FragmentActivity) {

            FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager();
            fm.registerFragmentLifecycleCallbacks(fragmentLifecycleCallBacks, true);

        }
    }

    @Override
    public void onActivityStarted(Activity activity) {

    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {

    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle bundle) {

    }

    @Override
    public void onActivityDestroyed(Activity activity) {

        String key = activity.getClass().getName();
        destroyed(key);
    }


}

LSHttpFragmentLifecycleCallBacks

/**
* request 绑定Fragment 生命周期
*/
public class LSHttpFragmentLifecycleCallBacks extends FragmentManager.FragmentLifecycleCallbacks {

    private LSHttpLifecycleCallBacks callBacks;

    public LSHttpFragmentLifecycleCallBacks(LSHttpLifecycleCallBacks callBacks) {
        this.callBacks = callBacks;
    }


    @Override
    public void onFragmentDestroyed(FragmentManager fm, Fragment f) {
        super.onFragmentDestroyed(fm, f);

        String key = f.getClass().getName();
        callBacks.destroyed(key);


        fm.unregisterFragmentLifecycleCallbacks(this);

    }
}

下载

支持进度监听,通过查资料,一般有两种实现方式进行进度监听,一种是在请求成功后,在写入file时,监听的本地文件写入进度,还有一种就是通过网络拦截器包装ResponseBody,这种是监听网络下载进度,下载完毕后再写入file。但是我在写的时候发现了一个问题,那就是进度卡在100%,然后等file写入完毕后,才能拿到文件。我在方案二的基础上进行了改造,实现了边下边存,当下载完毕时文件也保存完毕了。

1.定义接口

interface ProgressListener {
    //下载进度监听
    void update(long bytesRead, long contentLength, boolean done);
    //写入文件
    void writeTo(byte[] bytes, int off, int len);
}

2.继承ResponseBody

private static class ProgressResponseBody extends ResponseBody {

    private final ResponseBody responseBody;
    private final ProgressListener progressListener;
    private BufferedSource bufferedSource;

    ProgressResponseBody(ResponseBody responseBody, ProgressListener progressListener) {
        this.responseBody = responseBody;
        this.progressListener = progressListener;

    }

    @Override
    public MediaType contentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                // read() returns the number of bytes read, or -1 if this source is exhausted.
                boolean done = bytesRead == -1;
                if (!done) {
                    byte[] bytes = sink.readByteArray();
                    progressListener.writeTo(bytes, 0, bytes.length);
                }

                totalBytesRead += bytesRead != -1 ? bytesRead : 0;

                LSLog.i("read size:" + totalBytesRead);

                progressListener.update(totalBytesRead, responseBody.contentLength(), done);
                return bytesRead;
            }
        };
    }
}

3.添加拦截器

this.mClient = builder
            .addNetworkInterceptor(new Interceptor() {
                @Override
                public Response intercept(Chain chain) throws IOException {

                    Response originalResponse = chain.proceed(chain.request());

                    if (originalResponse.isSuccessful()) {
                        return originalResponse.newBuilder()
                                .body(new ProgressResponseBody(originalResponse.body(), listener))
                                .build();
                    } else {
                        return originalResponse;
                    }


                }
            })
            .build();

关键代码,在第二步

private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                // read() returns the number of bytes read, or -1 if this source is exhausted.
                boolean done = bytesRead == -1;
                if (!done) {
                    byte[] bytes = sink.readByteArray();
                    progressListener.writeTo(bytes, 0, bytes.length);
                }

                totalBytesRead += bytesRead != -1 ? bytesRead : 0;

                LSLog.i("read size:" + totalBytesRead);

                progressListener.update(totalBytesRead, responseBody.contentLength(), done);
                return bytesRead;
            }
        };
    }

如果没有下载完,不断的将读取的字节写入到文件

if (!done) {
    byte[] bytes = sink.readByteArray();
    progressListener.writeTo(bytes, 0, bytes.length);
}

通过RandomAccessFile 不断的将字节写入到文件

private class DownloadFile {
    RandomAccessFile randomAccessFile;
    File file;
    Call call;

    public DownloadFile(String path, String url) throws FileNotFoundException {
       ...
    }

    public void setCall(Call call) {
        this.call = call;
    }

    public void seek(long pos) {
        try {
            randomAccessFile.seek(pos);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void write(byte[] bytes, int off, int len) throws IOException {
        randomAccessFile.write(bytes, off, len);
    }

    public void close() {
        try {
            randomAccessFile.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public long size() {
        if (file != null) {
            return file.length();
        } else {
            return 0;
        }
    }


    public void cancel() {
        call.cancel();
    }

}

监听

 final ProgressListener listener = new ProgressListener() {
        boolean firstUpdate = true;

        @Override
        public void update(long bytesRead, long contentLength, boolean done) {
            if (done) {
                System.out.println("completed");
                success(downloadFile.file.getAbsolutePath());
                downloadFile.close();
            } else {
                if (firstUpdate) {
                    firstUpdate = false;
                    if (contentLength == -1) {
                        System.out.println("content-length: unknown");
                    } else {
                        System.out.format("content-length: %d\n", contentLength);
                    }
                    start();//开始下载

                }

                if (contentLength != -1) {
                    int progress = (int) ((100 * (bytesRead + startPos)) / (contentLength + startPos));
                    System.out.format("%d%% done\n", progress);

                    loading(progress);

                }
            }

        }

        @Override
        public void writeTo(byte[] bytes, int off, int len) {
            try {
                downloadFile.write(bytes, off, len);
            } catch (IOException e) {
                e.printStackTrace();
                downloadFile.cancel();
                error(new LSHttpException(LSHttpException.ERROR.HTTP_ERROR, "文件下载失败"));
            }
        }

    };

使用到的库

com.squareup.okhttp3:okhttp:3.12.3

com.google.code.gson:gson:2.8.2

https内容借鉴

OKGO

本次介绍到此为止,谢谢各位大佬的观看,有写的不对的地方,有写的不对的地方希望各位大佬指正。如果觉得写的可以的话,去GitHub给个星呗。

更新日志

  • 2019.08.05 新增http stauts失败时对应status返回

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容