Android學習之——自己搭建Http框架(2)——框架擴展

一、Json指定轉化成對象返回

       上篇文章主要講基礎的框架搭建起來了,這次需要做一些些的擴展,這裏Json轉化用到了google的Gson。

        上篇文章,我們直接返回了String的字符串,那麼如果是請求返回回來的是Json格式的,我們能否在數據返回的時候將數據轉化成需要的對象呢。答案當然是可以的。

        我們可以在UI線程中創建Callback的時候將預處理的對象放入進去,還是直接代碼描述比較清楚:

        1. 首先我們需要傳遞 實體類 的class 進去,該方法我們可以在抽象類 AbstractCallback 中定義:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public AbstractCallback<T> setReturnClass(Class<T> clz) {  
  2.     this.mReturnClass = clz;  
  3.     return this;  
  4. }  
  5. public AbstractCallback<T> setReturnType(Type type) {  
  6.     this.mReturnType = type;  
  7.     return this;  
  8. }  
        2.  使用上述方法,在ui線程代碼中,當使用的的時候調用方法如下, 注下面代碼中的 new TypeToken<Entity>(){}.getType() 爲GSON獲取Type的方法:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void requestJson() {  
  2.     Request request = new Request(UrlHelper.test_json_url, RequestMethod.GET);//UrlHelper.test_json_url是一個json地址  
  3.     request.setCallback(new JsonCallback<Entity>() {     // Entity 爲 json 要轉化的實體類 ,可以是 ArrayList<Entity>的形式等  
  4.         @Override  
  5.         public void onFilure(Exception result) {  
  6.         }  
  7.         @Override  
  8.         public void onSuccess(Entity result) {  
  9.             mTestResultLabel.setText(result.weatherinfo + "----");  
  10.         }  
  11.     }.setReturnType(new TypeToken<Entity>(){}.getType()));//.setReturnClass(Entity.class));  
  12.     request.execute();  
  13. }  
        3. JsonCallback的實現方式如下所示:其中,我們需要將繼承的類AbstractCallback改成AbstractCallback<T> 以及對應的接口也改成ICallback<T>,這裏就不具體列出修改泛型的代碼了
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public abstract class JsonCallback<T> extends AbstractCallback<T> {  
  2.     public static Gson gson = new Gson();  
  3.     @Override  
  4.     protected T bindData(String content) {  
  5.         Log.i("bindData", content);  
  6.         if (TextUtil.isValidate(path)) {  
  7.             content = IOUtilities.readFromFile(path);  
  8.         }  
  9.         if (mReturnClass != null) {  
  10.             return gson.fromJson(content, mReturnClass);  
  11.         } else if (mReturnType != null) {  
  12.             return gson.fromJson(content, mReturnType);  
  13.         }  
  14.         return null;  
  15.     }  
  16. }  
        至此,轉化成Json的對象已經處理完成。可能描述的不是太清楚,其實主要的步驟就是,跟上篇文章實現StringCallback.java一樣,在UI線程的request.setcallback中new一個匿名內部類將ICallback以及他的抽象類,抽象子類實現泛型,使之可以傳遞需要的實體類的 cass,或者 type 進去。其實這個Json的轉化並不是非常重要,在我閱讀以及使用 android-async-http 框架的時候,直接的做法是,直接將HTTP返回的值預處理成JSON然後再onSuccess的時候進行JSON的解析,效率也並不會有太大的影響。


二、下載進度更新 

        處理思路:

        在 AsyncTask 中doInBackground 裏有一個方法publishProgress ,通過 AbstractCallback 的裏的寫入文件的進度,將進度實時更新到 onProgressUpdate 從而將更新進度更新到 主線程的功能,然後對進度進行相應的處理。如更新進度條等。

        1. 首先添加一個監聽接口:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public interface IProgressListener {  
  2.     void onProgressUpdate(int curPos,int contentLength);  
  3. }  
        2. 我們可以通過監聽 寫入到文件的循環中來進行監聽,在AbstractCallback.java 中handle方法的寫入文件的代碼如下:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. while ((read = in.read(b)) != -1) {    
  2.     // TODO update progress    
  3.     fos.write(b, 0, read);    
  4. }    
            爲了節省篇幅,具體的代碼可以去上一篇文章閱讀。在這裏,我們可以通過當前寫入的進度和總長度進行比較寫入監聽的接口方法中,具體代碼如下:

            a. 修改 ICallback 接口:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. Object handle(HttpResponse response, IProgressListener mProgressListener);  
            b. 當然了同時要修改AbstractCallback 中的實現類
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2.     public Object handle(HttpResponse response, IProgressListener mProgressListener){.........}  
           c. 修改AbstractCallback 中的handle方法中寫入文件的那段代碼即上文代碼的 //TODO update progress 段,具體代碼如下:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. byte[] b = new byte[IO_BUFFER_SIZE];  
  2. int read;  
  3. long curPos = 0;  
  4. long length = entity.getContentLength();  
  5. while ((read = in.read(b)) != -1) {  
  6.     checkIfCanceled();  
  7.     if (mProgressListener != null) {  
  8.         curPos += read;  
  9.         //將當前進度和總進度返回到具體的實現層,  
  10.         //我們在 RequestTask 的 doInBackground 中去實現 IProgressLinstener 接口中的的該方法,將值傳到onProgressUpdate中  
  11.         mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024));  
  12.     }  
  13.     fos.write(b, 0, read);  
  14. }  

        3. 在RequestTask.java 中的doInBackground 中我們來實現上述內容:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. protected Object doInBackground(Object... params) {  
  3.     try {  
  4.         HttpResponse response = HttpClientUtil.excute(request);  
  5.         //response 解析代碼放到對應的類中,對應handle中的bindData方法  
  6.         Log.i("doInBackground", response.toString());  
  7.         if (request.mProgressListener != null) {  
  8.             return request.callback.handle(response, new IProgressListener() {  
  9.                 @Override  
  10.                 public void onProgressUpdate(int curPos, int contentLength) {  
  11.                     //這裏的參數類型是 AsyncTask<Object, Integer, Object>中的Integer決定的,在onProgressUpdate中可以得到這個值去更新UI主線程    
  12.                     publishProgress(curPos,contentLength);  
  13.                 }  
  14.             });  
  15.         }else {  
  16.             return request.callback.handle(response, null);  
  17.         }  
  18.     } catch (Exception e) {  
  19.         return e;  
  20.     }  
  21. }  
        4. 寫到這裏,突然忘記最重要的一點,我們需要在主線程中設置它的監聽才能真正實現監聽,在Request.java中我們加入如下方法,使主線程可以調用:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public IProgressListener mProgressListener;  
  2. public void setProgressListener(IProgressListener iProgressListener) {  
  3.     this.mProgressListener = iProgressListener;  
  4. }  
        5. 繼續上面第3點的話題,在RequestTask.java 中我們實現 AsyncTask 的onProgressUpdate 方法, 將在doInBackground 中調用的 publishProgress(..., ...) 方法得到的值在該方法中中傳給IProgressListener的onProgressUpdate。不知道我這樣的描述是否準確。表達不是很理想。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. protected void onProgressUpdate(Integer... values) {  
  3.     super.onProgressUpdate(values);  
  4.     if (request.mProgressListener != null) {  
  5.         request.mProgressListener.onProgressUpdate(values[0], values[1]);  
  6.     }  
  7. }  
        6. 最後,我們在主線程中設置setProgressListener 並實現匿名內部類 IProgressListener ,通過重寫 onProgressUpdate 方法得到當前進度值和總進度值,根據該進度值,進行實時的進度條更新等操作,主線程調用方法如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void requestString() {  
  2.     //設置保存路徑  
  3.     String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mrfu_http.txt";  
  4.     Request request = new Request(UrlHelper.test_string_url, RequestMethod.GET);  
  5.     request.setCallback(new StringCallback() {  
  6.         @Override  
  7.         public void onSuccess(String result) {  
  8.             mTestResultLabel.setText((String)result);  
  9.         }  
  10.         @Override  
  11.         public void onFilure(Exception result) {  
  12.             result.printStackTrace();  
  13.         }  
  14.     }.setPath(path));  
  15.     request.setProgressListener(new IProgressListener() {  
  16.         //在這裏實現 IProgressListener 的onProgressUpdate 將子線程中得到的進度值獲取到。  
  17.         //這裏,我們只是顯示到LogCat中,實際我們可以根據需要實現進度條更新等操作  
  18.         @Override  
  19.         public void onProgressUpdate(int curPos, int contentLength) {  
  20.             System.err.println("curPost:"+curPos +",contentLength:" + contentLength);  
  21.         }  
  22.     });  
  23.     request.execute();  
  24. }  


三、 如何隨時取消 Request 請求

        1. 我們需要取消 Request 請求,那麼,在代碼中,我們在哪些地方可以取消請求呢?我們先來分析框架的基本內容:

            a. 在主線程我們執行 request.execute(); 在 Request.java 中開啓了一個 RequestTask,它繼承自 AsyncTask

            b. doInBackground 是異步執行的,在這個子線程中 我們執行 HttpResponse response = HttpClientUtil.excute(request); 代碼段 正式調用HTTP的get或者set方法,得到類型爲 HttpResponse 的返回值 ,然後執行 AbstractCallback 的 handle 方法

            c. 在AbstractCallback 的 public T handle(HttpResponse response, IProgressListener mProgressListener) 方法中我們處理返回回來的 HttpResponse 的內容,如果返回的code是200,則成功,那麼我們根據是否設置了下載路徑選擇是否下載,或者是直接返回數值。

            d.  根據主線程設置的 StringCallback 或者JsonCallback 或者其他解析類型,通過調用 bindData(....); 去具體的解析內容,並返回到 UI 線程。

        2. 設計思路:

            在主線程,我們接到了取消請求的需求,通過 調用 Requset 的 cancel() 方法,去調用 callback 中的 cancel(); 方法,將其AbstractCallback 中的取消標誌設置爲true,如果爲true 我們就在checkIfCanceled()方法中拋出異常,結束該次請求。我們可以將 checkIfCanceled() 方法放在handle(..., ...)剛開始的時候,放在while ((read = in.read(b)) != -1){ fos.write(b, 0, read); } 寫入文件的時候 以及返回數據放入不同callback中進行處理的時候。下面我們會給出具體的實現方法,還有http請求的 get 和 post 的時候。

        3. 實現代碼:

            a. ICallback 接口中定義如下方法:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void checkIfCanceled() throws AppException;  
  2. void cancel();  

           b. 主線程調用cancel請求:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void testCancel(){  
  2.     String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "stay_training_http.txt";  
  3.     final Request request = new Request("http://h.hiphotos.baidu.com/image/w%3D2048/sign=432fca00369b033b2c88fbda21f636d3/a2cc7cd98d1001e9ae04c30bba0e7bec54e797fe.jpg",RequestMethod.GET);  
  4.     request.setCallback(new PathCallback() {  
  5.           
  6.         @Override  
  7.         public void onSuccess(String result) {  
  8.         }  
  9.   
  10.         @Override  
  11.         public void onFilure(Exception result) {  
  12.             result.printStackTrace();  
  13.         }  
  14.     }.setPath(path));  
  15.     request.setProgressListener(new IProgressListener() {  
  16.         @Override  
  17.         public void onProgressUpdate(int curPos, int contentLength) {  
  18.             System.err.println("curPost:"+curPos +",contentLength:" + contentLength);  
  19.             if (curPos > 8) {  
  20.                 request.cancel();//當下載進度爲8的時候我們取消了該請求  
  21.                 }  
  22.             }  
  23.         });  
  24.         request.execute();  
  25.     }  
  26. }  

            c. 在 Request 中實現 cancel() 方法,方法內調用 callback 的 cancel() 方法:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public ICallback callback;  
  2. public  void cancel(){  
  3.     if (callback != null) {  
  4.         this.callback.cancel();           
  5.     }  
  6. }  

            d. 在 AbstractCallback  中實現ICallback 裏定義的 cancel() 的方法,如果主線程調用了cancel方法,我們就將標誌設置爲 true

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. protected boolean isCancelled;  
  2. @Override  
  3. public void cancel() {  
  4.     isCancelled = true;  
  5. }  

            e. 實現 ICallback 中的 checkIfCanceled() 方法,如果 isCancelled 爲 true 則拋出異常,即可中斷請求操作,代碼中出現了 AppException 自定義異常類 這個我們後面再講

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. public void checkIfCanceled() throws AppException {  
  3.     if (isCancelled) {  
  4.         throw new AppException(EnumException.CancelException, "request has been cancelled");  
  5.     }  
  6. }  
            f. 在 handle 開開始放入 checkIfCanceled() 的判斷,在將下載的文件寫入到文件的時候我們也做判斷,還有在進行數據處理的時候也進行判斷

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. public T handle(HttpResponse response, IProgressListener mProgressListener) throws AppException{  
  3.     // file, json, xml, image, string  
  4.     checkIfCanceled();//在這裏我們調用檢查是否取消請求的方法  
  5.     int statusCode = -1;  
  6.     InputStream in = null;  
  7.     try {  
  8.         HttpEntity entity = response.getEntity();  
  9.         statusCode = response.getStatusLine().getStatusCode();  
  10.         switch (statusCode) {  
  11.         case HttpStatus.SC_OK:  
  12.             if (TextUtil.isValidate(path)) {  
  13.                 //將服務器返回的數據寫入到文件當中  
  14.                 FileOutputStream fos = new FileOutputStream(path);  
  15.                 if (entity.getContentEncoding() != null) {  
  16.                     String encoding = entity.getContentEncoding().getValue();  
  17.                     if (encoding != null && "gzip".equalsIgnoreCase(encoding)) {  
  18.                         in = new GZIPInputStream(entity.getContent());  
  19.                     } if (encoding != null && "deflate".equalsIgnoreCase(encoding)) {  
  20.                         in = new InflaterInputStream(entity.getContent());  
  21.                     }  
  22.                 } else {  
  23.                     in = entity.getContent();  
  24.                 }  
  25.                 byte[] b = new byte[IO_BUFFER_SIZE];  
  26.                 int read;  
  27.                 long curPos = 0;  
  28.                 long length = entity.getContentLength();  
  29.                 while ((read = in.read(b)) != -1) {  
  30.                     checkIfCanceled(); //<span style="font-family: Arial, Helvetica, sans-serif;">在這裏我們調用檢查是否取消請求的方法</span>  
  31.                     if (mProgressListener != null) {  
  32.                         curPos += read;  
  33.                         //將當前進度和總進度返回到具體的實現層,  
  34.                         //我們在 RequestTask 的 doInBackground 中去實現 IProgressLinstener 接口中的的該方法,將值傳到onProgressUpdate中  
  35.                         mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024));  
  36.                     }  
  37.                     fos.write(b, 0, read);  
  38.                 }  
  39.                 fos.flush();  
  40.                 fos.close();  
  41.                 in.close();  
  42.                 //寫入文件之後,再從文件當中將數據讀取出來,直接返回對象  
  43.                 return bindData(path);  
  44.             } else {  
  45.                 // 需要返回的是對象,而不是數據流,所以需要去解析服務器返回的數據  
  46.                 // 對應StringCallback 中的return content;  
  47.                 //2. 調用binData  
  48.                 return bindData(EntityUtils.toString(entity));  
  49.             }  
  50.         default:  
  51.             break;  
  52.         }  
  53.         return null;  
  54.     } catch (ParseException e) {  
  55.         throw new AppException(EnumException.ParseException, e.getMessage());  
  56.     } catch (IOException e) {  
  57.         throw new AppException(EnumException.IOException, e.getMessage());  
  58.     }  
  59. }  
  60. /** 
  61.  * 數據放入到不同的Callback中處理,StringCallback 等方法中實現了該方法 
  62.  * @throws AppException  
  63.  */  
  64. protected T bindData(String content) throws AppException{  
  65.     checkIfCanceled();//在這裏我們檢查是否取消請求的方法  
  66.     return null;  
  67. }  

           g. 我們在 RequestTask 中重寫 onCancelled  判斷 是否有做了 task.cancel(true); 的操作,當然,我們並沒有實現該操作,那是因爲,我們需要不管Request 的請求結果如果,我都需要返回到主線程,如果我這個時候取消掉了 AsyncTask ,那麼AsyncTask 就永遠不繼續執行了,也就無法回調回來了。

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. @Override  
  2. protected void onCancelled() {  
  3.     super.onCancelled();  
  4.     if (request.callback != null) {  
  5.         request.callback.cancel();            
  6.     }  
  7. }  
           h. HttpClientUtil.java 中的 post 和 get 代碼中加入如下代碼,具體內容請看註釋:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private static HttpResponse get(Request request) throws AppException {  
  2.     try {  
  3.         //如果在代碼已經執行到這裏的時候,AbstractCallback中的isCancelled被置爲了 true  
  4.         //這時我們就要再一次進行檢查是否取消。 post方法同理,不再贅述  
  5.         if (request.callback != null) {  
  6.             request.callback.checkIfCanceled();               
  7.         }  
  8.         HttpClient client = new DefaultHttpClient();  
  9.         HttpGet get = new HttpGet(request.url);  
  10.         addHeader(get, request.headers);  
  11.         //返回的結果放到上一層進行處理  
  12.         HttpResponse response = client.execute(get);  
  13.         return response;  
  14.     } catch (ClientProtocolException e) {  
  15.         throw new AppException(EnumException.ClientProtocolException, e.getMessage());  
  16.     } catch (IOException e) {  
  17.         throw new AppException(EnumException.IOException, e.getMessage());  
  18.     }  
  19. }  
發佈了7 篇原創文章 · 獲贊 0 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章