造輪子--擼一個(gè)網(wǎng)絡(luò)請(qǐng)求框架LSHttp

前言

為何自己動(dòng)手?jǐn)]了一個(gè)呢,不使用流行的Retrofit+RxJava + OkHttp 方式。首先是因?yàn)閷?duì)RxJava不了解,在平常項(xiàng)目中沒有使用過,其次就為了一個(gè)網(wǎng)絡(luò)框架引入RxJava RxAndroid 等,得不嘗試,增加apk體積。以上觀點(diǎn)僅代表個(gè)人觀點(diǎn),不喜勿噴。

介紹

LSHttp庫(kù),就僅僅是對(duì)OkHttp進(jìn)行了一些封裝,采用鏈?zhǔn)秸{(diào)用的方法,一行代碼實(shí)現(xiàn)網(wǎng)絡(luò)請(qǐng)求,支持綁定當(dāng)前Activity、Fragment,在銷毀時(shí)自動(dòng)取消請(qǐng)求,讓開發(fā)者使用起來(lái)更方便。

使用

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

支持以下操作

  • get請(qǐng)求
  • post表單、json
  • 上傳(單文件或多文件,支持進(jìn)度監(jiān)聽)
  • 下載(斷點(diǎn)下載)
  • 頁(yè)面銷毀時(shí)自動(dòng)取消請(qǐng)求
  • 自定義轉(zhuǎn)換器,默認(rèn)提供了StringCallBack,JsonCallBack

看到這里有些小伙伴就要說了,put、delete、head、options、trace、patch 這些請(qǐng)求為何不支持呢?這里分實(shí)話假話,實(shí)話就是水平有限我不會(huì),工作了幾年了沒有用過。假話就是,在實(shí)際開發(fā)中Get/Post兩種請(qǐng)求就可以滿足99%的場(chǎng)景了。當(dāng)然如果你的業(yè)務(wù)需求要用到的話,也可以繼承BaseRequest,實(shí)現(xiàn)generateRequest方法就可以了。

各位大佬注意了,接下來(lái)就要開始介紹使用方法了

配置 在Application里面:

LSHttp.init(this); //初始化,使用默認(rèn)配置

//配置自己的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) //添加多個(gè)headers
            .baseUrl("https://wanandroid.com/"); //baseurl

baseUrl說明:

如果配置了baseUrl,在request時(shí)可以直接傳入路徑,例如:user/login,如果某次請(qǐng)求的baseUrl不是當(dāng)前配置的,直接傳入完整的url就可以了,例如:https://www.33lc.com/article/UploadPic/2012-7/201272510182494484.jpg

如何做到Activity、Fragment銷毀,請(qǐng)求自動(dòng)取消

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

LSHttp.xxx(url).
    execute(); //不傳 Activity或者Fragment,在頁(yè)面銷毀時(shí)請(qǐng)求不會(huì)取消

CallBack介紹,打交道最多的,最終請(qǐng)求數(shù)據(jù)都會(huì)回到CallBack,默認(rèn)提供了兩種CallBack:

StringCallBack 請(qǐng)求返回字符串

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

            }

            @Override
            public void onFail(LSHttpException e) {
                              

            }
       });

JsonCallBack 返回實(shí)體類 內(nèi)部使用了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不能滿足你的使用,還可以自定義轉(zhuǎn)換器,需要三步:

1.定義接口,繼承ResponseCallBack

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

2.創(chuàng)建Class,實(shí)現(xiàn)接口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.請(qǐng)求

 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.如何知道,請(qǐng)求是onSuccess或onFail

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

     if (response.isSuccessful()) {
        //請(qǐng)求成功
        onConvertCallBack(response);
    } else {
        runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (callBack != null) {
                    callBack.onFail(new LSHttpException(LSHttpException.ERROR.HTTP_ERROR, "請(qǐng)求失敗,服務(wù)器開小差...", response.code()));
                }
            }
        });
    }
    removeLifecycle(obj, call);
}
  • response.isSuccessful() 判斷服務(wù)端返回的status是否在200~300,如果是,進(jìn)行onConvertCallBack,將結(jié)果轉(zhuǎn)化成對(duì)應(yīng)的數(shù)據(jù)。

  • 如果status沒有在200~300之間,就進(jìn)行onFail處理,將status拋給上層。

  • onConvertCallBack方法,進(jìn)行區(qū)分使用自定義的Convert還是默認(rèn)的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);
              }
          }
      }
    

如何發(fā)起一個(gè)請(qǐng)求呢?

LSHttp.xxx(url) //請(qǐng)求方法,get/post/json/download/multipart
      .xxx()//不同請(qǐng)求支持的方法
      .addHeader(key,value)//添加header
      .addHeaders(map)//添加多個(gè)header
      .tag(tag)//指定tag,用于取消
      .convert(IConvertResponse)//轉(zhuǎn)換器,使用方法上面有介紹
      .callback(ResponseCallBack)//請(qǐng)求回調(diào)
      .execute(null/Activity/Fragment);//開始執(zhí)行,傳入Activity和Fragment,就會(huì)在頁(yè)面銷毀時(shí)自動(dòng)取消請(qǐng)求

Get請(qǐng)求

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

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

                }
            }).execute();

Post請(qǐng)求

//表單請(qǐng)求  application/x-www-form-urlencoded
LSHttp.post("user/login")
            .addParams("username", "xxxx") //請(qǐng)求參數(shù)
            .addParams("password", "xxxxx")
            .callback(new StringCallBack() {
                @Override
                public void onSuccess(String string) {
                    Log.e("onSuccess", string);
                    mText.setText("請(qǐng)求成功:\n" + string);

                }

                @Override
                public void onFail(LSHttpException e) {
                    mText.setText("請(qǐng)求失敗:\n" + e.message);

                }
            }).execute();

//json請(qǐng)求   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("請(qǐng)求成功:\n" + string);

                }

                @Override
                public void onFail(LSHttpException e) {
                    mText.setText("請(qǐng)求失敗:\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("請(qǐng)求失敗:\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("請(qǐng)求成功:\n" + string);

                            }

                            @Override
                            public void onFail(LSHttpException e) {
                                mTxtResult.setText("請(qǐng)求失敗:\n" + e.message);

                            }
                    }).execute(UploadFragment.this);

上傳下載需要讀寫權(quán)限,推薦 LSPermissions

如何擴(kuò)展請(qǐng)求,例如put請(qǐng)求

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

以上使用方法已經(jīng)介紹完畢,接下來(lái)開始看下源碼

源碼

BaseRequest

定義了請(qǐng)求需要的公用參數(shù),以及請(qǐng)求邏輯,綁定Acitivy、Fragment生命周期

public abstract class BaseRequest<T extends BaseRequest> {
    protected OkHttpClient mClient;
    protected LSHttpActivityLifecycleCallBacks lifecycleCallBacks;
    protected String url; //請(qǐng)求url
    protected Map<String, String> headers = new HashMap<>(); //請(qǐng)求頭參數(shù)
    protected String tag;//請(qǐng)求tag
    protected ResponseCallBack callBack;//請(qǐng)求回調(diào)
    protected IConvertResponse convertResponse; //用于自定義轉(zhuǎn)換器

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

    public ResponseCallBack getCallBack() {
        return callBack;
    }

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

    //添加多個(gè)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;
    }

    //請(qǐng)求結(jié)果返回
    public T callback(ResponseCallBack callBack) {
        this.callBack = callBack;
        return (T) this;
    }

    //轉(zhuǎn)換器
    public T convert(IConvertResponse convertResponse) {
        this.convertResponse = convertResponse;
        return (T) this;
    }

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

    //構(gòu)建基礎(chǔ)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);

    //異步執(zhí)行網(wǎng)絡(luò)請(qǐng)求
    public void execute() {
        execute(null);
    }


    //異步執(zhí)行網(wǎng)絡(luò)請(qǐng)求 obj 支持Activity、Fragment
    public void execute(final Object obj) {
        checkUrl();

        OkHttpClient client = mClient;


        Call call = client.newCall(generateRequest(request()));
        //與當(dāng)前Acitivy、Fragment綁定關(guān)系
        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));
                        }
                    }
                });
                //請(qǐng)求結(jié)束,移除綁定關(guān)系
                removeLifecycle(obj, call);

            }

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

                if (response.code() == 200) {
                    //請(qǐng)求成功
                    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, "請(qǐng)求失敗,服務(wù)器開小差..." + response.code()));
                            }
                        }
                    });
                }
                //請(qǐng)求結(jié)束,移除綁定關(guān)系
                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;
            }
        }
    }

    //運(yùn)行到主線程
    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

請(qǐng)求結(jié)果轉(zhuǎn)換接口,可以根據(jù)自己需求,實(shí)現(xiàn)該接口,進(jìn)行定制化

public interface IConvertResponse {
/**
 * 轉(zhuǎn)換 
 * @param response
 */
void convert(Response response);

/**
 * 回調(diào)
 * @param callBack
 */
void setCallBack(ResponseCallBack callBack);
}

ResponseCallBack

和上面的 IConvertResponse 配合使用,只定義了失敗方法,成功方法根據(jù)轉(zhuǎn)換自己定義對(duì)應(yīng)的Success

public interface ResponseCallBack {

    void onFail(LSHttpException e);

}

LSHttpException,定義了些常見的錯(cuò)誤處理,在三種情況下會(huì)收到

1-請(qǐng)求失敗在如,SockteTimeOutException,等IO異常時(shí),即OkHttp返回onFailure。

2-請(qǐng)求成功,但是服務(wù)端返回http status 不在200~300之間,如404,500
3-請(qǐng)求成功,并且http status在200~300之間,但是convert時(shí)出錯(cuò)。

public class LSHttpException extends Exception {

    /**
    * 約定異常
    */
    public static class ERROR {
        /**
        * 未知錯(cuò)誤
        */
        public static final int UNKNOWN = 1000;
        /**
        * 連接超時(shí)
        */
        public static final int TIMEOUT_ERROR = 1001;
        /**
        * 空指針錯(cuò)誤
        */
        public static final int NULL_POINTER_EXCEPTION = 1002;

        /**
        * 證書出錯(cuò)
        */
        public static final int SSL_ERROR = 1003;

        /**
        * 類轉(zhuǎn)換錯(cuò)誤
        */
        public static final int CAST_ERROR = 1004;

        /**
        * 解析錯(cuò)誤
        */
        public static final int PARSE_ERROR = 1005;

        /**
        * 非法數(shù)據(jù)異常
        */
        public static final int ILLEGAL_STATE_ERROR = 1006;

        /**
        * 服務(wù)端異常 http code >= 200 && <300
        */
        public static final int HTTP_ERROR = 1007;


    }


    public final int code; //錯(cuò)誤Code
    public String message; //描述
    public int status; //當(dāng)code == 1007 時(shí) 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 = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
            return ex;
        } else if (e instanceof ConnectException) {
            ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
            return ex;
        } else if (e instanceof ConnectTimeoutException) {
            ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
            return ex;
        } else if (e instanceof UnknownHostException) {
            ex = new LSHttpException(e, ERROR.TIMEOUT_ERROR);
            ex.message = "網(wǎng)絡(luò)不可用,請(qǐng)稍后再試";
            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 = "證書驗(yàn)證失敗";
            return ex;
        } else if (e instanceof ClassCastException) {
            ex = new LSHttpException(e, ERROR.CAST_ERROR);
            ex.message = "類型轉(zhuǎn)換錯(cuò)誤";
            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 = "解析錯(cuò)誤";
            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 = "未知錯(cuò)誤";
            return ex;
        }
    }

}

請(qǐng)求如何綁定Activity、Fragment

這里主要使用了 ActivityLifecycleCallbacks 和 FragmentManager.FragmentLifecycleCallbacks ,監(jiān)聽頁(yè)面銷毀時(shí),將請(qǐng)求取消

LSHttpLifecycleCallBacks

/**
* 綁定生命周期基類、對(duì)外提供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銷毀時(shí)自動(dòng)取消
*/
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);

    }
}

下載

支持進(jìn)度監(jiān)聽,通過查資料,一般有兩種實(shí)現(xiàn)方式進(jìn)行進(jìn)度監(jiān)聽,一種是在請(qǐng)求成功后,在寫入file時(shí),監(jiān)聽的本地文件寫入進(jìn)度,還有一種就是通過網(wǎng)絡(luò)攔截器包裝ResponseBody,這種是監(jiān)聽網(wǎng)絡(luò)下載進(jìn)度,下載完畢后再寫入file。但是我在寫的時(shí)候發(fā)現(xiàn)了一個(gè)問題,那就是進(jìn)度卡在100%,然后等f(wàn)ile寫入完畢后,才能拿到文件。我在方案二的基礎(chǔ)上進(jìn)行了改造,實(shí)現(xiàn)了邊下邊存,當(dāng)下載完畢時(shí)文件也保存完畢了。

1.定義接口

interface ProgressListener {
    //下載進(jìn)度監(jiān)聽
    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();

關(guān)鍵代碼,在第二步

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

如果沒有下載完,不斷的將讀取的字節(jié)寫入到文件

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

通過RandomAccessFile 不斷的將字節(jié)寫入到文件

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

}

監(jiān)聽

 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, "文件下載失敗"));
            }
        }

    };

使用到的庫(kù)

com.squareup.okhttp3:okhttp:3.12.3

com.google.code.gson:gson:2.8.2

https內(nèi)容借鑒

OKGO

本次介紹到此為止,謝謝各位大佬的觀看,有寫的不對(duì)的地方,有寫的不對(duì)的地方希望各位大佬指正。如果覺得寫的可以的話,去GitHub給個(gè)星唄。

更新日志

  • 2019.08.05 新增http stauts失敗時(shí)對(duì)應(yīng)status返回

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容