Android面試題(五)—— Android的消息機(jī)制

前言


Handler是Android消息機(jī)制的上層接口,平時(shí)使用起來很方便,我們可以通過它把一個(gè)任務(wù)切換到Handler所在的線程中去運(yùn)行。而最常用的就是拿來從子線程切換到主線程以便更新UI。關(guān)于Android的消息機(jī)制無法以題目為導(dǎo)向來進(jìn)行講解,面試中可能會(huì)問關(guān)于Handler、Looper、MessageQueue、Message之間的關(guān)系,要完整回答,我們需要了解Handler內(nèi)部是如何工作的,而這一部分的源碼并不復(fù)雜。所以先整體分析得出結(jié)論,再?gòu)脑创a中驗(yàn)證結(jié)論。

Android的消息機(jī)制整體剖析


Android的消息機(jī)制工作原理大致如下:


Android消息機(jī)制.jpg
  1. MessageQueue:它的內(nèi)部存儲(chǔ)了一組數(shù)據(jù),以隊(duì)列的形式向外提供了插入和刪除的工作。但是它的內(nèi)部實(shí)現(xiàn)并不是隊(duì)列,而是單鏈表。對(duì)應(yīng)圖中長(zhǎng)方形格子

  2. Looper:會(huì)不停檢查是否有新的消息,如果有就調(diào)用最終消息中的Runnable或者Handler的handleMessage方法。對(duì)應(yīng)提取并處理消息。

  3. Handler:Handler的工作主要包含消息的發(fā)送和接收過程。消息的發(fā)送可以通過post的一系列方法以及send的一系列方法來實(shí)現(xiàn),不過最后都是通過send的一系列方法實(shí)現(xiàn)的。對(duì)應(yīng)添加消息處理線程

  4. Message:封裝了需要傳遞的消息,并且本身可以作為鏈表的一個(gè)節(jié)點(diǎn),方便MessageQueue的存儲(chǔ)。

  5. ThreadLocal:一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過它可以在指定的線程中儲(chǔ)存數(shù)據(jù),而其它線程無法獲取到。在Looper、AMS中都有使用。

  6. Thread:Android的線程類

Android消息機(jī)制的類的關(guān)系總結(jié)如下:


Android消息機(jī)制的類的關(guān)系UML圖

由上圖總結(jié)出以下結(jié)論:

  1. MessageQueue持有一個(gè)mMessages,作為消息隊(duì)列內(nèi)部存儲(chǔ)數(shù)據(jù)的鏈表頭。它具有兩個(gè)重要的操作:對(duì)消息的插入和讀取,對(duì)應(yīng)的方法分別是enqueueMessage和next。其中enqueueMessage是往消息隊(duì)列中插入一條信息,而next的作用是從消息隊(duì)列中取出一條信息并將其從消息隊(duì)列中移除。

  2. Message內(nèi)部除了obj,what,arg1,arg2等存儲(chǔ)數(shù)據(jù)的成員,還有一個(gè)可以指向其他Message的指針,所以MessageQueue可以使用它來作為鏈表的節(jié)點(diǎn)。

  3. Looper內(nèi)部持有一個(gè)消息隊(duì)列、線程、主線程、ThreadLocal。主要的方法有:

- prepare:為當(dāng)前線程創(chuàng)建一個(gè)Looper。
- quit:退出Looper,Looper退出后,Handler的send方法會(huì)返回false,在子線程手動(dòng)創(chuàng)建的Looper最好在不需要的時(shí)候終止掉。
- quitSafely:把消息隊(duì)列中已有的消息處理完畢后退出。
- getMainLooper:在任何地方獲取主線程的Looper。
- getLooper:獲取當(dāng)前線程的Looper。
- loop:最重要的一個(gè)方法,只有調(diào)用了loop方法后,消息循環(huán)系統(tǒng)才能起作用。(后面再做詳細(xì)解釋)
  1. 一個(gè)Thread只能持有一個(gè)Looper。

  2. Handler持有一個(gè)消息隊(duì)列、Looper、Callback。提供多種創(chuàng)建方法,默認(rèn)的Handler()將使用當(dāng)前線程的Looper,如果當(dāng)前線程沒有Looper會(huì)拋出異常,也可以通過傳參指定Looper。sendMessage方法可以往消息隊(duì)列添加消息。handleMessage方法在創(chuàng)建Handler的線程中或者指定的Looper持有的線程中處理消息。

  3. 一個(gè)Looper可以被多個(gè)Handler持有

  4. ThreadLocal的get和set方法操作的數(shù)據(jù),在每個(gè)線程中是相互獨(dú)立,互不干擾的。

源碼分析


1. ThreadLocal的工作原理

  • ThreadLocal是什么?
    ThreadLocal是一個(gè)線程內(nèi)部的數(shù)據(jù)存儲(chǔ)類,通過它可以在指定的線程中存儲(chǔ)數(shù)據(jù),而其它線程無法獲取到數(shù)據(jù)。在Looper、ActiivtyThread和AMS中都用到了ThreadLocal。
  • ThreadLocal的使用場(chǎng)景
    • 當(dāng)某些數(shù)據(jù)是以線程為作用域,并且不同線程有不同的數(shù)據(jù)副本的時(shí)候
    • 復(fù)雜邏輯下的對(duì)象傳遞,比如監(jiān)聽器的傳遞。使用參數(shù)傳遞的話:當(dāng)函數(shù)調(diào)用棧過深時(shí),設(shè)計(jì)會(huì)很糟糕。為每一個(gè)線程定義一個(gè)靜態(tài)變量存儲(chǔ)監(jiān)聽器,如果是多線程的話,一個(gè)線程就需要定義一個(gè)靜態(tài)變量,無法擴(kuò)展,這時(shí)候使用ThreadLocal可以解決問題。

從ThreadLocal的set和get方法可以看出,他們所操作的對(duì)象都是當(dāng)前線程的localValues對(duì)象的table數(shù)組,因此在不同的線程訪問ThreadLocal的set和get方法,他們對(duì)ThreadLocal的讀寫操作都是僅限于各自線程的內(nèi)部。這就是ThreadLocal可以在多個(gè)線程中互不干擾地存儲(chǔ)和修改數(shù)據(jù)的原因。
簡(jiǎn)單來講:就是每個(gè)線程都可以操作ThreadLocal,但他們操作的數(shù)據(jù)是分隔開的,互不干擾的,代碼如下:

        private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<Boolean>();

        mBooleanThreadLocal.set(true);
        Log.d(TAG, "[Thread#main]mBooleanThreadLocal="+ mBooleanThreadLocal.get());

        new Thread("Thread#1") {
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.d(TAG, "[Thread#1]mBooleanThreadLocal="+ mBooleanThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                Log.d(TAG, "[Thread#2]mBooleanThreadLocal="+ mBooleanThreadLocal.get());
            }
        }.start();

運(yùn)行結(jié)果:

07-17 16:23:23.222 23286-23286/com.ryg.chapter_15 D/MainActivity: [Thread#main]mBooleanThreadLocal=true
07-17 16:23:23.222 23286-23312/com.ryg.chapter_15 D/MainActivity: [Thread#1]mBooleanThreadLocal=false
07-17 16:23:23.222 23286-23313/com.ryg.chapter_15 D/MainActivity: [Thread#2]mBooleanThreadLocal=null

由此可以得出結(jié)論7是正確的。

2. 消息隊(duì)列的工作原理

消息隊(duì)列在Android中指的是MessageQueue,它主要包含兩個(gè)操作:插入和讀取。讀取操作本身會(huì)伴隨著刪除操作。插入和刪除對(duì)應(yīng)的方法分別為enqueueMessage和next。代碼如下:

    boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

從enqueueMessage的實(shí)現(xiàn)中,我們可以明顯看出這是一個(gè)單鏈表的插入操作,不多解釋,接著看next方法:

    Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            // We can assume mPtr != 0 because the loop is obviously still running.
            // The looper will not call this method after the loop quits.
            nativePollOnce(mPtr, nextPollTimeoutMillis);

            synchronized (this) {
                // 嘗試獲取一個(gè)消息,如果找到就返回它。
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
               ...
            }
            ...
        }
    }

next方法是一個(gè)無限循環(huán)的方法,如果消息隊(duì)列中沒有消息,那么next方法會(huì)一直阻塞在這里。當(dāng)有新消息到來時(shí),next方法會(huì)返回這條消息并將其從單鏈表中移除。

3. Looper的工作原理

Looper在Android消息機(jī)制中扮演消息循環(huán)的角色,它會(huì)不停地從MessageQueue中查看是否有新消息,如果有就立即處理,沒有就阻塞在那里?,F(xiàn)在,我們從Looper的使用的一個(gè)常見例子來分析這個(gè)Looper類。代碼如下:

    class LooperThread extends Thread {
        public Handler h;
        public void run() {
            // 1. 調(diào)用prepare
            Looper.prepare();
            // 2.進(jìn)入消息循環(huán) 
            Looper.loop();
            ...
        }
    }
    // 應(yīng)用程序使用LooperThread
    {
        ...
        new LooperThread().start(); //啟動(dòng)新線程,線程函數(shù)是run
    }

在上面代碼中,Looper有兩處關(guān)鍵調(diào)用,分別是1. 調(diào)用prepare 2.進(jìn)入消息循環(huán)。接下來我們重點(diǎn)分析這兩個(gè)函數(shù)。

  1. 第一個(gè)調(diào)用的函數(shù)是Looper的prepare函數(shù)。代碼如下:
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        // 一個(gè)線程中只能有一個(gè)Looper。只能調(diào)用一次prepare
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 構(gòu)造一個(gè)Looper對(duì)象,設(shè)置到調(diào)用線程的局部變量中。
        sThreadLocal.set(new Looper(quitAllowed));
    }
    // sThreadLocal的定義
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

根據(jù)上面對(duì)ThreadLocal的分析,我們知道使用局部變量sThreadLocal存儲(chǔ)的變量作用域是針對(duì)線程的。即通過prepare為調(diào)用的線程的設(shè)置了一個(gè)Looper對(duì)象。在看一看Looper的構(gòu)造方法。

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

在構(gòu)造方法中,創(chuàng)建了一個(gè)新的消息隊(duì)列,和持有當(dāng)前線程的引用。
所以prepare函數(shù)主要完成的工作是:在調(diào)用prepare的線程中,設(shè)置一個(gè)Looper對(duì)象,這個(gè)Looper對(duì)象保存在Thread的localValues中,而Looper對(duì)象內(nèi)部封裝了一個(gè)消息隊(duì)列。
prepare通過ThreadLocal機(jī)制,巧妙地將Looper和調(diào)用線程關(guān)聯(lián)在一塊。接著看第二個(gè)重要函數(shù)loop。

  1. Looper的循環(huán)
    代碼如下:
    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
        final Looper me = myLooper(); //myLooper返回當(dāng)前線程的Looper對(duì)象
        final MessageQueue queue = me.mQueue; // 取出Looper中的消息隊(duì)列
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            // 調(diào)用該消息的Handler,交給它的dispatchMessage處理
            msg.target.dispatchMessage(msg);
            ...
            // 消息的回收,回收到消息池中。
            msg.recycle();
        }
    }

從代碼中可以看出,loop是一個(gè)死循環(huán),唯一跳出循環(huán)的條件是MessageQueue的next方法返回null。而只有當(dāng)Looper調(diào)用quit或者quitSafely方法,方法內(nèi)部再調(diào)用MessageQueue的quit或者quitSafely方法通知消息隊(duì)列退出,當(dāng)消息隊(duì)列退出后,next方法才會(huì)返回null。從上面對(duì)MessageQueue的分析中,我們知道next方法是一個(gè)阻塞操作,當(dāng)消息隊(duì)列中沒有消息時(shí),next方法就會(huì)一直阻塞在那里,這也導(dǎo)致了loop方法一直阻塞在那里,直到next方法返回新的消息,才會(huì)調(diào)用msg.target.dispatchMessage(msg)來處理消息。這里的msg.target就是發(fā)送這條消息的Handler對(duì)象。需要注意的是:通過這一過程。因?yàn)镠andler的dispatchMessage方法在loop中執(zhí)行,所以發(fā)送消息所在的線程成功地把代碼邏輯切換到了Looper所在的線程中執(zhí)行,完成了線程間的切換。

4. Handler的工作原理

首先看一下Handler所包括的成員:

    final MessageQueue mQueue; // Handler中也有一個(gè)消息隊(duì)列,從mLooper中取出的
    final Looper mLooper; // 當(dāng)前線程的Looper或者指定的Looper
    final Callback mCallback; //有一個(gè)回調(diào)用的類

這幾個(gè)變量是如何被使用的?首先分析Handler的構(gòu)造方法,代碼如下:

    public Handler()

    public Handler(Callback callback)

    public Handler(Looper looper) 

    public Handler(Looper looper, Callback callback)

在Handler中我們常用的構(gòu)造方法有上面4個(gè),如果沒有指定Callback,默認(rèn)mCallback為null。如果沒有指定Looper,默認(rèn)使用當(dāng)前線程的Looper,當(dāng)前線程Looper為null時(shí),拋出異常。mQueue是通過mLooper的myLooper方法獲取的。
Handler的工作主要包含消息的發(fā)送和接收過程。消息的發(fā)送可以通過post的一系列方法以及send的一系列方法來實(shí)現(xiàn),不過最后都是通過send的一系列方法實(shí)現(xiàn)的。代碼如下:

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

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

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

可以發(fā)現(xiàn),Handler發(fā)送消息的過程不過是向消息隊(duì)列插入一條消息,MessageQueue的next方法會(huì)返回這條消息給Looper,Looper收到消息會(huì)交給Handler處理,Handler的dispatchMessage方法就會(huì)被調(diào)用DispatchMessage的實(shí)現(xiàn):

    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
Handler消息處理流程圖

dispatchMessage會(huì)先調(diào)用Message的callback接口,在調(diào)用Handler的Callback,最后調(diào)用handlerMessage方法。

Handler的實(shí)際應(yīng)用


1. Looper和Handler的同步關(guān)系

Looper和Handler會(huì)有什么同步關(guān)系呢?它們之間存在的同步關(guān)系跟多線程有關(guān),如果不注意,就容易引起錯(cuò)誤。
下面看一個(gè)例子:

    class LooperThread extends Thread {
        public Looper myLooper = null;

        @Override
        public void run() { // 假使run在線程2中執(zhí)行
            Looper.prepare();
            myLooper = Looper.myLooper();
            Looper.loop();
        }
    }
    // 假使下面代碼在線程1中運(yùn)行
    {
        LooperThread thread2 = new LooperThread();
        thread2.start();
        Looper looper = thread2.myLooper;
        Handler thread1Hanlder = new Handler(looper);
        thread1Hanlder.sendEmptyMessage(0);
    }

以上代碼的作用:

  • 在線程1中創(chuàng)建線程2,并且線程2通過Looper處理消息。
  • 線程1中得到線程2的Looper,并且根據(jù)這個(gè)Looper創(chuàng)建一個(gè)Handler,這樣發(fā)送給該Handler的消息將由線程2處理。
    理想是美好的,現(xiàn)實(shí)是殘酷的。如果我們熟悉多線程,就很容易發(fā)現(xiàn)這段代碼中存在巨大漏洞,需要注意的是:myLooper的賦值是在線程2的run方法中,而looper的賦值又是在線程1中,這樣就可能導(dǎo)致線程2的run函數(shù)還沒來得及給myLooper賦值,線程1中的looper就取得了myLooper的初值,即looper等于null。
    解決這個(gè)問題,只需要在其中加入線程鎖就可以了。不過不用我們自己動(dòng)手,Android已經(jīng)為這個(gè)問題提供了解決方案,那就是HandlerThread。
    HandlerThread可以完美解決myLooper可能為空的問題。直接上代碼:
    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;
    }

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

HanderThread很簡(jiǎn)單,使用wait/notifyAll就解決了這個(gè)問題。

2. 小心內(nèi)存泄露

Handler的使用是有可能引起內(nèi)存泄露的,先看一個(gè)例子

public class MainActivity extends Activity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mTextView = new TextView(this);
        mTextView.setText("內(nèi)存泄露?");
        setContentView(mTextView);
        MyHandler handler = new MyHandler(mTextView);
        handler.sendEmptyMessage(0x11);
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mTextView.setText("有內(nèi)存泄露");
        }
    };
}

上面的代碼如果是在Android Studio中編寫,Android Lint會(huì)提示可能存在內(nèi)存泄露,并提供相應(yīng)的解決方案:

This Handler class should be static or leaks might occur (anonymous android.os.Handler) less... (Ctrl+F1)
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

這段話的大概意思是:這個(gè)Handler應(yīng)該聲明為靜態(tài)的,否則可能導(dǎo)致內(nèi)存泄露。當(dāng)Handler聲明為內(nèi)部類時(shí),他可能持有外部類的引用。如果這時(shí)Handler使用一個(gè)并非來自主線程的Looper或者M(jìn)essageQueue時(shí),那就沒有問題。否則你需要修改你的Handler。具體步驟:
1. 將Handler聲明為靜態(tài)類,
2. 當(dāng)你的Handler類需要引用外部類的成員時(shí),使用WeakReference弱引用來獲得它們。

具體改造后,就變成了:

    public class MainActivity extends Activity {

        private TextView mTextView;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mTextView = new TextView(this);
            mTextView.setText("有內(nèi)存泄露");
            setContentView(mTextView);
            UiHandler handler = new UiHandler(mTextView);
            handler.sendEmptyMessage(0x11);
        }

        static class UiHandler extends Handler {
            WeakReference<MainActivity> mActivity;

            UiHandler(MainActivity activity) {
                mActivity = new WeakReference<MainActivity>(activity);
            }

            @Override
            public void handleMessage(Message msg) {
                TextView textView = mActivity.get().mTextView;
                if (textView != null) {
                    textView.setText("無內(nèi)存泄漏");
                }
            }
        }
    }

為以上代碼作以下幾點(diǎn)解釋:
1、上述Handler的作用,是在無內(nèi)存泄漏的情況下,為外部Activity的mTextView設(shè)置文本信息。
2、靜態(tài)類不持有外部類的對(duì)象,所以外部Activity可以隨意被回收,不會(huì)因delay的Message持有了Handler的引用,而Handler又持有Activity的引用,導(dǎo)致Activity被關(guān)閉后無法被GC回收。多次的打開和關(guān)閉,會(huì)造成OOM。
3、WeakReference是弱引用類型,我們可以借助弱引用類型對(duì)外部非靜態(tài)變量進(jìn)行操作,且Handler僅有一條弱引用指向了textView,不會(huì)影響textView的回收。

3. IntentService

這是 Service 的子類,它使用工作線程逐一處理所有啟動(dòng)請(qǐng)求。如果您不要求服務(wù)同時(shí)處理多個(gè)請(qǐng)求,這是最好的選擇。 您只需實(shí)現(xiàn)onHandleIntent()方法即可,該方法會(huì)接收每個(gè)啟動(dòng)請(qǐng)求的 Intent,使您能夠執(zhí)行后臺(tái)工作。
以下是 IntentService 的實(shí)現(xiàn)示例:

public class HelloIntentService extends IntentService {

  /**
   * A constructor is required, and must call the super IntentService(String)
   * constructor with a name for the worker thread.
   */
  public HelloIntentService() {
      super("HelloIntentService");
  }

  /**
   * The IntentService calls this method from the default worker thread with
   * the intent that started the service. When this method returns, IntentService
   * stops the service, as appropriate.
   */
  @Override
  protected void onHandleIntent(Intent intent) {
      // Normally we would do some work here, like download a file.
      // For our sample, we just sleep for 5 seconds.
      long endTime = System.currentTimeMillis() + 5*1000;
      while (System.currentTimeMillis() < endTime) {
          synchronized (this) {
              try {
                  wait(endTime - System.currentTimeMillis());
              } catch (Exception e) {
              }
          }
      }
  }
}

您只需要一個(gè)構(gòu)造函數(shù)和一個(gè) onHandleIntent() 實(shí)現(xiàn)即可。
IntentService內(nèi)部使用Handler來實(shí)現(xiàn),以下提供了Service類實(shí)現(xiàn)與IntentService相同功能的代碼:

public class HelloService extends Service {
  private Looper mServiceLooper;
  private ServiceHandler mServiceHandler;

  // Handler that receives messages from the thread
  private final class ServiceHandler extends Handler {
      public ServiceHandler(Looper looper) {
          super(looper);
      }
      @Override
      public void handleMessage(Message msg) {
          // Normally we would do some work here, like download a file.
          // For our sample, we just sleep for 5 seconds.
          long endTime = System.currentTimeMillis() + 5*1000;
          while (System.currentTimeMillis() < endTime) {
              synchronized (this) {
                  try {
                      wait(endTime - System.currentTimeMillis());
                  } catch (Exception e) {
                  }
              }
          }
          // Stop the service using the startId, so that we don't stop
          // the service in the middle of handling another job
          stopSelf(msg.arg1);
      }
  }

  @Override
  public void onCreate() {
    // Start up the thread running the service.  Note that we create a
    // separate thread because the service normally runs in the process's
    // main thread, which we don't want to block.  We also make it
    // background priority so CPU-intensive work will not disrupt our UI.
    HandlerThread thread = new HandlerThread("ServiceStartArguments",
            Process.THREAD_PRIORITY_BACKGROUND);
    thread.start();

    // Get the HandlerThread's Looper and use it for our Handler
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
  }

  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
      Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();

      // For each start request, send a message to start a job and deliver the
      // start ID so we know which request we're stopping when we finish the job
      Message msg = mServiceHandler.obtainMessage();
      msg.arg1 = startId;
      mServiceHandler.sendMessage(msg);

      // If we get killed, after returning from here, restart
      return START_STICKY;
  }

  @Override
  public IBinder onBind(Intent intent) {
      // We don't provide binding, so return null
      return null;
  }

  @Override
  public void onDestroy() {
    Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
  }
}

正如您所見,與使用 IntentService 相比,這需要執(zhí)行更多工作。
但是,因?yàn)槭怯赡约禾幚韺?duì) onStartCommand() 的每個(gè)調(diào)用,因此可以同時(shí)執(zhí)行多個(gè)請(qǐng)求。此示例并未這樣做,但如果您希望如此,則可為每個(gè)請(qǐng)求創(chuàng)建一個(gè)新線程,然后立即運(yùn)行這些線程(而不是等待上一個(gè)請(qǐng)求完成)。

推薦

《我的個(gè)人博客》

參考資料

《Android開發(fā)藝術(shù)探究》
《深入理解Android卷1》
服務(wù) | Android Developers
Android自定義無內(nèi)存泄露的Handler
自定義無內(nèi)存泄漏的Handler內(nèi)部類

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容