Android面試攻略(2)——異步消息處理機(jī)制

系列文章
Android面試攻略(1)——Android基礎(chǔ)
Android面試攻略(2)——異步消息處理機(jī)制
Android面試攻略(3)——View相關(guān)


本篇文章主要涉及Handler、AsyncTask、HandlerThread、IntentService

Handler

一、什么是Handler

當(dāng)程序第一次啟動(dòng)的時(shí)候,Android會(huì)同時(shí)啟動(dòng)一條主線程( Main Thread)來負(fù)責(zé)處理與UI相關(guān)的事件,我們叫做UI線程。
Android的UI操作并不是線程安全的(出于性能優(yōu)化考慮),意味著如果多個(gè)線程并發(fā)操作UI線程,可能導(dǎo)致線程安全問題。

為了解決Android應(yīng)用多線程問題—Android平臺(tái)只允許UI線程修改Activity里的UI組建,就會(huì)導(dǎo)致新啟動(dòng)的線程無法改變界面組建的屬性值。

簡(jiǎn)單的說:當(dāng)主線程隊(duì)列處理一個(gè)消息超過5秒,android 就會(huì)拋出一個(gè) ANP(無響應(yīng))的異常,所以,我們需要把一些要處理比較長(zhǎng)的消息,放在一個(gè)單獨(dú)線程里面處理,把處理以后的結(jié)果,返回給主線程運(yùn)行,就需要用的Handler來進(jìn)行線程建的通信。

handler通過發(fā)送和處理Message和Runnable對(duì)象來關(guān)聯(lián)相對(duì)應(yīng)的線程的MessageQueue。可以讓對(duì)應(yīng)的Message和Runnable在未來的某個(gè)時(shí)間點(diǎn)進(jìn)行相應(yīng)的處理(延時(shí)處理:postAtTime()、postDelayed())。讓自己想要處理的耗時(shí)操作放在子線程,讓更新ui的操作放在主線程。


二、Handler的基本使用

  • 首先聲明一個(gè)Handler,復(fù)寫其handleMessage方法
Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if(msg.what == 1)
                mTextView.setText("更新UI");
        }
    };
  • 在子線程發(fā)送消息通知Handler執(zhí)行操作
new Thread() {
            @Override
            public void run() {
                super.run();
                //do something ...

                //子線程中通過handler發(fā)送消息給handler接收,由handler去更新TextView的值
                Message msg = Message.obtain();
                msg.what = 1;
                msg.obj = "需要發(fā)送的數(shù)據(jù)";
                mHandler.sendMessage(msg);

            }
        }.start();

三、Handler原理分析

首先我們來想看下Handler的構(gòu)造方法

public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
public Handler(Looper looper, Callback callback);

前兩個(gè)構(gòu)造方法是沒有傳遞Looper對(duì)象的,這兩個(gè)方法內(nèi)部會(huì)調(diào)用另一個(gè)構(gòu)造方法

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

可以看到,這個(gè)構(gòu)造方法通過Looper.myLooper()獲取了保存在該線程下的mLooper實(shí)例對(duì)象,再通過mLopper.mQueue獲取了MessageQueue。也就是每個(gè)Handler 對(duì)應(yīng)一個(gè)Looper對(duì)象,產(chǎn)生一個(gè)MessageQueue。那其實(shí)我們并沒有做過Looper的保存操作,為什么這里可以拿到保存的Looper呢?其實(shí),我們這里獲取到的Looper,是主線程創(chuàng)建的時(shí)候,實(shí)現(xiàn)了Looper的兩個(gè)重要的方法。我們來看下ActivtyThread.class中的源碼:

public static final void main(String[] args) {

        // -----

        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

       // ----
    }

首先我們來看Looper.prepareMainLooper(),其內(nèi)部調(diào)用了prepare(false)

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

該方法保證了Looper的唯一性且會(huì)調(diào)用Looper構(gòu)造函數(shù)同時(shí)實(shí)例化出MessageQueue和當(dāng)前thread.

public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

prepare()方法中通過ThreadLocal對(duì)象實(shí)現(xiàn)Looper實(shí)例與線程的綁定,即保存當(dāng)前的Looper實(shí)例到ThreadLocal當(dāng)中,Handler中通過Looper.myLooper()將Looper實(shí)例從ThreadLocal中取出。

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

Handler、Looper、MessageQueue都有了,下面該發(fā)送消息了,我們看看Handle發(fā)送消息的幾個(gè)方法源碼

public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0);
    }

public final boolean sendEmptyMessage(int what) {
        return sendEmptyMessageDelayed(what, 0);
    }

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

可以看到,它們最終都調(diào)用了sendMessageAtTime()方法,然后返回了enqueueMessage(),我們來看看這個(gè)方法的源碼

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

在該方法中有兩件事需要注意:

  1. msg.target = this:該代碼將Message的target綁定為當(dāng)前的Handler
  2. queue.enqueueMessage :變量queue表示的是Handler所綁定的消息隊(duì)列MessageQueue,通過調(diào)用queue.enqueueMessage(msg, uptimeMillis)我們將Message放入到消息隊(duì)列中。

通過下圖可以看到完整的方法調(diào)用順序:
圖片來自網(wǎng)絡(luò)

消息放入消息隊(duì)列之后,怎么處理呢?我們最后來看下loop()方法

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

首先looper對(duì)象不能為空,就是說loop()方法調(diào)用必須在prepare()方法的后面。
Looper一直在不斷的從消息隊(duì)列中通過MessageQueue的next方法獲取Message,然后通過代碼msg.target.dispatchMessage(msg)讓該msg所綁定的Handler(Message.target)執(zhí)行dispatchMessage方法以實(shí)現(xiàn)對(duì)Message的處理。
Handler的dispatchMessage的源碼如下:

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

我們可以看到Handler提供了三種途徑處理Message,而且處理有前后優(yōu)先級(jí)之分:首先嘗試讓postXXX中傳遞的Runnable執(zhí)行,其次嘗試讓Handler構(gòu)造函數(shù)中傳入的Callback的handleMessage方法處理,最后才是讓Handler自身的handleMessage方法處理Message。

Callback參數(shù):Callback是Handler中的內(nèi)部接口,需要實(shí)現(xiàn)其內(nèi)部的handleMessage方法,Callback代碼如下:

public interface Callback {
        public boolean handleMessage(Message msg);
    }

Handler.Callback是用來處理Message的一種手段,如果沒有傳遞該參數(shù),那么就應(yīng)該重寫Handler的handleMessage方法,也就是說為了使得Handler能夠處理Message,我們有兩種辦法:

  1. 向Hanlder的構(gòu)造函數(shù)傳入一個(gè)Handler.Callback對(duì)象,并實(shí)現(xiàn)Handler.Callback的handleMessage方法。
  2. 無需向Hanlder的構(gòu)造函數(shù)傳入Handler.Callback對(duì)象,但是需要重寫Handler本身的handleMessage方法 。
    也就是說無論哪種方式,我們都得通過某種方式實(shí)現(xiàn)handleMessage方法,這點(diǎn)與Java中對(duì)Thread的設(shè)計(jì)有異曲同工之處。

四、在子線程中使用Handler

我們知道,在子線程中使用Handler會(huì)出錯(cuò),為什么?
Handler本質(zhì)是從當(dāng)前的線程中獲取到Looper來監(jiān)聽和操作MessageQueue,當(dāng)其他線程執(zhí)行完成后回調(diào)當(dāng)前線程。而子線程中并沒有預(yù)先設(shè)置Looper,所以導(dǎo)致Handler調(diào)用Looper.myLooper()時(shí)找不到Looper對(duì)象導(dǎo)致拋出異常。所以,我們?nèi)绻朐谧泳€程中使用,必須在聲明Handler之前,調(diào)用Looper.prepare(),然后調(diào)用Looper的loop()方法來啟動(dòng)Looper讓消息隊(duì)列轉(zhuǎn)動(dòng)起來。

class DownLoadThread implements Runnable {  
        public Handler myHandler;  
        // 實(shí)現(xiàn)Runnable接口的線程體 
        @Override  
        public void run() {  
            
         /*①、調(diào)用Looper的prepare()方法為當(dāng)前線程創(chuàng)建Looper對(duì)象并,
          創(chuàng)建Looper對(duì)象時(shí),它的構(gòu)造器會(huì)自動(dòng)的創(chuàng)建相對(duì)應(yīng)的MessageQueue*/
            Looper.prepare();  
            
            /*.②、創(chuàng)建Handler子類的實(shí)例,重寫HandleMessage()方法,該方法處理除當(dāng)前線程以外線程的消息*/
             myHandler = new Handler() {  
                @Override  
                public void handleMessage(Message msg) {  
                    String ms = "";  
                    if (msg.what == 0x777) {  
                     
                    }  
                }  
            };  
            //③、調(diào)用Looper的loop()方法來啟動(dòng)Looper讓消息隊(duì)列轉(zhuǎn)動(dòng)起來
            Looper.loop();  
        }
    }

五、Handler引起的內(nèi)存泄漏以及解決辦法

原因:靜態(tài)內(nèi)部類持有外部類的匿名引用,導(dǎo)致外部Activity無法釋放。
解決辦法:1. Handler內(nèi)部持有外部Activity的弱引用。2. 把Handler改為靜態(tài)內(nèi)部類。3. 在Activity的onDestroy()方法中調(diào)用mHandler.removeCallback()。


AsyncTask

一、什么是AsyncTask

AsyncTask本質(zhì)上就是一個(gè)封裝了線程池和Handler的異步框架。適用于簡(jiǎn)單的異步處理。


二、基本使用

繼承AsyncTask類實(shí)現(xiàn)自己的類,并重寫其中的方法。

三個(gè)參數(shù)

public abstract class AsyncTask<Params, Progress, Result> {}

Params: 輸入?yún)?shù),對(duì)應(yīng)excute()方法中傳遞的參數(shù)。如果不需要傳遞參數(shù),則直接設(shè)為void即可。

Progress:后臺(tái)任務(wù)執(zhí)行的百分比

Result:返回值類型,和doInBackground()方法的返回值類型保持一致。

五個(gè)方法

protected abstract Result doInBackground(Params... params);

protected void onPostExecute(Result result) {
    }

protected void onProgressUpdate(Progress... values) {
    }

protected void onPreExecute() {
    }

protected void onCancelled() {
    }

最少要重寫以下這兩個(gè)方法:

  • doInBackground(Params…)
      在子線程(其他方法都在主線程執(zhí)行)中執(zhí)行比較耗時(shí)的操作,不能更新UI,可以在該方法中調(diào)用publishProgress(Progress…)來更新任務(wù)的進(jìn)度。Progress方法是AsycTask中一個(gè)final方法只能調(diào)用不能重寫。

  • onPostExecute(Result)
      使用在doInBackground 得到的結(jié)果處理操作UI, 在主線程執(zhí)行,任務(wù)執(zhí)行的結(jié)果作為此方法的參數(shù)返回。    有時(shí)根據(jù)需求還要實(shí)現(xiàn)以下三個(gè)方法:

  • onProgressUpdate(Progress…)
      可以使用進(jìn)度條增加用戶體驗(yàn)度。 此方法在主線程執(zhí)行,用于顯示任務(wù)執(zhí)行的進(jìn)度。

  • onPreExecute()
      這里是最終用戶調(diào)用Excute時(shí)的接口,當(dāng)任務(wù)執(zhí)行之前開始調(diào)用此方法,可以在這里顯示進(jìn)度對(duì)話框。

  • onCancelled()
      用戶調(diào)用取消時(shí),要做的操作

在主線程中申明該類的對(duì)象,調(diào)用對(duì)象的execute()函數(shù)開始執(zhí)行。

MyAsyncTask t = new MyAsyncTask();
t.execute();

三、AsyncTask的機(jī)制原理

  1. AsyncTask的本質(zhì)是一個(gè)靜態(tài)的線程池,AsyncTask派生出的子類可以實(shí)現(xiàn)不同的異步任務(wù),這些任務(wù)都是提交到靜態(tài)的線程池中執(zhí)行。
  2. 線程池中的工作線程執(zhí)行doInBackground方法執(zhí)行異步任務(wù)。
  3. 當(dāng)任務(wù)狀態(tài)改變后,工作線程會(huì)向UI線程發(fā)送消息,AsyncTask內(nèi)部的InternalHandler響應(yīng)這些消息,并調(diào)用相關(guān)的回調(diào)函數(shù)。

四、AsyncTask的注意事項(xiàng)

  1. AsyncTask不適合特別耗時(shí)的任務(wù)
    AsyncTask的生命周期和Activity的生命周期不同步,Activity銷毀了但是AsyncTask中的任務(wù)還是會(huì)繼續(xù)執(zhí)行完畢,一個(gè)最典型的例子就是Activity的橫豎屏切換,AsyncTask中引用的Activity不是當(dāng)前的Activity,onPostExecute()中執(zhí)行的仍然是上一個(gè)Activity。還有一個(gè)原因是因?yàn)锳syncTask在執(zhí)行長(zhǎng)時(shí)間的耗時(shí)任務(wù)時(shí)也會(huì)持有一個(gè)Activity對(duì)象,即使這個(gè)Activity已經(jīng)不可見了,Android也無法對(duì)這個(gè)Activity進(jìn)行回收,導(dǎo)致內(nèi)存泄露

  2. AsyncTask只能在主線程中創(chuàng)建以及使用
    AsyncTask被用于執(zhí)行異步任務(wù),然后更新UI,所以最后的onPostExecute()方法執(zhí)行在創(chuàng)建該AsyncTask對(duì)象的線程中,如果不在主線程中創(chuàng)建以及使用,就達(dá)不到更新UI的目的。

  3. 一個(gè)AsyncTask對(duì)象只能執(zhí)行一次
    一個(gè)AsyncTask對(duì)象只能執(zhí)行一次,即只能調(diào)用一次execute方法,否則會(huì)報(bào)運(yùn)行時(shí)異常AsyncTask。這是源碼注釋中寫的,但是我查閱資料也沒有找到什么具體原因。本人學(xué)藝也不精...也就知道了不能重復(fù)對(duì)一個(gè)AsyncTask對(duì)象調(diào)用execute()方法。

  4. AsyncTask在不同的Android版本下的并行和串行問題
    關(guān)于AsyncTask的并行和串行問題,在不同的API下是有不同的。在Android1.6之前,AsyncTask是串行執(zhí)行任務(wù)的;到了Android1.6時(shí),開始采用線程池來并行執(zhí)行任務(wù);在Android3.0之后的版本中,AsyncTask又開始用一個(gè)線程串行執(zhí)行任務(wù)。雖然Android3.0之后采用串行方式執(zhí)行任務(wù),但我們可以通過AsyncTask的executeOnExecutor(exe,params),自定義一個(gè)線程池來并行執(zhí)行任務(wù)。


HandlerThread

一、什么是HandlerThread

當(dāng)我們需要執(zhí)行耗時(shí)操作時(shí),很自然的會(huì)想到開啟一個(gè)子線程,當(dāng)耗時(shí)操作完成之后,線程自動(dòng)銷毀。之后如果我們又需要執(zhí)行另一個(gè)耗時(shí)操作,再開啟一個(gè)子線程,這時(shí)就涉及到一個(gè)性能問題了。要知道,頻繁的創(chuàng)建和銷毀線程是很耗費(fèi)系統(tǒng)資源的,所以,為了解決這個(gè)問題,Android給我們提供了HandlerThread這個(gè)類。
好吧,其實(shí)初看這段話,我是沒搞懂HandlerThread到底是干啥的,所以我們來看看它的用法先吧。


二、HandlerThread的用法

HandlerThread的使用十分簡(jiǎn)單,簡(jiǎn)單到只有幾行代碼

        HandlerThread ht = new HandlerThread("newThread");
        ht.start();
        mHandler = new Handler(ht.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                //do something
            }
        };

聲明一個(gè)HandlerThread對(duì)象并傳入一個(gè)字符串參數(shù),然后調(diào)用start()方法啟動(dòng)HandlerThread,將HandlerThread里的Looper傳入Handler。最后記得在Activity的onDestroy()方法中將HandlerThread關(guān)閉

@Override
    protected void onDestroy() {
        super.onDestroy();
        //釋放資源
        ht.quit() ;
    }

用法很簡(jiǎn)單,但是為什么要這么用?這樣子聲明的Handler跟平時(shí)我們用的有什么不一樣呢,那我們來看看HandlerThread的源碼。


三、HandlerThread的源碼分析

HandlerThread的源碼不多,首先來看下類的繼承關(guān)系

public class HandlerThread extends Thread {}

HandlerThread 繼承自Thread,也就是說HandlerThread 本質(zhì)就是一個(gè)線程類,所以當(dāng)我們new HandlerThread ()的時(shí)候?qū)嶋H上是創(chuàng)建了一個(gè)子線程所以之后調(diào)用了start()方法啟動(dòng)了該線程,那接下來我們來看下關(guān)鍵的run()方法

@Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

run()方法里面調(diào)用了Looper.prepare()和Looper.loop(),這個(gè)過程是不是很眼熟,我們前面說過了在子線程中使用Handler,需要自己調(diào)用這兩個(gè)方法,而我們?cè)诼暶鱄andler時(shí),將這個(gè)Looper對(duì)象通過getLooper()方法獲取到并作為參數(shù)傳入了進(jìn)去

public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

如果你夠細(xì)心你會(huì)發(fā)現(xiàn),run方法里面當(dāng)mLooper創(chuàng)建完成后有個(gè)notifyAll(),getLooper()中有個(gè)wait(),這是為什么呢?因?yàn)閙Looper在一個(gè)線程中執(zhí)行,而我們的handler是在UI線程初始化的,也就是說,我們必須等到mLooper創(chuàng)建完成,才能正確的返回getLooper();wait(),notify()就是為了解決這兩個(gè)線程的同步問題。

結(jié)合前面對(duì)Handler的分析,我們知道,當(dāng)我們需要在子線程與子線程之間進(jìn)行通信,也就是需要在子線程中使用Handler時(shí),需要手動(dòng)去操作Looper,這是很不推薦的做法。所以Google官方很貼心的幫我們封裝好了一個(gè)類,那就是剛才HandlerThread啦。

最后再來看看quit()方法

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

兩個(gè)方法最后都是調(diào)用MessageQueue里面的quit(boolean)方法

void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }
            nativeWake(mPtr);
        }
    }

當(dāng)我們調(diào)用Looper的quit方法時(shí),實(shí)際上執(zhí)行了MessageQueue中的removeAllMessagesLocked方法,該方法的作用是把MessageQueue消息池中所有的消息全部清空,無論是延遲消息(延遲消息是指通過sendMessageDelayed或通過postDelayed等方法發(fā)送的需要延遲執(zhí)行的消息)還是非延遲消息。

當(dāng)我們調(diào)用Looper的quitSafely方法時(shí),實(shí)際上執(zhí)行了MessageQueue中的removeAllFutureMessagesLocked方法,通過名字就可以看出,該方法只會(huì)清空MessageQueue消息池中所有的延遲消息,并將消息池中所有的非延遲消息派發(fā)出去讓Handler去處理,quitSafely相比于quit方法安全之處在于清空消息之前會(huì)派發(fā)所有的非延遲消息。

無論是調(diào)用了quit方法還是quitSafely方法只會(huì),Looper就不再接收新的消息。即在調(diào)用了Looper的quit或quitSafely方法之后,消息循環(huán)就終結(jié)了,這時(shí)候再通過Handler調(diào)用sendMessage或post等方法發(fā)送消息時(shí)均返回false,表示消息沒有成功放入消息隊(duì)列MessageQueue中,因?yàn)橄㈥?duì)列已經(jīng)退出了。


四、HandlerThread的特點(diǎn)

  • HandlerThread的本質(zhì)依然是一個(gè)線程類,它繼承自Thread。
  • HandlerThread有自己的內(nèi)部Looper對(duì)象,可以進(jìn)行l(wèi)ooper循環(huán)。
  • 通過獲取HandlerThread的looper對(duì)象傳遞給Handler對(duì)象,可以在handleMessage方法中執(zhí)行異步任務(wù)。
  • 優(yōu)點(diǎn)是不會(huì)阻塞,減少了對(duì)性能的消耗,缺點(diǎn)是不能同時(shí)進(jìn)行多任務(wù)的處理,需要等待進(jìn)行處理,處理效率低。
  • 與線程池注重并發(fā)不同,HandleThread是一個(gè)串行隊(duì)列,HandlerThread背后只有一個(gè)線程。

IntentService

一、IntentService是什么

IntentService是繼承并處理異步請(qǐng)求的一個(gè)類,在IntentService內(nèi)有一個(gè)工作線程來處理耗時(shí)操作,啟動(dòng)IntentService的方式和傳統(tǒng)的Service一樣,同時(shí),當(dāng)任務(wù)執(zhí)行完后,IntentService會(huì)自動(dòng)停止,而不需要我們手動(dòng)去控制或者stopSelf()。另外,可以啟動(dòng)IntentService多次,而每一次耗時(shí)操作會(huì)以工作隊(duì)列的方式在IntentService的onHandleIntent回調(diào)方法中執(zhí)行,并且,每次只會(huì)執(zhí)行一個(gè)工作線程,執(zhí)行完第一個(gè)再執(zhí)行第二個(gè)。

  • 它本質(zhì)是一種特殊的Service,繼承自Service并且本身就是一個(gè)抽象類。
  • 它內(nèi)部通過HandlerThread和Handler實(shí)現(xiàn)異步操作。

二、IntentService的基本使用

繼承IntentService實(shí)現(xiàn)自己類,并重寫構(gòu)造函數(shù)和onHandleIntent()方法,onHandleIntent()是個(gè)異步方法,可以執(zhí)行耗時(shí)操作。

public class MyIntentService extends IntentService {

    public static final String INDEX_FLAG = "index_flag";
    public static UpdateUI updateUI;

    public MyIntentService(String name) {
        super(name);
    }

    public static void setUpdateUI(UpdateUI updateUIInterface) {
        updateUI = updateUIInterface;
    }

      //運(yùn)行在子線程的方法,intent是從啟動(dòng)該服務(wù)的context傳過來的
    @Override
    protected void onHandleIntent(Intent intent) {
        try {
            Thread.sleep(5000);
            Message msg = new Message();
            msg.what = intent.getIntExtra(INDEX_FLAG, 0);
            msg.obj = "返回的值" + msg.what;
            if (updateUI != null)
                updateUI.updateUI(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public interface UpdateUI {
        void updateUI(Message message);
    }
}

Activity中啟動(dòng)該服務(wù)并設(shè)置回調(diào)

public class MainActivity extends AppCompatActivity implements MyIntentService.UpdateUI {

    private static TextView mTextView;

    private static final Handler mUIHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mTextView.setText((String) msg.obj);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = findViewById(R.id.text);
        
        Intent intent = new Intent(this, MyIntentService.class);
        for (int i = 0; i < 7; i++) {
            intent.putExtra(MyIntentService.INDEX_FLAG, i);
            startService(intent);
        }
        MyIntentService.setUpdateUI(this);

    }

    @Override
    public void updateUI(Message message) {
        mUIHandler.sendMessageDelayed(message, message.what * 1000);
    }
}

activity中循環(huán)啟動(dòng)了7次service,但實(shí)際只有一個(gè)IntentService實(shí)例,IntentService會(huì)依次執(zhí)行這7次操作,然后通過UpdateUI 接口回調(diào)結(jié)果更新UI。


三、IntentService的源碼解析

首先,還是看繼承關(guān)系

public abstract class IntentService extends Service {}

可以看到,IntentService本質(zhì)還是個(gè)Service,那我們就去看它的onCreate()方法

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

在onCreate()方法中,創(chuàng)建了一個(gè)HandlerThread實(shí)例對(duì)象并將它傳入了ServiceHandler對(duì)象中,ServiceHandler實(shí)際就是一個(gè)Handler,所以通過這一步,IntentService具備了進(jìn)行異步操作的能力。接著我們來看onStart()方法

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

在onStart()中,通過ServiceHandler發(fā)送了一條附帶startId和Intent的Message,最終這個(gè)Message會(huì)被發(fā)送到持有l(wèi)ooper的ServiceHandler的handleMessage()方法中

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

handleMessage中執(zhí)行了onHandleIntent()這個(gè)方法,也就是我們繼承IntentService時(shí)需要重寫的抽象方法,所以onHandleIntent()是個(gè)異步方法,因?yàn)樗菆?zhí)行在HandlerThread開啟的子線程中的。
值得注意的一點(diǎn)是當(dāng)調(diào)用完onHandleIntent()之后ServiceHandler 調(diào)用了stopSelf(msg.arg1),這個(gè)方法跟stopSelf()的區(qū)別是stopSelf(msg.arg1)會(huì)判斷當(dāng)前的服務(wù)是不是最后的服務(wù),如果不是則不關(guān)閉,等最后的服務(wù)完成后才關(guān)閉,而stopSelf()會(huì)直接關(guān)閉服務(wù)。

所以,IntentService的本質(zhì)就是一個(gè)封裝了HandlerThread和Handler的異步框架。


參考資料
Android 異步消息處理機(jī)制(Handler 、 Looper 、MessageQueue)源碼解析
Android異步任務(wù)機(jī)制之AsycTask

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

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