Android網絡開發 請求隊列
文章出處:Android那些事兒的部落格
因為之前參與的網絡開發項目都遇到一些相同的問題:
1.大量的并發請求造成堵塞,特别是遇上讓人無語的3G網絡,無限loading。。。
2.一般來說一個網絡請求都會用使用到一個異步線程,大量的線程建立、運作、銷毀又造成了系統資源的浪費
3.請求結束得到結果後,如果需要更新UI,一個不小心忘了傳回UI線程,各種崩潰。。。
前些日子跟同僚商量能不能做個請求隊列去進行控制,于是趁着熱度沒消退說幹就幹,建了個模型,以備日後使用。
在這個模型中,有高中低三個優先級信道如下:高優先級–1,中優先級–3,低優先級–2
規則:
1.正常情況下各個優先級使用各自信道(線程)
2.進階信道滿載、中、低級信道空置,則進階請求可使用低級信道
構思:
UI線程将期望的網絡請求url和參數通過一個封裝好的Runnable送出給Service處理(當然也可以交給一個Thread處理,本例使用Service),Service接收到請求,判斷優先級,加入到相應線程池中排隊。線程池啟動線程發起網絡請求,最後通過監聽器将結果傳回給Service,Service發送廣播通知UI線程,UI線程更新相關界面,結束。
廢話說完,上例子:
首先是封裝好的Runnable
public class HttpConnRunnable implements Runnable, Parcelable {
public static final int HIGH_LEVEL = 0;
public static final int NORMAL_LEVEL = 1;
public static final int LOW_LEVEL = 2;
private int mPriority = NORMAL_LEVEL;//優先級,預設為普通
private String mUrl = "";
private HttpConnListener mListener;//監聽器
public HttpConnRunnable() {
super();
}
public HttpConnRunnable(int priority) {
super();
mPriority = priority;
}
@Override
public void run() {
Log.i(Thread.currentThread().getName(), "----Start to connect:" + mUrl + ", priority:" + mPriority + "-----");
try {
Thread.sleep(10000);
//TODO:進行網絡請求相關操作,并通過listener傳回結果
mListener.onSucceed("Connected to " + mUrl + " succeed!");
}
catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(Thread.currentThread().getName(), "----Finish to connect:" + mUrl + ", priority:" + mPriority + "-----");
}
public int getPriority() {
return mPriority;
}
public void setPriority(int priority) {
mPriority = priority;
}
public String getURL() {
return mUrl;
}
public void setURL(String url) {
mUrl = url;
}
public void setHttpConnListener(HttpConnListener listener) {
mListener = listener;
}
//序列化,為了傳遞給Service,如果是使用Thread處理本例,則無需序列化
public static final Parcelable.Creator<HttpConnRunnable> CREATOR = new Creator<HttpConnRunnable>() {
@Override
public HttpConnRunnable createFromParcel(Parcel source) {
HttpConnRunnable data = null;
Bundle bundle = source.readBundle();
if(bundle != null) {
data = new HttpConnRunnable(bundle.getInt("PRIORITY"));
data.mUrl = bundle.getString("URL");
}
return data;
}
@Override
public HttpConnRunnable[] newArray(int size) {
return new HttpConnRunnable[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
Bundle bundle = new Bundle();
bundle.putInt("PRIORITY", mPriority);
bundle.putString("URL", mUrl);
dest.writeBundle(bundle);
}
}
Service的處理:
public class HttpConnService extends Service implements HttpConnListener {
public static final String HTTP_POOL_PARAM_KEYWORD = “HttpPoolParam”; //網絡參數傳遞的關鍵字
private final int HIGH_POOL_SIZE = 1;
private final int NORMAL_POOL_SIZE = 3;
private final int LOW_POOL_SIZE = 2;
// 可重用固定線程數的線程池
private ThreadPoolExecutor mHighPool;
private ThreadPoolExecutor mNormalPool;
private ThreadPoolExecutor mLowPool;
@Override
public void onCreate() {
//初始化所有
mHighPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(HIGH_POOL_SIZE);
mNormalPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(NORMAL_POOL_SIZE);
mLowPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(LOW_POOL_SIZE);
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//接受到來自UI線程的請求
//取出Runnable,并加入到相應隊列
Bundle bundle = intent.getExtras();
HttpConnRunnable httpConnRunnable = bundle.getParcelable(HTTP_POOL_PARAM_KEYWORD);
if (httpConnRunnable != null) {
httpConnRunnable.setHttpConnListener(HttpConnService.this);
int level = httpConnRunnable.getPriority();
switch (level) {
case HttpConnRunnable.HIGH_LEVEL:
//如果進階池滿而低級池未滿交由低級池處理
//如果進階池滿而普通池未滿交由普通池處理
//如果進階池未滿則交給進階池處理,否則,交由進階池排隊等候
if (mHighPool.getActiveCount() == HIGH_POOL_SIZE && mLowPool.getActiveCount() < LOW_POOL_SIZE) {
mLowPool.execute(httpConnRunnable);
}
else if (mHighPool.getActiveCount() == HIGH_POOL_SIZE && mNormalPool.getActiveCount() < NORMAL_POOL_SIZE) {
mNormalPool.execute(httpConnRunnable);
}
else {
mHighPool.execute(httpConnRunnable);
}
break;
case HttpConnRunnable.NORMAL_LEVEL:
//如果普通池滿而低級池未滿交由低級池處理
//如果普通池未滿則交給普通池處理,否則,交由普通池排隊等候
if (mNormalPool.getActiveCount() == NORMAL_POOL_SIZE && mLowPool.getActiveCount() < LOW_POOL_SIZE) {
mLowPool.execute(httpConnRunnable);
}
else {
mNormalPool.execute(httpConnRunnable);
}
break;
case HttpConnRunnable.LOW_LEVEL:
mLowPool.execute(httpConnRunnable);
break;
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
mHighPool.shutdownNow();
mNormalPool.shutdownNow();
mLowPool.shutdownNow();
mNormalPool = null;
mLowPool = null;
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onSucceed(String result) {
Intent intent = new Intent();
intent.setAction("com.ezstudio.connpool.HttpConnReceiver");
// 要發送的内容
intent.putExtra("RESULT", result);
// 發送 一個無序廣播
sendBroadcast(intent);
}
@Override
public void onFailed() {
// TODO Auto-generated method stub
}
}
Receiver的處理比較簡單:
public class HttpConnReceiver extends BroadcastReceiver {
private HttpConnListener mListener;
public void setHttpConnListener (HttpConnListener listener) {
mListener = listener;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals("com.ezstudio.connpool.HttpConnReceiver")) {
String result = intent.getStringExtra("RESULT");
mListener.onSucceed(result);
}
}
}
可能很多同學都看過這麼文章,不過由于沒有源碼可能還是雲裡霧裡,由于我也是個小菜鳥的關系就本着學習的心态對照原部落格,可新浪微網誌發送微網誌的效果自己實作了隊列請求的效果,便記錄下來,以便日後拿來用。也将源碼分享給大家
源碼
此源碼是as工程,不會在eclipse導入as工程的同學可以new eclipse工程,拷一下代碼和布局檔案。。。