編輯:關於Android編程
· 本文主要講解的是Json指定轉化成對象返回,下載進度更新,隨時取消Request請求
一、Json指定轉化成對象返回
上篇文章主要講基礎的框架搭建起來了,這次需要做一些些的擴展,這裡Json轉化用到了google的Gson。
上篇文章,我們直接返回了String的字符串,那麼如果是請求返回回來的是Json格式的,我們能否在數據返回的時候將數據轉化成需要的對象呢。答案當然是可以的。
我們可以在UI線程中創建Callback的時候將預處理的對象放入進去,還是直接代碼描述比較清楚:
1. 首先我們需要傳遞 實體類 的class 進去,該方法我們可以在抽象類 AbstractCallback 中定義:
public AbstractCallback2. 使用上述方法,在ui線程代碼中,當使用的的時候調用方法如下, 注下面代碼中的 new TypeTokensetReturnClass(Class clz) { this.mReturnClass = clz; return this; } public AbstractCallback setReturnType(Type type) { this.mReturnType = type; return this; }
private void requestJson() { Request request = new Request(UrlHelper.test_json_url, RequestMethod.GET);//UrlHelper.test_json_url是一個json地址 request.setCallback(new JsonCallback3. JsonCallback的實現方式如下所示:其中,我們需要將繼承的類AbstractCallback改成AbstractCallback() { // Entity 為 json 要轉化的實體類 ,可以是 ArrayList 的形式等 @Override public void onFilure(Exception result) { } @Override public void onSuccess(Entity result) { mTestResultLabel.setText(result.weatherinfo + "----"); } }.setReturnType(new TypeToken (){}.getType()));//.setReturnClass(Entity.class)); request.execute(); }
public abstract class JsonCallback至此,轉化成Json的對象已經處理完成。可能描述的不是太清楚,其實主要的步驟就是,跟上篇文章實現StringCallback.java一樣,在UI線程的request.setcallback中new一個匿名內部類將ICallback以及他的抽象類,抽象子類實現泛型,使之可以傳遞需要的實體類的 cass,或者 type 進去。其實這個Json的轉化並不是非常重要,在我閱讀以及使用 android-async-http 框架的時候,直接的做法是,直接將HTTP返回的值預處理成JSON然後再onSuccess的時候進行JSON的解析,效率也並不會有太大的影響。extends AbstractCallback { public static Gson gson = new Gson(); @Override protected T bindData(String content) { Log.i("bindData", content); if (TextUtil.isValidate(path)) { content = IOUtilities.readFromFile(path); } if (mReturnClass != null) { return gson.fromJson(content, mReturnClass); } else if (mReturnType != null) { return gson.fromJson(content, mReturnType); } return null; } }
二、下載進度更新
處理思路:
在 AsyncTask 中doInBackground 裡有一個方法publishProgress ,通過 AbstractCallback 的裡的寫入文件的進度,將進度實時更新到 onProgressUpdate 從而將更新進度更新到 主線程的功能,然後對進度進行相應的處理。如更新進度條等。
1. 首先添加一個監聽接口:
public interface IProgressListener { void onProgressUpdate(int curPos,int contentLength); }2. 我們可以通過監聽 寫入到文件的循環中來進行監聽,在AbstractCallback.java 中handle方法的寫入文件的代碼如下:
while ((read = in.read(b)) != -1) { // TODO update progress fos.write(b, 0, read); }為了節省篇幅,具體的代碼可以去上一篇文章閱讀。在這裡,我們可以通過當前寫入的進度和總長度進行比較寫入監聽的接口方法中,具體代碼如下:
a. 修改 ICallback 接口:
Object handle(HttpResponse response, IProgressListener mProgressListener);b. 當然了同時要修改AbstractCallback 中的實現類
@Override public Object handle(HttpResponse response, IProgressListener mProgressListener){.........}c. 修改AbstractCallback 中的handle方法中寫入文件的那段代碼即上文代碼的 //TODO update progress 段,具體代碼如下:
byte[] b = new byte[IO_BUFFER_SIZE]; int read; long curPos = 0; long length = entity.getContentLength(); while ((read = in.read(b)) != -1) { checkIfCanceled(); if (mProgressListener != null) { curPos += read; //將當前進度和總進度返回到具體的實現層, //我們在 RequestTask 的 doInBackground 中去實現 IProgressLinstener 接口中的的該方法,將值傳到onProgressUpdate中 mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024)); } fos.write(b, 0, read); }
3. 在RequestTask.java 中的doInBackground 中我們來實現上述內容:
@Override protected Object doInBackground(Object... params) { try { HttpResponse response = HttpClientUtil.excute(request); //response 解析代碼放到對應的類中,對應handle中的bindData方法 Log.i("doInBackground", response.toString()); if (request.mProgressListener != null) { return request.callback.handle(response, new IProgressListener() { @Override public void onProgressUpdate(int curPos, int contentLength) { //這裡的參數類型是 AsyncTask4. 寫到這裡,突然忘記最重要的一點,我們需要在主線程中設置它的監聽才能真正實現監聽,在Request.java中我們加入如下方法,使主線程可以調用:
public IProgressListener mProgressListener; public void setProgressListener(IProgressListener iProgressListener) { this.mProgressListener = iProgressListener; }5. 繼續上面第3點的話題,在RequestTask.java 中我們實現 AsyncTask 的onProgressUpdate 方法, 將在doInBackground 中調用的 publishProgress(..., ...) 方法得到的值在該方法中中傳給IProgressListener的onProgressUpdate。不知道我這樣的描述是否准確。表達不是很理想。
@Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); if (request.mProgressListener != null) { request.mProgressListener.onProgressUpdate(values[0], values[1]); } }6. 最後,我們在主線程中設置setProgressListener 並實現匿名內部類 IProgressListener ,通過重寫 onProgressUpdate 方法得到當前進度值和總進度值,根據該進度值,進行實時的進度條更新等操作,主線程調用方法如下:
private void requestString() { //設置保存路徑 String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mrfu_http.txt"; Request request = new Request(UrlHelper.test_string_url, RequestMethod.GET); request.setCallback(new StringCallback() { @Override public void onSuccess(String result) { mTestResultLabel.setText((String)result); } @Override public void onFilure(Exception result) { result.printStackTrace(); } }.setPath(path)); request.setProgressListener(new IProgressListener() { //在這裡實現 IProgressListener 的onProgressUpdate 將子線程中得到的進度值獲取到。 //這裡,我們只是顯示到LogCat中,實際我們可以根據需要實現進度條更新等操作 @Override public void onProgressUpdate(int curPos, int contentLength) { System.err.println("curPost:"+curPos +",contentLength:" + contentLength); } }); request.execute(); }
三、 如何隨時取消 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 接口中定義如下方法:
void checkIfCanceled() throws AppException; void cancel();
b. 主線程調用cancel請求:
public void testCancel(){ String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "stay_training_http.txt"; final Request request = new Request("http://h.hiphotos.baidu.com/image/w%3D2048/sign=432fca00369b033b2c88fbda21f636d3/a2cc7cd98d1001e9ae04c30bba0e7bec54e797fe.jpg",RequestMethod.GET); request.setCallback(new PathCallback() { @Override public void onSuccess(String result) { } @Override public void onFilure(Exception result) { result.printStackTrace(); } }.setPath(path)); request.setProgressListener(new IProgressListener() { @Override public void onProgressUpdate(int curPos, int contentLength) { System.err.println("curPost:"+curPos +",contentLength:" + contentLength); if (curPos > 8) { request.cancel();//當下載進度為8的時候我們取消了該請求 } } }); request.execute(); } }
c. 在 Request 中實現 cancel() 方法,方法內調用 callback 的 cancel() 方法:
public ICallback callback; public void cancel(){ if (callback != null) { this.callback.cancel(); } }
d. 在 AbstractCallback 中實現ICallback 裡定義的 cancel() 的方法,如果主線程調用了cancel方法,我們就將標志設置為 true
protected boolean isCancelled; @Override public void cancel() { isCancelled = true; }
e. 實現 ICallback 中的 checkIfCanceled() 方法,如果 isCancelled 為 true 則拋出異常,即可中斷請求操作,代碼中出現了 AppException 自定義異常類 這個我們後面再講
@Override public void checkIfCanceled() throws AppException { if (isCancelled) { throw new AppException(EnumException.CancelException, "request has been cancelled"); } }f. 在 handle 開開始放入 checkIfCanceled() 的判斷,在將下載的文件寫入到文件的時候我們也做判斷,還有在進行數據處理的時候也進行判斷
@Override public T handle(HttpResponse response, IProgressListener mProgressListener) throws AppException{ // file, json, xml, image, string checkIfCanceled();//在這裡我們調用檢查是否取消請求的方法 int statusCode = -1; InputStream in = null; try { HttpEntity entity = response.getEntity(); statusCode = response.getStatusLine().getStatusCode(); switch (statusCode) { case HttpStatus.SC_OK: if (TextUtil.isValidate(path)) { //將服務器返回的數據寫入到文件當中 FileOutputStream fos = new FileOutputStream(path); if (entity.getContentEncoding() != null) { String encoding = entity.getContentEncoding().getValue(); if (encoding != null && "gzip".equalsIgnoreCase(encoding)) { in = new GZIPInputStream(entity.getContent()); } if (encoding != null && "deflate".equalsIgnoreCase(encoding)) { in = new InflaterInputStream(entity.getContent()); } } else { in = entity.getContent(); } byte[] b = new byte[IO_BUFFER_SIZE]; int read; long curPos = 0; long length = entity.getContentLength(); while ((read = in.read(b)) != -1) { checkIfCanceled(); //在這裡我們調用檢查是否取消請求的方法 if (mProgressListener != null) { curPos += read; //將當前進度和總進度返回到具體的實現層, //我們在 RequestTask 的 doInBackground 中去實現 IProgressLinstener 接口中的的該方法,將值傳到onProgressUpdate中 mProgressListener.onProgressUpdate((int) (curPos / 1024), (int) (length / 1024)); } fos.write(b, 0, read); } fos.flush(); fos.close(); in.close(); //寫入文件之後,再從文件當中將數據讀取出來,直接返回對象 return bindData(path); } else { // 需要返回的是對象,而不是數據流,所以需要去解析服務器返回的數據 // 對應StringCallback 中的return content; //2. 調用binData return bindData(EntityUtils.toString(entity)); } default: break; } return null; } catch (ParseException e) { throw new AppException(EnumException.ParseException, e.getMessage()); } catch (IOException e) { throw new AppException(EnumException.IOException, e.getMessage()); } } /** * 數據放入到不同的Callback中處理,StringCallback 等方法中實現了該方法 * @throws AppException */ protected T bindData(String content) throws AppException{ checkIfCanceled();//在這裡我們檢查是否取消請求的方法 return null; }
g. 我們在 RequestTask 中重寫 onCancelled 判斷 是否有做了 task.cancel(true); 的操作,當然,我們並沒有實現該操作,那是因為,我們需要不管Request 的請求結果如果,我都需要返回到主線程,如果我這個時候取消掉了 AsyncTask ,那麼AsyncTask 就永遠不繼續執行了,也就無法回調回來了。
@Override protected void onCancelled() { super.onCancelled(); if (request.callback != null) { request.callback.cancel(); } }h. HttpClientUtil.java 中的 post 和 get 代碼中加入如下代碼,具體內容請看注釋:
private static HttpResponse get(Request request) throws AppException { try { //如果在代碼已經執行到這裡的時候,AbstractCallback中的isCancelled被置為了 true //這時我們就要再一次進行檢查是否取消。 post方法同理,不再贅述 if (request.callback != null) { request.callback.checkIfCanceled(); } HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(request.url); addHeader(get, request.headers); //返回的結果放到上一層進行處理 HttpResponse response = client.execute(get); return response; } catch (ClientProtocolException e) { throw new AppException(EnumException.ClientProtocolException, e.getMessage()); } catch (IOException e) { throw new AppException(EnumException.IOException, e.getMessage()); } }
上文提到的 AppException 就放到下篇文章再講吧,還有 預處理返回的對象,即將返回回來的數據解析成對象以後,對該對象進行預處理操作,如寫入數據庫之類的操作,也一起放入下篇文章講解,因為這塊我也還不是啃的很透,需要再磨練磨練,再看看 stay 老師的視頻,
一不小心一點半了,今天就寫到這裡吧,要寫好一篇博客真心難,從晚上 9 點開始一邊回顧視頻,一邊整理思路,一邊再重新實現一遍,然後一點點寫上來。
ps 本來寫了一大段抒情性的話的,等寫完了又覺得不太好意思,技術博客就單純一點吧。
特別感謝 stay 老師在這當中的幫助。讓我在框架學習這塊實打實的邁出了第一步! 他的個人網站:Stay技術分享技術生活 ,有興趣的可以去看看他的視頻,講解的相當到位。——純技術推薦...
觸摸事件相關方法:ViewGroupdispatchTouchEvent(MotionEvent) 用於分發touch事件onInterceptTouchEvent(Mo
前言我們已經介紹了SimpleNet網絡框架的基本結構,今天我們就開始從代碼的角度來開始切入該網絡框架的實現,在剖析的同時我們會分析設計思路,以及為什麼要這樣做,這樣做的
我們使用百度地圖的時候,點擊地圖上的Marker,會彈出一個該地點詳細信息的窗口,如下左圖所示,有時候,我們希望自己定義這個彈出窗口的內容,或者,干脆用自己的數據來構造這
FloatingActionButton和ImageButton的區別:跟上一篇博文對比,這裡左右對調了兩種Button的位置,對實現效果沒有影響,可以自己嘗試換過來,也