編輯:關於Android編程
最近有空在看《App研發錄》一書,良心之作。書中第一部分第二章節講了不少關於網絡底層封裝的知識,看後覺得學到了不少干貨。
索性自己也動手完成了一個非常輕量級的網絡請求框架,從該書中獲得了不少幫助。特此記錄,回顧一下思路,整理收獲。OK,一起來看。
就如書中所言,通常我們可以通過AsyncTask來進行網絡請求的處理。而不少網絡請求框架的底層也正是基於AsyncTask來進行封裝的。
顯然AsyncTask有很多優點,使用也十分便捷。但它肯定同樣也存在缺點:即我們無法靈活控制其內部的線程池;無法取消請求等。
無法取消一個請求的情況是指:假設我們在Activity-A中有10個請求需要執行。那麼可能因為網絡條件等原因出現一種情況:
即用戶已經通過某種操作從Activity-A跳轉至B,這時B中也有網絡請求需要執行。但Activity雖然已經跳轉,而在其中發出的請求仍會繼續進行。
那麼,Activity-B中的請求就會因為等待Activity-A中的請求執行完畢而陷入阻塞。從而造成一種無限“擁堵”的情況。
我們自然需要避免之前的這些情況。所以我們的框架將采取原生 的ThreadPoolExecutor + Runnble + Handler + HttpUrlConnection實現。
我們先通過一張圖,來看一看完成後的框架其最終的結構是怎麼樣的:
現在我們一次來分析一下它們的作用,由於篇幅的原因,這裡不會一一貼出源碼,該框架的github地址如下,有興趣的朋友可以對應一看:
https://github.com/unconventional1programmer/PacHttpClient
現在我們首先肯定就是用一用它,先爽一下。然後簡單的挨個分析一下框架中的每個類都扮演了什麼角色。
首先,我們要做的肯定是設置相關配置信息,並初始化我們的請求框架。就像下面這樣:PacHttpClientConfig config = new PacHttpClientConfig(getApplicationContext()) .corePoolZie( ? ) .maxPoolSize( ? ) .keepAliveTime( ? ) .timeUnit( ? ) .blockingQueue( ? ); PacHttpClient.init(config);
而因為我們本身在框架裡也設置過默認的線程池配置信息,所以我們也可以使用另一種更加偷懶的初始化方式:
PacHttpClientConfig config = new PacHttpClientConfig(getApplicationContext()); PacHttpClient.init(config);好了,現在我們先來簡單的發起一個GET請求。來體驗一下框架的使用快感:
/*HttpRequest request = */ PacHttpClient.invokeRequest(this, "testGet", null, new RequestCallback() { @Override public void onSuccess(String content) { Log.d("TestMyFrameWork", "請求成功"); } @Override public void onFail(String errorMessage) { Log.d("TestMyFrameWork", "請求失敗"); } });
運行程序,我們查看相關的日志打印,證明我們確實已經成功的與服務器進行了一次GET通信:
要模擬中斷的情況也簡單,我們只需要在Servlet服務器通過讓線程休眠5秒來模擬實際情況中的讀取數據的過程。也就是說:
當我們該次HTTP請求與服務器建立起鏈接後,read inputstrem的過程會經過5秒的時間。我們就在這個時間內,中斷本次請求。
現在,我們在之前的代碼的基礎上,加上如下一句中斷請求的代碼:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
PacHttpClient.cancelDesignatedRequest(this,request);
我們再次運行程序,經過耐心的等待,會發現不會得到任何相關的輸出信息。因為請求的確已經被我們中斷了。
好的,現在我們來加大點力度。假設我們在一個Activity中發出10個請求,看看會發生什麼。for (int i = 0; i < 10; i++) { // PacHttpClient.invokeRequest()..... }
再次運行程序,我們會發現如下的輸出情況:
這種情況我們是可以預料得到的,因為我們在框架中為線程池設置的核心池的默認大小為5,所以每次自然只會有5個線程來執行請求。
而當有請求執行完畢後,則會從阻塞隊列中取出新的請求來執行。那麼注意了,也就是說我們在Activity發出10個請求後:
有5個請求會率先開始執行,另外5個將會進入阻塞隊列中等待。那麼,我們也就可以測試我們的框架中的另一個方法了。
這時,我們可能會希望用兩種方式應對。第一種就是,我們希望已經開始執行的請求繼續。但將還沒執行的請求中斷。而第二種就像我們說過的:
假設我們跳轉後的Activity也有請求需要執行,那麼受之前的界面中的請求影響,所以我們希望中斷跳轉之前的Activity中的所有請求,包括正在執行的。
那麼,這個時候就開心了。因為我們在自己的請求框架中已經對於這些情況做了封裝。所以我們能很容易就能實現這種需求。
首先,我們來看看中斷未執行的請求怎麼樣發生。我們在之前的代碼的基礎上加上如下代碼:
PacHttpClient.cancelBlockingRequest(this);
然後,我們觀察日志信息發現,還未來得及執行的5條請求的確是被取消了:
好的,現在修改如下的代碼。這樣做的目的在於:我們雖然發起了10個請求,但我們希望只要有某一個請求執行完畢,就取消剩余所有的請求(包括正在執行的)
public void onSuccess(String content) { Log.d("TestMyFrameWork", "請求成功"); PacHttpClient.cancelAllRequest(MainActivity.this); }
根據日志信息,我們可以驗證我們的確實現了我們的目的:
由此,我們就可以針對於一些情況做出應對了。以我們說的跳轉Activity希望取消請求而言,我們只需要在適合的聲明周期調用對應的方法就搞定了。
最後,PacHttpClient還有提供了另外兩個公有方法:shutdown以及shutdownRightnow。顧名思義,就是關閉框架中封裝的線程池的。簡單的爽了一下,現在來簡單分析下整個框架的構成。首先來說,當我們項目中的http-api越來越多,那麼將這些url信息存放在代碼中肯定是很不爽的。
那麼,就像《App研發錄》一書中推薦的一樣,我們可以在xml目錄下新建一個xml文件單獨來管理我們的api。我們暫時將格式設定如下:
現在有了存放url的xml文件。那麼,對應的我們就需要一個類來解析xml文件,獲取到相關的請求信息;並將讀取到的信息存放進一個實體類以供使用。
URLEntity.java
class URLEntity { private String key; //apiKey private long expires; //緩存時間 private HttpRequest.RequestType netType; //請求方式(GET or POST) private String url; //url //相關的setter/getter }
URLConfigManager.java
關於這個其實類沒什麼好說的,所做的工作就是解析xml文件,並將讀取的信息存放進URLEntity對象。唯一值得注意的一點是:
如果每次讀取url都從xml文件進行解析,肯定影響效率。所以我們在初次讀取時,一次性將所有url讀進內存中的map存放,以後就直接從map中讀取。
RequestThreadPool.java
我們說過框架將采取原生的RequestThreadPool實現,該類實際就是對線程池的一個封裝。並提供相關的操作線程池的方法。
class RequestThreadPool { // 封裝的線程池 private static ThreadPoolExecutor pool; /** * 根據配置信息初始化線程池 */ static void init(){ PacHttpClientConfig config = PacHttpClient.config; pool = new ThreadPoolExecutor(config.corePoolZie, config.maxPoolSize, config.keepAliveTime, config.timeUnit, config.blockingQueue); } /** * 執行任務 * @param r */ public static void execute(final Runnable r) {} /** * 清空阻塞隊列 */ static void removeAllTask() {} /** * 從阻塞隊列中刪除指定任務 * @param obj * @return */ static boolean removeTaskFromQueue(final Object obj) {} /** * 獲取阻塞隊列 * @return */ static BlockingQueuegetQuene(){} /** * 關閉,並等待任務執行完成,不接受新任務 */ static void shutdown() {} /** * 關閉,立即關閉,並掛起所有正在執行的線程,不接受新任務 */ static void shutdownRightnow() {} }
HttpRequest.java
這可以說是最關鍵的一個類了,在這個類當中,我們通過HttpUrlConncetion完成對請求的實際封裝。
public class HttpRequest implements Runnable { //some code... @Override public void run() { // 判斷請求類型 switch (urlInfo.getNetType()) { case GET: // 類型為HTTP-GET時,將請求參數組裝到URL鏈接字符串上 String trulyURL; if (params != null && !params.isEmpty()) { StringBuilder urlBuilder = new StringBuilder(urlInfo.getUrl()); urlBuilder.append("?").append(convertParam2String()); trulyURL = urlBuilder.toString(); } else { trulyURL = urlInfo.getUrl(); } // 正式發送GET請求到服務器 sendHttpGetToServer(trulyURL); break; case POST: // 發送POST請求到服務器 sendHttpPostToServer(urlInfo.getUrl()); break; default: break; } } /** * 發起GET請求 * * @param url */ private void sendHttpGetToServer(String url) { try { mURL = new URL(url); mConnection = (HttpURLConnection) mURL.openConnection(); // 連接服務器的超時時長 mConnection.setConnectTimeout(5000); // 從服務器讀取數據的超時時長 mConnection.setReadTimeout(8000); if (mConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { // 如果未設置請求中斷,則進行讀取數據的工作 if (!interrupted) { // read content from response.. final String result = readFromResponse(mConnection.getInputStream()); // call back if (callback != null) { handler.post(new Runnable() { @Override public void run() { callback.onSuccess(result); } }); } } else { // 中斷請求 return; } } else { handleNetworkError("網絡異常"); } } catch (MalformedURLException e) { handleNetworkError("網絡異常"); } catch (IOException e) { handleNetworkError("網絡異常"); } finally { hostManager.requests.remove(this); } } // some code.... /** * 中斷請求 */ void disconnect() { // 設置標志位 interrupted = true; // 如果當前請求正處於與服務器連接狀態下,則斷開連接 if (mConnection != null) mConnection.disconnect(); } }
我們保留了部分關鍵代碼,其實該類的核心工作從上面的代碼基本上能夠得以體現。
我們這裡關注的重點放在“取消”請求。“取消”的情況實際上大體可以分為三種:
RequestManager.java
因為我們知道一個activity通常肯定不會只有一個請求需要執行。所以,我們需要一個對象來管理activity中的所有請求。
class RequestManager { ArrayListrequests; public RequestManager() { requests = new ArrayList<>(); } /** * 無參數調用 */ public HttpRequest createRequest(URLEntity url, RequestCallback requestCallback) { return createRequest(url, null, requestCallback); } /** * 有參數調用 */ public HttpRequest createRequest(URLEntity url, List params, RequestCallback requestCallback) { HttpRequest request = new HttpRequest(this, url, params, requestCallback); addRequest(request); return request; } /** * 添加Request到列表 */ public void addRequest(final HttpRequest request) { requests.add(request); } /** * 取消所有的網絡請求(包括正在執行的) */ public void cancelAllRequest() { BlockingQueue queue = RequestThreadPool.getQuene(); for (int i = requests.size() - 1; i >= 0; i--) { HttpRequest request = requests.get(i); if (queue.contains(request)) { queue.remove(request); } else { request.disconnect(); } } requests.clear(); } /** * 取消未執行的網絡請求 */ public void cancelBlockingRequest() { // 取交集(即取出那些在線程池的阻塞隊列中等待執行的請求) List intersection = (List ) requests.clone(); intersection.retainAll(RequestThreadPool.getQuene()); // 分別刪除 RequestThreadPool.getQuene().removeAll(intersection); requests.removeAll(intersection); } /** * 取消指定的網絡請求 */ public void cancelDesignatedRequest(HttpRequest request) { if (!RequestThreadPool.removeTaskFromQueue(request)) { request.disconnect(); } } }
RequestParameter.java
這個類也很簡單,就是對請求參數做一個封裝,簡單的來說就是封裝請求參數的鍵值對。
RequestCallback.java
很顯然,通常我們都會根據請求從服務器返回的數據來執行一些操作。所以,我們還需要一個回調接口:
public interface RequestCallback { void onSuccess(String content); void onFail(String errorMessage); }
PacHttpClient.java
實際上,現在我們已經萬事俱備了。但我們不希望框架的使用者直接接觸我們底層封裝的這些類。所以我們來提供一個共有的調用類。
這個類的工作很簡單,就是提供一些共有的方法供用戶調用,來完成發起請求,中斷請求,關閉線程池等操作。
該類中還有一個關鍵的變量managerMap。我們說了,每個activity都需要自己的RequestManager來管理自身的所有請求。
這個意義在於,調用者在Activity執行響應的請求操作時,只需要傳入自身this對象,我們就能夠找到對應的請求進行操作。
// 存放每個Activity對應的RequestManager static Map managerMap;
當然,我們需要為我們的框架提供一個酷酷的名字。因為讀書的時候就是一位偉大的已故HipHop大神2pac的腦殘粉,所以就叫PacHttpClient吧。
PacHttpClientConfig.java
我們還可以支持讓用戶來自己定制關於網絡框架的一些相關信息,目前這裡主要是提供對於線程池的配置信息以及context的設置。
ImageLoader.getInstance.init(config);
上面這樣類似的代碼一定很熟悉吧,我們這裡定義的此類也是提供同樣的效果。
好了,就總結到這裡了。當然了,這個小框架肯定還有很多不足和可以改進的地方,請多多指教!
上一篇寫了一個可隨時暫停的圓形進度條,接下來再來撸一個帶小圓圈的倒計時View,主要難點是對於隨著進度條變化而變化的小圓的繪制。看了givemeacondom大神寫的小圓
android 控件眾多 , 額 , 具體多少個呢? 貌似有那麼幾十個吧,也沒做個統計,嘿嘿!...... 有木有朋友感覺寫了那麼長時間的android代碼,有
自從Gallery被谷歌廢棄以後,Google推薦使用ViewPager和HorizontalScrollView來實現Gallery的效果。的確HorizontalSc
短信注冊和短信驗證已經是家常便飯了,所以當然要學習如何使用SDK啦 MobSDK可以免費發短信,當然就用它啦 http://www.mob.com1.首先下載sdk2.在