使用LiteHttp的另一種姿勢

國際慣例 ,介紹!

LiteHttp 是一款簡單、靈活的 HTTP 框架庫,它在請求和響應層面做到了「全自動」構建和解析。
LiteHttp 只需要一行代碼即可完美實現網絡連接,它全面支持 GET, POST, PUT, DELETE, HEAD, TRACE, OPTIONS 和 PATCH 八種基本類型。 LiteHttp 能將 Java Model 轉化為請求參數,也能將響應的 json 語句智能轉化為 JavaModel ,這種全自動解析策略將節省你大量的構建請求、解析響應的時間。 并且,你能自己繼承重新實現 Dataparser 這個抽象類并設置給 Request,來將原始的 InputStream 轉化為任何你想要的東西。

前言

好吧,看到今天liter更新了LiteHttp我也耐不住寂寞也出來寫一篇我在自己項目中使用的LiteHttp。

Android網絡開發我覺得是一件很枯燥的事情,無非就是填充參數->發送請求->等待數據->處理數據->請求結束,的確很枯燥,但又是App中不可或缺的一部分,所以我也一直在尋求最方便和最懶的方式去實現數據請求。之前有看到一篇文章RxJava 與 Retrofit 結合的最佳實踐,作者結合了RxJava和Retrofit的特性,將懶進行到底。

本文中我們采用MVP的設計模式,不了解的同學可以到Android MVP 詳解(上)了解

如果你還不了解LiteHttp,那么你可以去這個Github去看一看,當然,你也可以去Lite Your Android查看Liter所有的開源項目。Liter有很多關于LiteHttp使用的說明,不,是說明書。

接下來進入正文,會從下面幾個角度讓你變懶。歡迎大家拍磚。

  1. HttpListener和loading的結合
  2. 如何處理header和session
  3. Activity銷毀后任務的取消

HttpListener和loading的結合

首先是準備工作,先來看看Presenter

public interface IBasePresenter {

      /** 
       * 當Activity onCreate時調用 
       */
      void onCreate();

      /** 
       * 當Activity onDestory時調用 
       */
      void onDestroy();

      /** 
       * 當前Activity是否destroy
       */
      boolean isDestroy();

      void showWaitDialog(String message, WaitCancelListener listener);

      void dismiss();

      void showToast(String msg);

      /** 
       * 當請求成功時候調用
       */
      void onSuccess(Response response);

      void onNetWorkError();
    
      /** 
       * 將HttpRichParamModel放入線程中執行
       */
      <T> void executeSync(HttpRichParamModel<T> model);

}

WaitCancelListener監聽loading

public interface WaitCancelListener {    
      void onDialogCancel();
}

BaseModel,繼承liteHttp中的HttpRichParamModel

  public abstract class BaseModel<T> extends HttpRichParamModel<T> {    
      public HttpRichParamModel<T> setMyHttpListener(HttpListener<T> listener) {        
            setHttpListener(listener);        
            return this;    
      }
}

注意:<T> void executeSync(HttpRichParamModel<T> model);必須使用BaseModel<T>的子類,否則會出現轉型錯誤,

然后是MyHttpListener

public class MyHttpListener<Data> extends HttpListener<Data> implements LoadingDialog.WaitCancelListener {

      private IBasePresenter presenter;

      private String title = "請稍候";

      private AbstractRequest<Data> request;

      //任務開始時是否顯示等待框
      private boolean isShowWait = true;

      //是否自動顯示error為非0時的信息提示
      private boolean isApiShowToast = true;

      //任務結束后是否自動關閉等待框
      private boolean isAutoDismiss = true;

      public MyHttpListener(IBasePresenter presenter) {    
            this.presenter = presenter;
      }

      @Override
      public void onStart(AbstractRequest<Data> request) {
            super.onStart(request);
            this.request = request;
            if (isShowWait) presenter.showWaitDialog(title,this);
      }

      @Override
      public void onEnd(Response<Data> response) {
            super.onEnd(response);
            dismissWait();
      }

      public void dismissWait() {
            if (isShowWait && isAutoDismiss) presenter.dismissWaitDialog();                                                             
      }

      @Override
      public void onDialogCancel() {
            if (request != null) {
                  request.cancel();
            }
      }

      /**
       * 成功,但是不保證用戶數據正確性。
       * 這里自動處理解析后的數據,不同的code調用不同的方法
       */
      @Override
      public void onSuccess(Data s, Response<Data> response) {
            super.onSuccess(s, response);
            if (presenter.isDestroy()) return;
            if (s instanceof BaseEntity) {
                  BaseEntity ss = (BaseEntity) s;
                  if (ss.getCode() == Constant.ERROR.SUCCESS) {
                        onSuccessOk(s, response);
                  } else {
                        onSuccessNoZero(ss.getMessage(),response);
                  }
          }
            presenter.onSuccess(response);
      }
      /**
       * 自己需要實現的類
       */
      public void onSuccessOk(Data s, Response<Data> response){}

      public void onSuccessNoZero(String reason,Response<Data> response){
            showToast(reason);
      }

      public void showToast(String message) {
            if (isApiShowToast) presenter.showBigErrorToast(message);
      }

      @Override
      public void onFailure(HttpException e, Response<Data> response) {
            super.onFailure(e, response);
            if (e instanceof HttpNetException) {
                  showToast(((HttpNetException) e).getExceptionType().chiReason);
                  onNetWorkError();
            } else if (e instanceof HttpClientException) {
                  showToast(((HttpClientException) e).getExceptionType().chiReason);
                  dismissWait();
            } else if (e instanceof HttpServerException) {
                  return;
            } else {
                  onUnKnowError(e);
            }
            dismissWait();
      }

      public void onUnKnowError(HttpException e) {
            e.printStackTrace();
            presenter.showToast(e.getMessage() == null ? "" : e.getMessage());
      }

      public void onNetWorkError() {
            presenter.onNetWorkError();
      }
}

注意:

  1. 自動處理的前提就是數據的格式必須得統一,例如下面的json,如果你是其他類型的返回數據,請自行ThinkInJava。
  2. MyHttpListener中對界面的操作在presenter中實現,并且在這里實現dialog被手動cancel的回調。
{
      "message":"處理成功",
      "code": 0
        ...
}

現在我們來看看MyHttpListener是如何工作的,

  1. onStart -> 根據**isShowWait **決定是否自動顯示dialog,
  2. onSuccess -> 分發調用并通知presenter,你要問我這有什么用,請自行腦補。
  3. onSuccessOk -> 成功收到數據
  4. onSuccessNoZero -> 根據isApiShowToast是否顯示錯誤提示
  5. onEnd -> 請求結束,消失dialog,
  6. onFailure() -> 請求失敗,因為liteHttp提供了異常類,你可以單獨處理,當然也可以統一處理。

最后是void onDialogCancel(),當加載數據時間較長時,可以隨時取消當前請求。這樣就成了一個非常簡單的自動處理dialog的類,省去了許多代碼。另外,對上述提到的幾個boolean控制類可以這樣實現,通過和Builder一樣的方式一路點下去,是不是很清爽啊!!!

      public MyHttpListener<Data> setTitle(String title) {
            this.title = title;
            return this;
      }

      public MyHttpListener<Data> disableWait() {
            this.isShowWait = false;
            return this;
      }

      public MyHttpListener<Data> disableToast() {
            this.isApiShowToast = false;
            return this;
      }

      public MyHttpListener<Data> disableAutoDismiss() {
            this.isAutoDismiss = false;
            return this;
      }

      public MyHttpListener<Data> setShowWait(boolean showWait) {
            isShowWait = showWait;
            return this;
      }

      public MyHttpListener<Data> setApiShowToast(boolean apiShowToast) {
            isApiShowToast = apiShowToast;
            return this;
      }

最后是發起請求

public class LoginModel extends BaseModel<BaseEntity> {    
      public String userName;    
      public String pwd;    
      public LoginModel(String userName, String pwd) {        
            this.userName = userName;        
            this.pwd = pwd;    
      }
}

      //在合適的地方調用即可,詳見github
      executeSync(new LoginModel(userName,pwd).setMyHttpListener(new MyHttpListener<BaseEntity>(this) {    
      @Override    
      public void onSuccess(BaseEntity s, Response<BaseEntity> response) {        
            super.onSuccess(s, response);        
            //TODO SUCCESS
      }}.disableToast()));

別忘記加權限

<uses-permission android:name="android.permission.INTERNET"/>

這一部分就基本上完了,需要大家注意的是,由于筆者采用的是mvp的設計模式,因此MyHttpListener中持有了presenter的對應,這不是必須的,你也可以在MyHttpListener中通過其他方式實現。
放上源碼LiteHttpDemo TAG 選擇setup1

如何處理header和session(token)

其實在App的接口訪問中使用session的并不多見,但是還是會由于種種原因,某些App會使用到session。然而這給App帶來了一些麻煩,尤其是你在考慮到有以下情況時,服務器crsf保護和session過期需要自動登錄兩重校驗時你就頭大了,實現他們并不需要多么高超的技術,問題在于如果你有強迫癥,要使用優雅而快速的方式去實現就是一件很虐心的事情。

因為在請求數據時剛好你的session過期了,就會打斷用戶當前的操作,對用戶體驗是及其糟糕的,在這種情況下如何連續而又優雅的解決這個問題就是我們接下來要討論的。當然,我說了,這只是實現自動處理session的一種姿勢而已。

首先來說一說思路,當本地保存的session過期后繼續訪問服務器,一般會拋出服務器內部錯誤,沒錯就是萬惡是500,這就是我們的切入點。沒當發起一個http請求時,我們將這個請求對象保存在隊列中,如果這個請求成功了,就從隊列中取出來,如果請求失敗并且拋出500錯誤,我們就讓隊列保持不變,另外生成一個更新session的請求,當更新session成功后在從隊列中取出之前的請求繼續執行,因為我們保存了它的對象,里面的任何設置并不會有任何影響。

好了,開始貼代碼了。

因為我們要統一管理所有的請求,所有我們得先有個管理類,我們就叫
MyHttp吧,代碼太多,只貼關鍵部分。

//初始化,MyHttp需要繼承GlobalHttpListenter
public MyHttp() {    
      HttpConfig config = new HttpConfig(null);    
      //設置litehttp的globalHttpListener,方便監聽token過期。           
      config.setGlobalHttpListener(this);    
      mLiteHttp =  LiteHttp.newApacheHttpClient(config);    
      mTask = new LinkedList<>();
}

@Override
public void onSuccess(Object o, Response<?> response) {    
try { 
       //檢查一下鏈接,如果是更新token的鏈接接繼續之前的任務        
      String url = response.getRequest().getFullUri();        
      String fullUrl = TOKEN_URL;        
      if (TextUtils.equals(url, fullUrl)) {            
            //TODO GET TOKEN SUCCESS            
            if (o instanceof TokenEntity) {                
                  Log.i(TAG,"token獲取成功:"+((TokenEntity) o).getToken());           
             }                  
            toContinueTask();        
      } else {           
             //TODO        
      }    
      } catch (HttpClientException e) {        
            e.printStackTrace();    
      }
}

/** 
* 繼續上次一次任務
 */
public void toContinueTask() {    
      if (mTask.isEmpty()) return;    
      AbstractRequest request = mTask.poll();    
      executeSync(request);    
      Log.i(TAG,"繼續任務:" + request.getUri());
}

/**     
* 因為難得搞500錯誤,就用ClientException代替了,     
* BaseEntity中的code為int,使用Integer.parseInt(code)隨機產生異常
* @param e     
* @param response     
*/    
@Override    
public void onFailure(HttpException e, Response<?> response) {       
       try {           
      //檢查一下鏈接,如果是更新token的鏈接就不用處理了,直接找后臺吧。             
      String url = response.getRequest().getFullUri();            
      String fullUrl = TOKEN_URL;           
      if (TextUtils.equals(url, fullUrl)) return;       
      } catch (HttpClientException e1) {            
            e.printStackTrace();        
      }       
       if (e instanceof HttpClientException) {           
             if (((HttpClientException) e).getExceptionType() == ClientException.SomeOtherException) {                
doUpdateSession();       
      //將失敗的任務添加到隊列中        
                  mTask.offer(response.getRequest());           
       }       
 }
//        if (e instanceof HttpServerException) {
//            if (((HttpServerException) e).getExceptionType() == ServerException.ServerInnerError) {
//                doUpdateSession();
//                mTask.offer(response.getRequest());
//            }
//        }    
}

public void doUpdateSession() {    
      Log.i(TAG,"開始獲取token");    
      JsonRequest<TokenEntity> request = new JsonRequest<>(TOKEN_URL,TokenEntity.class);    
      request.setMethod(HttpMethods.Get);    
      executeSync(request);
}

public <T extends AbstractRequest> void executeSync(T request) {        
      mLiteHttp.executeAsync(request);
}
public <T> AbstractRequest executeSync(HttpRichParamModel<T> model) {    
      return mLiteHttp.executeAsync(model);
}

代碼中主要看幾個地方,一是onFailure,在失敗時候如果異常(因為無法模擬500錯誤,顧在解析json的通過mock隨機拋出httpClientException來當作是500錯誤,生成環境請自行腦補)是我們預期的錯誤就會進行更新token的操作,當token更新完成以后在繼續之前的任務,下面我以登錄為例,通過log來看具體的流程。

登錄Log日志.png

Myhttp會一直去更新token直到成功為止,當然你也可以自己設定更新次數,避免某些情況下的無限更新。

現在,自動更新token已經ok了,貌似已經可以工作了,但是還有一個小小問題就是,因為中間有更新token的請求,在更新操作之間的請求實際上已經結束了,dialog也已經dismiss,在此發起請求又會重新調起dialog,看起來有點小小的不爽,于是我們可以這樣

在MyHTTpListener中增加private boolean isInternalError
于是MyHTTpListener變成了這樣。

@Override    
public void onFailure(HttpException e, Response<Data> response) {        
      super.onFailure(e, response);       
       if (e instanceof HttpNetException) {            
            showToast(((HttpNetException) e).getExceptionType().chiReason);            
            onNetWorkError();        
      } else if (e instanceof HttpClientException) {        
            //標記錯誤,避免dialog自動dimiss    
            isInternalError = true;            
            Log.i(MyHttp.TAG,"請求token過期:"+ "service internal error");
//            showToast(((HttpClientException) e).getExceptionType().chiReason);
//            dismissWait();        
      } else if (e instanceof HttpServerException) {            
            return;        
      } else {            
            onUnKnowError(e);        
      }        
      dismissWait();   
 }

@Override
public void onSuccess(Data s, Response<Data> response) {        
      super.onSuccess(s, response);    
      isInternalError = false;    
      if (presenter.isDestroy()) return;    
      if (s instanceof BaseEntity) {        
            BaseEntity ss = (BaseEntity) s;        
      } if (ss.getCode() == 0) {            
            onSuccessOk(s, response);        
      } else {            
            onSuccessNoZero(ss.getMessage(),response);       
      }    
      presenter.onSuccess(response);
}

//dismiss dialog
public void dismissWait() {    
      if (isShowWait && isAutoDismiss && !isInternalError) presenter.dismiss();
}

注意isInternalError的變化

Activity銷毀后任務的取消

Activity中同時進行多個任務是常有的事情,上面我們有一個boolean isDestroy();的方法判斷當前的activity是否被銷毀 ,但問題是,這個時候任務其實是一件請求完畢,如果activity已經銷毀,在返回數據就沒有任何意義,除非你真的想那么做。

所以,要在activity銷毀的時候將正在運行和即將要允許的任務取消掉,我們可以這樣做。

前面我們提到有這個方法

public <T> AbstractRequest executeSync(HttpRichParamModel<T> model) {    
      return mLiteHttp.executeAsync(model);
}

實現IBasePresenter中的方法

/** * 獲取網絡數據 
* 
* @param model 請求 
*/
      public <T> void executeSync(HttpRichParamModel<T> model) {    
            mTask.add(http.executeSync(model));
      }

      @Override
      public void onSuccess(Response response) {    
            if (mIBaseView != null) {        
                  mIBaseView.onSuccess();    
            }    
            mTask.remove(response.getRequest());
      }

      @Overridepublic void onDestroy() {    
      this.isOnCreate = false;    
      if (mTask == null) return;
      for (AbstractRequest item : mTask) {    
            item.cancel();
      }
      mTask.clear();
}

在presenter中新增一個隊列,成功后移出隊列,onDestory后取消所有任務。

如果你正是用LiteHttp,以上1和3將會很適合你,如果你有遇到session或token的困擾,2將會給你很好的解答。

好了,歡迎拍磚。
奉上GitHub

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,269評論 25 708
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,923評論 18 139
  • 智能對于數據的搜集與歸納能力是我們人類大腦所不能企及的。 這是不是就意味著一切和數據相關的工作都將會被智能替代?是...
    楊麗影閱讀 216評論 0 0
  • 今天早上我們一家都睡過頭了,一睜眼六點三十分了,趕緊讓女兒起床。女兒也是一激靈,雖然著急,也不敢埋冤,嘴里不斷...
    崔嘉諾閱讀 125評論 0 0