安卓開發之訊息機制和AsyncTask實現的基本原理

cxmscb發表於2016-07-08

一、基本概述

  1. 在Android中,只可以在UiThread(UI主執行緒)才可以直接更新介面,不然會丟擲異常。
    WHY:
    防止多個執行緒來修改介面,導致混亂
    通過同步鎖來防止介面混亂會導致效能降低 。

  2. 在Android中,長時間的工作(比如聯網)都需要在workerThread(分執行緒/工作執行緒)中執行。

  3. 在分執行緒中獲取伺服器資料後,可通過訊息的傳遞進行執行緒間的通訊,到主執行緒中更新介面顯示。

二、訊息機制中的訊息Message

  1. Message可以理解為執行緒間通訊的訊息單元,可通過message攜帶需要傳遞的資料。

  2. 封裝資料:

    public int what  // 標識訊息的id
    
    public int arg1   // 可用來攜帶int型別的資料
    
    public int arg2   // 可用來攜帶int型別的資料  
    
    public Object obj //可用來攜帶一個物件資料
    
  3. 與一個Handler物件繫結

    Handler target //繫結的一個handler物件,由該handler物件傳送和處理該訊息。
    
  4. 建立物件:

    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

  1. MessageQueue用來存放通過Handler傳送過來的訊息

  2. 訊息佇列MessageQueue是一個按Message的when排序的優先順序佇列,先進先出。

    即時訊息:when = 傳送時的當前時間

    延時訊息:when = 傳送時的當前時間 + 延遲時間

  3. MessageQueue內部使用連結串列來實現,容易對佇列進行刪/增結點

五、訊息機制中的迴圈器Looper(鉤子)

訊息泵,不斷地從MessageQueue中抽取Message執行。一個MessageQueue對應一個Looper。

  1. 負責迴圈取出訊息佇列MessageQueue裡面的當前需要處理的訊息Message

  2. 負責將取出的訊息Message交給對應的目標Handler來處理。

  3. 處理完之後,將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物件傳送訊息

  1. 建立Handler成員變數物件,並重寫其handleMessage(Message msg)方法。

  2. 在分執行緒/主程式建立Message物件,使用Message訊息攜帶非同步獲得的訊息

  3. 使用Handler物件傳送Message

  4. 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

  1. 在沒有AsyncTask之前,我們可以使用Handler+Thread來實現非同步任務的功能需求。

  2. AsyncTask是對Handler和Thread的封裝,使用它編碼更簡潔,效率更高。

  3. AsyncTask封裝了ThreadPool執行緒池,實現了執行緒的複用,比直接使用Thread效率更高。

  4. 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) 
    //在主執行緒中,更新當前進度
    

    這裡寫圖片描述

  5. 在分執行緒裡不要進行UI操作,比如在doInBackground執行Toast提示操作。

  6. 例項:

        new DemoAsyncTask(.......).execute(...); 
        //自行設計泛型/引數,進度和返回結果的型別,補充各個以上方法即可
    

AysncTask實現的基本原理

AysncTask是一個抽象類:下列程式碼片段摘自AsyncTask原始碼(Android-23)

  1. 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,
    
    }
    
  2. 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;
    }
    
  3. 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);
            }
        }
    }
    
  4. 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

參考:

Android非同步訊息處理機制完全解析,帶你從原始碼的角度徹底理解

相關文章