編輯:關於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 BlockingQueue getQuene(){}
/**
* 關閉,並等待任務執行完成,不接受新任務
*/
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 {
ArrayList requests;
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);
上面這樣類似的代碼一定很熟悉吧,我們這裡定義的此類也是提供同樣的效果。
好了,就總結到這裡了。當然了,這個小框架肯定還有很多不足和可以改進的地方,請多多指教!
Android自定義View之帶小圓圈的倒計時圓形進度條
上一篇寫了一個可隨時暫停的圓形進度條,接下來再來撸一個帶小圓圈的倒計時View,主要難點是對於隨著進度條變化而變化的小圓的繪制。看了givemeacondom大神寫的小圓
一步一步學android控件(之一) —— 開始篇
android 控件眾多 , 額 , 具體多少個呢? 貌似有那麼幾十個吧,也沒做個統計,嘿嘿!...... 有木有朋友感覺寫了那麼長時間的android代碼,有
Android自定義HorizontalScrollView打造超強Gallery效果
自從Gallery被谷歌廢棄以後,Google推薦使用ViewPager和HorizontalScrollView來實現Gallery的效果。的確HorizontalSc
Android使用MobSDK短信驗證
短信注冊和短信驗證已經是家常便飯了,所以當然要學習如何使用SDK啦 MobSDK可以免費發短信,當然就用它啦 http://www.mob.com1.首先下載sdk2.在