安卓開發之訊息機制和AsyncTask實現的基本原理
一、基本概述
在Android中,只可以在UiThread(UI主執行緒)才可以直接更新介面,不然會丟擲異常。
WHY:
防止多個執行緒來修改介面,導致混亂
通過同步鎖來防止介面混亂會導致效能降低 。在Android中,長時間的工作(比如聯網)都需要在workerThread(分執行緒/工作執行緒)中執行。
在分執行緒中獲取伺服器資料後,可通過訊息的傳遞進行執行緒間的通訊,到主執行緒中更新介面顯示。
二、訊息機制中的訊息Message
Message可以理解為執行緒間通訊的訊息單元,可通過message攜帶需要傳遞的資料。
封裝資料:
public int what // 標識訊息的id public int arg1 // 可用來攜帶int型別的資料 public int arg2 // 可用來攜帶int型別的資料 public Object obj //可用來攜帶一個物件資料
與一個Handler物件繫結
Handler target //繫結的一個handler物件,由該handler物件傳送和處理該訊息。
建立物件:
Message.obtain() 從**訊息池**中獲取訊息 /new Message() 重新建立一個訊息。
三、訊息機制中的處理器Handler
Handler是訊息Message的處理器,同時負責訊息的傳送、處理和未處理訊息的移除工作。
具體方法
傳送即時訊息:sendMessage(Message msg)
傳送延時訊息:sendMessage(Message msg, long time) 【延時處理】
傳送帶標識的空訊息:sendEmptyMessage(int what)
立即傳送Message到佇列的最前面:sendMessageAtFrontOfQueue()
處理訊息:handleMessage(Message msg) 【這是一個回撥方法,需要重寫回撥方法來處理訊息】
移除還未處理的訊息:removeMessage(int what)
四、訊息機制中的訊息佇列MessageQueue
MessageQueue用來存放通過Handler傳送過來的訊息
訊息佇列MessageQueue是一個按Message的when排序的優先順序佇列,先進先出。
即時訊息:when = 傳送時的當前時間
延時訊息:when = 傳送時的當前時間 + 延遲時間
MessageQueue內部使用連結串列來實現,容易對佇列進行刪/增結點
五、訊息機制中的迴圈器Looper(鉤子)
訊息泵,不斷地從MessageQueue中抽取Message執行。一個MessageQueue對應一個Looper。
負責迴圈取出訊息佇列MessageQueue裡面的當前需要處理的訊息Message
負責將取出的訊息Message交給對應的目標Handler來處理。
處理完之後,將Mesaage快取到訊息池中,以備複用。
六、不使用直接Handler實現非同步工作
1.`Activity的runOnUiThread方法在主執行緒中呼叫。(內部還是使用下面的Handler的post方法)
//在分執行緒獲取資料,獲取到資料在主執行緒中更新UI
new Thread(){
@Override
public void run() {
super.run();
final String jsonResult = request(httpUrl, httpArg);
//分執行緒獲取網路資料,獲取資料完後再呼叫下面的方法
MainActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
if(jsonResult!=null)
TextView.setText(jsonResult);
//主執行緒UiThread中更新介面
}
});
}
}.start();
2. View.post(Runnable) : Runnable與View在同一個執行緒。
mScrollView.post(new Runnable() {
@Override
public void run() {
mScrollView.scrollTo(0,0);
}
});
七、使用Handler實現非同步工作
1. 直接呼叫Handler物件的post方法
handler的post方法:Runnable與handler在同一個執行緒。
//在分執行緒獲取資料,獲取到資料在主執行緒中更新UI
new Thread(){
@Override
public void run() {
super.run();
final String jsonResult = request(httpUrl, httpArg);
//分執行緒獲取網路資料
// (mHandler在主執行緒)
mHandler.post(new Runnable() {
@Override
public void run() {
if(jsonResult!=null)
TextView.setText(jsonResult);
//主執行緒UiThread中更新介面
}
});
}
}.start();
2. 使用Handler物件傳送訊息
建立Handler成員變數物件,並重寫其handleMessage(Message msg)方法。
在分執行緒/主程式建立Message物件,使用Message訊息攜帶非同步獲得的訊息。
使用Handler物件傳送Message
在handleMessage(Message msg) 回撥方法中獲取資料。(也可以在該方法中傳送延時訊息,從而形成一種定時迴圈,停止迴圈時使用removeMessage(int what)方法即可)
// 寫在MainActiviy中 private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if(msg.what==0) TextView.setText((String) msg.obj); } };
// 寫在MainActivity的onCreate中 new Thread(){ @Override public void run() { super.run(); //非同步執行緒獲取資料 final String jsonResult = request(httpUrl, httpArg); //在分執行緒/主程式建立Message物件,使用Message訊息攜帶非同步訊息。 Message msg = Message.obtain(); msg.what = 0; msg.obj = jsonResult; handler.sendMessage(msg);//傳送即時訊息 } }.start();
“`
八、Handler對訊息的三種處理方式
主要通過Handler中的dispatchMessage方法對Message物件處理
下圖為dispatchMessage(Message msg)的原始碼
一、通過Message物件的callback處理
主要體現在 Handler.post(Runnable r) / View.post(Runnable r) 方法中。該方法將傳入的引數Runnable物件封裝成一個Message物件,該Message物件的callback就是傳入Runnable物件r.
在dispatchMessage原始碼中,當Message物件的回撥callback不為空時,是通過handleCallback來處理該訊息的。下面為handleCallback的原始碼:
private final void handleCallback(Message message) {
message.callback.run();
}
二、使用Handler的handleMessage(Message msg)處理。
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
三、通過Handler的callback來處理
public Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
//TODO
return false;
//返回false時不對該訊息攔截,因此下面的訊息會被再handleMessage處理
//返回true時對該訊息攔截,因此下面的訊息不會被再處理
//可以不復寫下面的方法
}
}){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//TODO
}
};
Handler的使用:
如果通過按鈕的點選事件來使用handler傳遞訊息啟動非同步工作,那麼一個非同步工作啟動後要限制住按鈕的可操作性,防止啟動多個非同步工作。(比如設定邏輯:如果再次點選按鈕,則重新啟動非同步工作,而不是再次啟動另一個非同步工作;或者設定按鈕不可以點選。)
非同步任務AsyncTask
在沒有AsyncTask之前,我們可以使用Handler+Thread來實現非同步任務的功能需求。
AsyncTask是對Handler和Thread的封裝,使用它編碼更簡潔,效率更高。
AsyncTask封裝了ThreadPool執行緒池,實現了執行緒的複用,比直接使用Thread效率更高。
API
1.AsyncTask<Params,Progress,Result> //不能使用基本資料型別 Params : 啟動任務執行的輸入可變引數泛型 Progress:後臺任務執行的百分比 //一般為Integer型別 Result:後臺執行任務後返回的結果泛型 2.execute(Params...params) //啟動任務 3.void onPreExecute() //在分執行緒工作開始之前在UIThread中執行 4.Result doInBackground(Params..params) //在分執行緒中執行,完成任務的主要哦工作 5.void onPostExecute(Result reslut ) // 在doInBackground執行完後在UiThread中執行,一般用來更新介面 6.publishProgress(Progress..values) //在分執行緒中,釋出當前進度 7.void onProgressUpdate(Progress..values) //在主執行緒中,更新當前進度
在分執行緒裡不要進行UI操作,比如在doInBackground執行Toast提示操作。
例項:
new DemoAsyncTask(.......).execute(...); //自行設計泛型/引數,進度和返回結果的型別,補充各個以上方法即可
AysncTask實現的基本原理
AysncTask是一個抽象類:下列程式碼片段摘自AsyncTask原始碼(Android-23)
AsyncTask的三種狀態:等待、執行、完成
/** * Indicates the current status of the task. Each status will be set only once * during the lifetime of a task. */ public enum Status { /** * Indicates that the task has not been executed yet. */ PENDING, /** * Indicates that the task is running. */ RUNNING, /** * Indicates that {@link AsyncTask#onPostExecute} has finished. */ FINISHED, }
AsyncTask的構造方法
A. Params和Result為我們實現AsyncTask時傳入的引數和返回結果型別
B. 在建構函式中,我們寫的後臺操作 doInBackground 先被封裝為一個WorkerRunnable物件mWorker,再將mWorker封裝為一個FutureTask物件mFuture。mFuture的任務執行結束後呼叫done方法通過handler來傳遞返回的結果:
C. 在構造方法中,mFuture任務的執行執行緒並沒有馬上啟動。
public AsyncTask() { mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked Result result = doInBackground(mParams); Binder.flushPendingCommands(); return postResult(result); } }; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); } } }; } ------------------------ private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { postResult(result); } } private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }
AsyncTask中的execute方法
public final AsyncTask<Params, Progress, Result> execute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
然後再看一下executeOnExecutor方法:
A. 在該方法中,先判斷AsyncTask物件的狀態,狀態不為等待時則丟擲異常
B. 然後在方法中呼叫我們的預處理方法 onPreExecute(在主執行緒中呼叫),最後設定一下非同步任務物件的狀態和引數,再將上述的FutureTask物件mFuture執行。
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec, Params... params) { if (mStatus != Status.PENDING) { switch (mStatus) { case RUNNING: throw new IllegalStateException("Cannot execute task:" + " the task is already running."); case FINISHED: throw new IllegalStateException("Cannot execute task:" + " the task has already been executed " + "(a task can be executed only once)"); } } mStatus = Status.RUNNING; onPreExecute(); mWorker.mParams = params; exec.execute(mFuture); return this; }
C. exec是一個SerialExecutor物件(執行緒池),這裡將mFuture線上程池中執行。
private static class SerialExecutor implements Executor { final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>(); Runnable mActive; public synchronized void execute(final Runnable r) { mTasks.offer(new Runnable() { public void run() { try { r.run(); } finally { scheduleNext(); } } }); if (mActive == null) { scheduleNext(); } } protected synchronized void scheduleNext() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
AsyncTask執行任務時並行/序列?
在Android 1.6之前的版本,AsyncTask是序列的,在1.6至2.3的版本,改成了並行的。在2.3之後的版本又做了修改,可以支援並行和序列,當想要序列執行時,直接執行execute()方法,如果需要並行執行,則要執行executeOnExecutor(Executor)
Handler和AysncTask的考量
AysncTask會使用到執行緒池,當非同步任務特別龐大時,執行緒池的優勢就會顯示出來。當非同步任務比較多時,可使用AsyncTask來完成非同步操作。單個任務使用AsyncTask會銷燬較多的資源。
當然AsyncTask的執行緒池個數也是有限的
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
以8核手機為例,其核心執行緒數是9個,最大執行緒是17,所能最大加入的任務數是128+17=145
參考:
相關文章
- OC訊息機制,訊息轉發機制
- Fluter訊息機制之微任務實現原理
- 要點提煉|開發藝術之訊息機制
- iOS進階之訊息轉發機制iOS
- Android 之訊息機制Android
- iOS:利用訊息轉發機制實現多播委託iOS
- Android程式間通訊–訊息機制及IPC機制實現薦Android
- Android訊息傳遞之Handler訊息機制Android
- Android-Handler訊息機制實現原理Android
- 書海拾貝|開發藝術探索之 android 的訊息機制Android
- 訊息機制
- IOS學習之NSNotificationCenter訊息機制iOS
- android之 Android訊息機制Android
- 簡單實現Android中的訊息迴圈機制Android
- iOS訊息機制iOS
- SAP訊息機制
- iOS 訊息轉發機制Demo解析iOS
- 使用Erlang訊息機制實現穩定婚姻問題
- iOS探索 動態方法解析和訊息轉發機制iOS
- Android的訊息機制Android
- WebRTC中的訊息機制Web
- WTL的訊息機制 (轉)
- JAVA訊息確認機制之ACK模式Java模式
- 深入探索Android訊息機制之HandlerAndroid
- 用redis實現訊息佇列(實時消費+ack機制)Redis佇列
- iOS開發筆記(三):訊息傳遞與轉發機制iOS筆記
- AsyncTask機制詳解
- 訊息機制篇——初識訊息與訊息佇列佇列
- JMS java 訊息機制Java
- Windows訊息機制概述Windows
- Android的訊息機制之ThreadLocal的工作原理Androidthread
- OC訊息機制和super關鍵字
- Android訊息機制Message訊息池Android
- 【RocketMQ】訊息的刷盤機制MQ
- 06.Android之訊息機制問題Android
- 安卓開發之Fragment的使用與通訊安卓Fragment
- 使用SpringCloud Stream結合rabbitMQ實現訊息消費失敗重發機制SpringGCCloudMQ
- RabbitMQ訊息佇列(九):Publisher的訊息確認機制MQ佇列