Android異步消息處理機制

Android異步消息經常使用匯總

android經常使用異步框架分爲handler、AsyncTask、handlerThread、IntentService。java

什麼是handler

android消息機制的上層接口,經過發送和處理Message和Runnable對象來關聯相對應線程的MessageQueue。android

1. 可讓對應的Message和Runnable在將來的某個時間點進行相應的處理。
    1. 讓本身想要處理的耗時操做放在子線程,讓更新ui放在主線程。
複製代碼

handler的使用方法

  • post(runnable)
  • sendMessage(msg)注意 msg的獲取最好用Message.obtain()獲取。底層採用對象池減小內存消耗。

handler機制原理

-w687
Looper是每個線程所獨有的,經過loop()方法讀取下面的MessageQueue,讀到消息後把消息發送給Handler來處理.MessageQueue是一個消息隊列,是FIFO。建立Loop的時候就建立了MessageQueue,2者就關聯在了一塊兒。Message就是消息對象。Handler就是處理消息和發送消息。 可是它只能發送跟它相關聯的MessageQueue,而MessageQueue又跟looper相關聯,因此handler發送消息必須有一個維護它的Looper。

源碼分析:

-w540

  1. 首先咱們看下handler,MessageQueue,Looper是怎麼關聯在一塊兒的?
public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
複製代碼

在handler的構造方法中建立looper,而後根據looper的成員變量建立MessageQueue這樣handler就和MessageQueue關聯在了一塊兒,而MessageQueue是經過Looper來管理的,從而三者關聯在了一塊兒; 咱們點進去看下Looper.myLooper方法,發現是從sThreadLocal獲取的,ThreadLocal是每一個線程都獨有的一份數據,因此每一個handler獲取的都對應本身線程的Looper。bash

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製代碼

接着往下看發現sThreadLocal實在prepare()中賦值的,這樣保證了每一個線程的惟一性。markdown

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }
複製代碼
  1. 接着咱們看下looper是怎麼獲取MessageQueue消息的。
public static void loop() {
        ...省略部分不重要代碼
        for (;;) {
            Message msg = queue.next(); // might block
                     try {
                msg.target.dispatchMessage(msg);
              
            }             msg.recycleUnchecked();
        }
    }
    
複製代碼

其實就是建立了一個for死循環,逐個取出msg,經過Handler(msg.target)dispatchMessage(msg)併發

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

dispatchMessage,其實就是中轉器,根據不一樣的條件不一樣的處理方式。框架

-w817
3. 總結 Looper開啓了一個循環,不斷的從MessageQueue獲取消息,把MessageQueue頭部獲取的msg,已msg.target形式交由handler.handleMessage中處理。 最終處理完成後,仍是會返回到loop中,不斷的處理。

handler內存泄露及解決方案

非靜態內部類持有外部類的引用,handler裏面有可能裏面有耗時或者延時操做,當activity銷燬後因爲handler持有activity,致使activity沒法釋放,形成了內存泄露。 解決方案:handler設置爲靜態內部類,在activity的onDestroy中調用handler.removeCallBack()。注意若是在靜態內部類中,若是要使用activity,必定用弱引用,而不是直接使用activity。異步

AsyncTask

AsyncTask能夠用來處理一些後臺較耗時的任務,查看源碼發現其內部就是一個Handler和線程池的封裝,能夠幫助咱們處理耗時任務的同時去更新UI。async

AsyncTask的使用
  • AsyncTask抽象類的3參數
public abstract class AsyncTask<Params, Progress, Result> {
......
}
 Params 啓動任務執行的輸入參數,好比下載URL
 Progress 後臺任務執行的百分比,好比下載進度
 Result 後臺執行任務最終返回的結果,好比下載結果
複製代碼
  • 子類能夠實現的函數
onPreExecute():(運行在UI線程中) (非必須方法)

在任務執行前調用,一般用來作一些準備操做,好比下載文件前,在顯示一個進度條等。

doInBackground(Params... params):(運行在子線程中)(必須實現)

能夠在此方法中處理比較耗時的操做,好比下載文件等等。

onProgressUpdate(Progress... values) (運行在UI線程中) (非必須方法)

此函數異步任務執行時,回調給UI主線程的進度,好比上傳或者下載進度。

onPostExecute(Result result)(運行在UI線程中) (非必須方法)

此函數表明任務執行結束了,回調給UI主線程的結果。好比下載結果。

onCancelled(Result result)onCancelled()任務關閉的函數
複製代碼
  • 經常使用函數
cancel (boolean mayInterruptIfRunning)取消執行任務

execute (Params... params)用指定的參數來執行此任務

executeOnExecutor(Executor exec,Params... params)在指定的Executor中執行任務。

getStatus ()得到任務的當)前狀態  PENDING(等待執行)、RUNNING(正在運行)、FINISHED(運行完成)

isCancelled ()在任務正常結束以前能成功取消任務則返回true,不然返回false

複製代碼

AsyncTask內部原理簡介

-w916
標記1.內部也實例化了帶Looper的Handler,爲UI更新作好準備 標記2異步執行的地方,WorkRunnable能夠理解爲一個工做線程,同時自身實現了Callable接口,Call方法中包含了AyncTask最終要執行的任務,並返回結果。 postResult就是將Result攜帶的信息,發送給指定target的Handler。就是咱們前面所說的線程池+handler實現的。

AsyncTask注意事項

  1. 內存泄露 非靜態內部類持有外部類的引用;若是Activity已經被銷燬,AsyncTask的後臺線程還在執行,它將繼續在內存裏保留這個引用,致使Activity沒法被回收,引發內存泄露。 解決方案:把AsyncTask設置爲靜態內部類,裏面持有activity的弱引用。並在onDestroy中cancel()任務。ide

  2. AsyncTask不與任何組件綁定生命週期 解決方案:在Activity/Fragment的onDestory()調用 cancel(true);函數

  3. 串行或者並行的執行異步任務 當想要串行執行時,直接執行execute()方法,若是須要並行執行,則要執行executeOnExecutor(Executor)。建議串行,保證線程池的穩定,AsyncTask通常作不了高併發,太耗時的操做。

  4. 結果丟失 屏幕旋轉或Activity在後臺被系統殺掉等狀況會致使Activity的從新建立,以前運行的AsyncTask會持有一個以前Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將再也不生效。

handlerThread

開啓子線程進行耗時操做,屢次建立和銷燬線程是很消耗系統資源的,google爲咱們封裝了handlerThread。它是有handler +thread +looper構成的。它是經過獲取handlerThread的 looper對象傳遞給handler,而後在handleMessage執行異步任務。 優勢是不會阻塞UI線程,缺點是串行執行,處理效率較低。

handlerThread使用

  1. 初始化HandlerThread而後調用其start方法
mHandlerThread = new HandlerThread("mHandlerThread");//這裏的mHandlerThread其實就是線程的名字
 mHandlerThread.start();
複製代碼

初始化Handler進行,傳入mHandlerThread中的Looper,這樣就保證了Handler運行在子線程,loop輪訓,不一樣的交給handleMessage處理消息。

workHandler = new Handler(mHandlerThread.getLooper()) {
 
             @Override
             public void handleMessage(Message msg) {
                 ...//消息處理
                }             }
         };
複製代碼

3.使用工做線程Handler向工做線程的消息隊列發送消息

Message msg = Message.obtain();
  msg.what = 2; //消息的標識
  msg.obj = "B"; // 消息的存放
  // b. 經過Handler發送消息到其綁定的消息隊列
  workHandler.sendMessage(msg);
複製代碼

4.結束線程,即中止線程的消息循環 mHandlerThread.quit();

handlerThread源碼分析

//1.Thread的子類
public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    private @Nullable Handler mHandler;

//還記得上面的HandlerThread使用的初始化嗎?
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    
    //thread在開啓start後會執行run方法,在這裏會準備Looper並開啓輪訓。
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    //還記得上面的Handler的建立嗎?
      public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

  //能夠發送消息的異步Handler
    @NonNull
    public Handler getThreadHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

        public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

       public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

  
    public int getThreadId() {
        return mTid;
    }
}

複製代碼

注意點1:這裏用了java線程知識,wait();和notifyAll(),爲何這麼用呢 首先,Handler的建立通常是在主線程完成的,建立的時候獲取HandlerThread.getLooper(),而Looper的建立是在子線程中建立的,這裏就有線程同步問題了,好比咱們調用getLooper()的時候HandlerThread中run()方法還沒執行完,mLooper變量還未賦值,此時就執行了wait()等待邏輯,一直等到run()方法中mLooper被賦值,以後當即執行notifyAll(),而後getLooper()就能夠正確返回mLooper了。 注意點2:quitSafely()和quit()有什麼區別?

區別在於該方法移除消息、退出循環時是否在乎當前隊列是否正在處理消息,不管是否正在執行此時quit()都會當即退出該循環。 如果正在處理quitSafely,則等待該消息處理處理完畢再退出該循環。

IntentService

一個封裝了HandlerThread和Handler的特殊Service,能夠屢次啓動,每一個耗時操做都會以工做隊列的方式串行在IntentService的onHandleIntent回調方法中執行。任務執行完後會自動中止。

IntentService 使用

1.自定義LocalIntentService繼承自IntentService

public class MyIntentService extends IntentService{
 public LocalIntentService(String name) {
        super(name);
    }
}
複製代碼

2.實現onHandleIntent(), 是耗時操做。

@Override
    protected void onHandleIntent(@Nullable Intent intent) {
        String action = intent.getStringExtra("task_action");
      //dosomething
    }
複製代碼

IntentService 源碼分析

  1. IntentService繼承Service,首先咱們看下onCreate(),能夠看到裏面建立了HandlerThread,因此能夠耗時操做。
//IntentService第一次啓動調用
        public void onCreate() {
            super.onCreate();
                       HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
            thread.start();
            mServiceLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mServiceLooper);
        }
複製代碼

2.屢次啓動會走startService()->onStartCommand()->onStart()經過HandlerThread的handler去發送消息。

@Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

 @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
複製代碼

3.IntentService的SerivceHandler

HandlerThread順序取出任務執行,會調用ServiceHandler的 handleMessage()->onHandleIntent()。任務執行完畢後stopSelf(startId)中止Service。任務結束後,在onDestory()中會退出HandlerThread中Looper的循環。

//ServiceHandler接收並處理onStart()方法中發送的Msg
        private final class ServiceHandler extends Handler {
            public ServiceHandler(Looper looper) {
                super(looper);
            }
            @Override
            public void handleMessage(Message msg) {
                onHandleIntent((Intent)msg.obj);
                stopSelf(msg.arg1); //會判斷啓動服務次數是否與startId相等
            }
        }

        public void onDestroy() {
            mServiceLooper.quit();
        }
複製代碼
相關文章
相關標籤/搜索