Android異步消息處理機(jī)制

Android異步消息處理機(jī)制
android常用異步框架分為handler官册、AsyncTask覆糟、handlerThread朴乖、IntentService。

什么是handler

android消息機(jī)制的上層接口劝贸,通過(guò)發(fā)送和處理Message和Runnable對(duì)象來(lái)關(guān)聯(lián)相對(duì)應(yīng)線程的MessageQueue。

1. 可以讓對(duì)應(yīng)的Message和Runnable在未來(lái)的某個(gè)時(shí)間點(diǎn)進(jìn)行相應(yīng)的處理逗宁。
2. 讓自己想要處理的耗時(shí)操作放在子線程映九,讓更新ui放在主線程。

handler的使用方法

  • post(runnable)
  • sendMessage(msg)注意 msg的獲取最好用Message.obtain()獲取瞎颗。底層采用對(duì)象池減少內(nèi)存消耗件甥。

handler機(jī)制原理

-w687

Looper是每一個(gè)線程所獨(dú)有的,通過(guò)loop()方法讀取下面的MessageQueue哼拔,讀到消息后把消息發(fā)送給Handler來(lái)處理.MessageQueue是一個(gè)消息隊(duì)列引有,是FIFO。創(chuàng)建Loop的時(shí)候就創(chuàng)建了MessageQueue倦逐,2者就關(guān)聯(lián)在了一起譬正。Message就是消息對(duì)象。Handler就是處理消息和發(fā)送消息檬姥。 但是它只能發(fā)送跟它相關(guān)聯(lián)的MessageQueue曾我,而MessageQueue又跟looper相關(guān)聯(lián),所以handler發(fā)送消息必須有一個(gè)維護(hù)它的Looper健民。

源碼分析:

-w540
  1. 首先我們看下handler抒巢,MessageQueue,Looper是怎么關(guān)聯(lián)在一起的秉犹?
 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的構(gòu)造方法中創(chuàng)建looper蛉谜,然后根據(jù)looper的成員變量創(chuàng)建MessageQueue這樣handler就和MessageQueue關(guān)聯(lián)在了一起,而MessageQueue是通過(guò)Looper來(lái)管理的崇堵,從而三者關(guān)聯(lián)在了一起;
我們點(diǎn)進(jìn)去看下Looper.myLooper方法型诚,發(fā)現(xiàn)是從sThreadLocal獲取的,ThreadLocal是每個(gè)線程都獨(dú)有的一份數(shù)據(jù)鸳劳,所以每個(gè)handler獲取的都對(duì)應(yīng)自己線程的Looper狰贯。

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

接著往下看發(fā)現(xiàn)sThreadLocal實(shí)在prepare()中賦值的,這樣保證了每個(gè)線程的唯一性棍辕。

   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();
        }
    }
    

其實(shí)就是創(chuàng)建了一個(gè)for死循環(huán)还绘,逐個(gè)取出msg楚昭,通過(guò)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,其實(shí)就是中轉(zhuǎn)器拍顷,根據(jù)不同的條件不同的處理方式抚太。

-w817
  1. 總結(jié)
    Looper開(kāi)啟了一個(gè)循環(huán),不斷的從MessageQueue獲取消息,把MessageQueue頭部獲取的msg尿贫,已msg.target形式交由handler.handleMessage中處理电媳。 最終處理完成后,還是會(huì)返回到loop中庆亡,不斷的處理匾乓。

handler內(nèi)存泄露及解決方案

非靜態(tài)內(nèi)部類持有外部類的引用,handler里面有可能里面有耗時(shí)或者延時(shí)操作又谋,當(dāng)activity銷毀后由于handler持有activity拼缝,導(dǎo)致activity無(wú)法釋放,造成了內(nèi)存泄露彰亥。
解決方案:handler設(shè)置為靜態(tài)內(nèi)部類咧七,在activity的onDestroy中調(diào)用handler.removeCallBack()。注意如果在靜態(tài)內(nèi)部類中任斋,如果要使用activity继阻,一定用弱引用,而不是直接使用activity废酷。

AsyncTask

AsyncTask可以用來(lái)處理一些后臺(tái)較耗時(shí)的任務(wù)瘟檩,查看源碼發(fā)現(xiàn)其內(nèi)部就是一個(gè)Handler和線程池的封裝,可以幫助我們處理耗時(shí)任務(wù)的同時(shí)去更新UI澈蟆。

AsyncTask的使用
  • AsyncTask抽象類的3參數(shù)
public abstract class AsyncTask<Params, Progress, Result> {
......
}
 Params 啟動(dòng)任務(wù)執(zhí)行的輸入?yún)?shù)芒帕,比如下載URL
 Progress 后臺(tái)任務(wù)執(zhí)行的百分比,比如下載進(jìn)度
 Result 后臺(tái)執(zhí)行任務(wù)最終返回的結(jié)果丰介,比如下載結(jié)果
  • 子類可以實(shí)現(xiàn)的函數(shù)
 onPreExecute():(運(yùn)行在UI線程中) (非必須方法)

在任務(wù)執(zhí)行前調(diào)用背蟆,通常用來(lái)做一些準(zhǔn)備操作,比如下載文件前哮幢,在顯示一個(gè)進(jìn)度條等带膀。

doInBackground(Params... params):(運(yùn)行在子線程中)(必須實(shí)現(xiàn))

可以在此方法中處理比較耗時(shí)的操作,比如下載文件等等橙垢。

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

此函數(shù)異步任務(wù)執(zhí)行時(shí)垛叨,回調(diào)給UI主線程的進(jìn)度,比如上傳或者下載進(jìn)度柜某。

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

此函數(shù)代表任務(wù)執(zhí)行結(jié)束了嗽元,回調(diào)給UI主線程的結(jié)果。比如下載結(jié)果喂击。

onCancelled(Result result)onCancelled()任務(wù)關(guān)閉的函數(shù)
  • 常用函數(shù)
cancel (boolean mayInterruptIfRunning)取消執(zhí)行任務(wù)

execute (Params... params)用指定的參數(shù)來(lái)執(zhí)行此任務(wù)

executeOnExecutor(Executor exec,Params... params)在指定的Executor中執(zhí)行任務(wù)剂癌。

getStatus ()獲得任務(wù)的當(dāng))前狀態(tài)  PENDING(等待執(zhí)行)、RUNNING(正在運(yùn)行)翰绊、FINISHED(運(yùn)行完成)

isCancelled ()在任務(wù)正常結(jié)束之前能成功取消任務(wù)則返回true佩谷,否則返回false

AsyncTask內(nèi)部原理簡(jiǎn)介

-w916

標(biāo)記1.內(nèi)部也實(shí)例化了帶Looper的Handler旁壮,為UI更新做好準(zhǔn)備
標(biāo)記2異步執(zhí)行的地方,WorkRunnable可以理解為一個(gè)工作線程,同時(shí)自身實(shí)現(xiàn)了Callable接口谐檀,Call方法中包含了AyncTask最終要執(zhí)行的任務(wù)抡谐,并返回結(jié)果。
postResult就是將Result攜帶的信息桐猬,發(fā)送給指定target的Handler麦撵。就是我們前面所說(shuō)的線程池+handler實(shí)現(xiàn)的。

AsyncTask注意事項(xiàng)

  1. 內(nèi)存泄露 非靜態(tài)內(nèi)部類持有外部類的引用溃肪;如果Activity已經(jīng)被銷毀厦坛,AsyncTask的后臺(tái)線程還在執(zhí)行,它將繼續(xù)在內(nèi)存里保留這個(gè)引用乍惊,導(dǎo)致Activity無(wú)法被回收杜秸,引起內(nèi)存泄露。
    解決方案:把AsyncTask設(shè)置為靜態(tài)內(nèi)部類润绎,里面持有activity的弱引用撬碟。并在onDestroy中cancel()任務(wù)。

  2. AsyncTask不與任何組件綁定生命周期
    解決方案:在Activity/Fragment的onDestory()調(diào)用 cancel(true)莉撇;

  3. 串行或者并行的執(zhí)行異步任務(wù)
    當(dāng)想要串行執(zhí)行時(shí)呢蛤,直接執(zhí)行execute()方法,如果需要并行執(zhí)行棍郎,則要執(zhí)行executeOnExecutor(Executor)其障。建議串行,保證線程池的穩(wěn)定涂佃,AsyncTask一般做不了高并發(fā)励翼,太耗時(shí)的操作。

  4. 結(jié)果丟失
    屏幕旋轉(zhuǎn)或Activity在后臺(tái)被系統(tǒng)殺掉等情況會(huì)導(dǎo)致Activity的重新創(chuàng)建辜荠,之前運(yùn)行的AsyncTask會(huì)持有一個(gè)之前Activity的引用汽抚,這個(gè)引用已經(jīng)無(wú)效,這時(shí)調(diào)用onPostExecute()再去更新界面將不再生效伯病。

handlerThread

開(kāi)啟子線程進(jìn)行耗時(shí)操作造烁,多次創(chuàng)建和銷毀線程是很消耗系統(tǒng)資源的,google為我們封裝了handlerThread午笛。它是有handler +thread +looper構(gòu)成的惭蟋。它是通過(guò)獲取handlerThread的 looper對(duì)象傳遞給handler,然后在handleMessage執(zhí)行異步任務(wù)药磺。
優(yōu)點(diǎn)是不會(huì)阻塞UI線程告组,缺點(diǎn)是串行執(zhí)行,處理效率較低与涡。

handlerThread使用

  1. 初始化HandlerThread然后調(diào)用其start方法
mHandlerThread = new HandlerThread("mHandlerThread");//這里的mHandlerThread其實(shí)就是線程的名字
 mHandlerThread.start();

初始化Handler進(jìn)行惹谐,傳入mHandlerThread中的Looper,這樣就保證了Handler運(yùn)行在子線程驼卖,loop輪訓(xùn)氨肌,不同的交給handleMessage處理消息。

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

3.使用工作線程Handler向工作線程的消息隊(duì)列發(fā)送消息

 Message msg = Message.obtain();
  msg.what = 2; //消息的標(biāo)識(shí)
  msg.obj = "B"; // 消息的存放
  // b. 通過(guò)Handler發(fā)送消息到其綁定的消息隊(duì)列
  workHandler.sendMessage(msg);

4.結(jié)束線程酌畜,即停止線程的消息循環(huán)
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在開(kāi)啟start后會(huì)執(zhí)行run方法,在這里會(huì)準(zhǔn)備Looper并開(kāi)啟輪訓(xùn)桥胞。
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    
    //還記得上面的Handler的創(chuàng)建嗎恳守?
      public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

  //可以發(fā)送消息的異步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;
    }
}

注意點(diǎn)1:這里用了java線程知識(shí),wait();和notifyAll()贩虾,為什么這么用呢
首先催烘,Handler的創(chuàng)建一般是在主線程完成的,創(chuàng)建的時(shí)候獲取HandlerThread.getLooper(),而Looper的創(chuàng)建是在子線程中創(chuàng)建的缎罢,這里就有線程同步問(wèn)題了伊群,比如我們調(diào)用getLooper()的時(shí)候HandlerThread中run()方法還沒(méi)執(zhí)行完,mLooper變量還未賦值策精,此時(shí)就執(zhí)行了wait()等待邏輯舰始,一直等到run()方法中mLooper被賦值,之后立即執(zhí)行notifyAll()咽袜,然后getLooper()就可以正確返回mLooper了丸卷。
注意點(diǎn)2:quitSafely()和quit()有什么區(qū)別?

區(qū)別在于該方法移除消息询刹、退出循環(huán)時(shí)是否在意當(dāng)前隊(duì)列是否正在處理消息谜嫉,無(wú)論是否正在執(zhí)行此時(shí)quit()都會(huì)立即退出該循環(huán)。 若是正在處理quitSafely凹联,則等待該消息處理處理完畢再退出該循環(huán)骄恶。

IntentService

一個(gè)封裝了HandlerThread和Handler的特殊Service,可以多次啟動(dòng)匕垫,每個(gè)耗時(shí)操作都會(huì)以工作隊(duì)列的方式串行在IntentService的onHandleIntent回調(diào)方法中執(zhí)行僧鲁。任務(wù)執(zhí)行完后會(huì)自動(dòng)停止。

IntentService 使用

1.自定義LocalIntentService繼承自IntentService

public class MyIntentService extends IntentService{
 public LocalIntentService(String name) {
        super(name);
    }
}

2.實(shí)現(xiàn)onHandleIntent(), 是耗時(shí)操作象泵。

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

IntentService 源碼分析

  1. IntentService繼承Service寞秃,首先我們看下onCreate(),可以看到里面創(chuàng)建了HandlerThread偶惠,所以可以耗時(shí)操作春寿。
 //IntentService第一次啟動(dòng)調(diào)用
        public void onCreate() {
            super.onCreate();
                       HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
            thread.start();
            mServiceLooper = thread.getLooper();
            mServiceHandler = new ServiceHandler(mServiceLooper);
        }

2.多次啟動(dòng)會(huì)走startService()->onStartCommand()->onStart()通過(guò)HandlerThread的handler去發(fā)送消息。

@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順序取出任務(wù)執(zhí)行忽孽,會(huì)調(diào)用ServiceHandler的
handleMessage()->onHandleIntent()绑改。任務(wù)執(zhí)行完畢后stopSelf(startId)停止Service谢床。任務(wù)結(jié)束后,在onDestory()中會(huì)退出HandlerThread中Looper的循環(huán)厘线。

//ServiceHandler接收并處理onStart()方法中發(fā)送的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); //會(huì)判斷啟動(dòng)服務(wù)次數(shù)是否與startId相等
            }
        }

        public void onDestroy() {
            mServiceLooper.quit();
        }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末识腿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子造壮,更是在濱河造成了極大的恐慌渡讼,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耳璧,死亡現(xiàn)場(chǎng)離奇詭異成箫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)旨枯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)蹬昌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人攀隔,你說(shuō)我怎么就攤上這事凳厢。” “怎么了竞慢?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵先紫,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我筹煮,道長(zhǎng)遮精,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任败潦,我火速辦了婚禮本冲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘劫扒。我一直安慰自己檬洞,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布沟饥。 她就那樣靜靜地躺著添怔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贤旷。 梳的紋絲不亂的頭發(fā)上广料,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音幼驶,去河邊找鬼艾杏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛盅藻,可吹牛的內(nèi)容都是我干的购桑。 我是一名探鬼主播畅铭,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼勃蜘!你這毒婦竟也來(lái)了硕噩?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤元旬,失蹤者是張志新(化名)和其女友劉穎榴徐,沒(méi)想到半個(gè)月后守问,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體匀归,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年耗帕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了穆端。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡仿便,死狀恐怖体啰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嗽仪,我是刑警寧澤荒勇,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站闻坚,受9級(jí)特大地震影響沽翔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窿凤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一仅偎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雳殊,春花似錦橘沥、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仓洼,卻和暖如春箫措,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背衬潦。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工斤蔓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人镀岛。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓弦牡,卻偏偏與公主長(zhǎng)得像友驮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驾锰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容