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

前言


Handler是Android消息機制的上層接口,平時使用起來很方便叭披,我們可以通過它把一個任務(wù)切換到Handler所在的線程中去運行。而最常用的就是拿來從子線程切換到主線程以便更新UI。關(guān)于Android的消息機制無法以題目為導(dǎo)向來進行講解淡诗,面試中可能會問關(guān)于Handler覆旱、Looper蘸朋、MessageQueue、Message之間的關(guān)系扣唱,要完整回答藕坯,我們需要了解Handler內(nèi)部是如何工作的团南,而這一部分的源碼并不復(fù)雜。所以先整體分析得出結(jié)論炼彪,再從源碼中驗證結(jié)論吐根。

Android的消息機制整體剖析


Android的消息機制工作原理大致如下:


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

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

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

  4. Message:封裝了需要傳遞的消息特愿,并且本身可以作為鏈表的一個節(jié)點,方便MessageQueue的存儲勾缭。

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

  6. Thread:Android的線程類

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


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

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

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

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

  3. Looper內(nèi)部持有一個消息隊列雷客、線程芒珠、主線程、ThreadLocal搅裙。主要的方法有:

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

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

  3. 一個Looper可以被多個Handler持有

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

源碼分析


1. ThreadLocal的工作原理

  • ThreadLocal是什么倦卖?
    ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲數(shù)據(jù)椿争,而其它線程無法獲取到數(shù)據(jù)怕膛。在Looper、ActiivtyThread和AMS中都用到了ThreadLocal丘薛。
  • ThreadLocal的使用場景
    • 當(dāng)某些數(shù)據(jù)是以線程為作用域嘉竟,并且不同線程有不同的數(shù)據(jù)副本的時候
    • 復(fù)雜邏輯下的對象傳遞,比如監(jiān)聽器的傳遞洋侨。使用參數(shù)傳遞的話:當(dāng)函數(shù)調(diào)用棧過深時舍扰,設(shè)計會很糟糕。為每一個線程定義一個靜態(tài)變量存儲監(jiān)聽器希坚,如果是多線程的話边苹,一個線程就需要定義一個靜態(tài)變量,無法擴展裁僧,這時候使用ThreadLocal可以解決問題个束。

從ThreadLocal的set和get方法可以看出慕购,他們所操作的對象都是當(dāng)前線程的localValues對象的table數(shù)組,因此在不同的線程訪問ThreadLocal的set和get方法茬底,他們對ThreadLocal的讀寫操作都是僅限于各自線程的內(nèi)部沪悲。這就是ThreadLocal可以在多個線程中互不干擾地存儲和修改數(shù)據(jù)的原因。
簡單來講:就是每個線程都可以操作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();

運行結(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. 消息隊列的工作原理

消息隊列在Android中指的是MessageQueue涉馁,它主要包含兩個操作:插入和讀取。讀取操作本身會伴隨著刪除操作爱致。插入和刪除對應(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的實現(xiàn)中,我們可以明顯看出這是一個單鏈表的插入操作糠悯,不多解釋帮坚,接著看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) {
                // 嘗試獲取一個消息,如果找到就返回它逢防。
                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方法是一個無限循環(huán)的方法叶沛,如果消息隊列中沒有消息,那么next方法會一直阻塞在這里忘朝。當(dāng)有新消息到來時灰署,next方法會返回這條消息并將其從單鏈表中移除。

3. Looper的工作原理

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

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

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

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

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

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

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

在構(gòu)造方法中骄噪,創(chuàng)建了一個新的消息隊列尚困,和持有當(dāng)前線程的引用。
所以prepare函數(shù)主要完成的工作是:在調(diào)用prepare的線程中链蕊,設(shè)置一個Looper對象尾组,這個Looper對象保存在Thread的localValues中,而Looper對象內(nèi)部封裝了一個消息隊列示弓。
prepare通過ThreadLocal機制,巧妙地將Looper和調(diào)用線程關(guān)聯(lián)在一塊呵萨。接著看第二個重要函數(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對象
        final MessageQueue queue = me.mQueue; // 取出Looper中的消息隊列
        ...
        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是一個死循環(huán)忱嘹,唯一跳出循環(huán)的條件是MessageQueue的next方法返回null嘱腥。而只有當(dāng)Looper調(diào)用quit或者quitSafely方法,方法內(nèi)部再調(diào)用MessageQueue的quit或者quitSafely方法通知消息隊列退出拘悦,當(dāng)消息隊列退出后齿兔,next方法才會返回null。從上面對MessageQueue的分析中础米,我們知道next方法是一個阻塞操作分苇,當(dāng)消息隊列中沒有消息時,next方法就會一直阻塞在那里屁桑,這也導(dǎo)致了loop方法一直阻塞在那里医寿,直到next方法返回新的消息,才會調(diào)用msg.target.dispatchMessage(msg)來處理消息蘑斧。這里的msg.target就是發(fā)送這條消息的Handler對象靖秩。需要注意的是:通過這一過程。因為Handler的dispatchMessage方法在loop中執(zhí)行竖瘾,所以發(fā)送消息所在的線程成功地把代碼邏輯切換到了Looper所在的線程中執(zhí)行沟突,完成了線程間的切換。

4. Handler的工作原理

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

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

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

    public Handler()

    public Handler(Callback callback)

    public Handler(Looper looper) 

    public Handler(Looper looper, Callback callback)

在Handler中我們常用的構(gòu)造方法有上面4個乐横,如果沒有指定Callback求橄,默認(rèn)mCallback為null今野。如果沒有指定Looper,默認(rèn)使用當(dāng)前線程的Looper罐农,當(dāng)前線程Looper為null時条霜,拋出異常。mQueue是通過mLooper的myLooper方法獲取的涵亏。
Handler的工作主要包含消息的發(fā)送和接收過程宰睡。消息的發(fā)送可以通過post的一系列方法以及send的一系列方法來實現(xiàn),不過最后都是通過send的一系列方法實現(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ā)送消息的過程不過是向消息隊列插入一條消息,MessageQueue的next方法會返回這條消息給Looper宠默,Looper收到消息會交給Handler處理麸恍,Handler的dispatchMessage方法就會被調(diào)用DispatchMessage的實現(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會先調(diào)用Message的callback接口,在調(diào)用Handler的Callback搀矫,最后調(diào)用handlerMessage方法抹沪。

Handler的實際應(yīng)用


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

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

    class LooperThread extends Thread {
        public Looper myLooper = null;

        @Override
        public void run() { // 假使run在線程2中執(zhí)行
            Looper.prepare();
            myLooper = Looper.myLooper();
            Looper.loop();
        }
    }
    // 假使下面代碼在線程1中運行
    {
        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ù)這個Looper創(chuàng)建一個Handler虹茶,這樣發(fā)送給該Handler的消息將由線程2處理逝薪。
    理想是美好的,現(xiàn)實是殘酷的蝴罪。如果我們熟悉多線程董济,就很容易發(fā)現(xiàn)這段代碼中存在巨大漏洞,需要注意的是:myLooper的賦值是在線程2的run方法中要门,而looper的賦值又是在線程1中虏肾,這樣就可能導(dǎo)致線程2的run函數(shù)還沒來得及給myLooper賦值,線程1中的looper就取得了myLooper的初值欢搜,即looper等于null封豪。
    解決這個問題,只需要在其中加入線程鎖就可以了炒瘟。不過不用我們自己動手吹埠,Android已經(jīng)為這個問題提供了解決方案,那就是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很簡單粘都,使用wait/notifyAll就解決了這個問題。

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

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

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會提示可能存在內(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.

這段話的大概意思是:這個Handler應(yīng)該聲明為靜態(tài)的呻纹,否則可能導(dǎo)致內(nèi)存泄露堆生。當(dāng)Handler聲明為內(nèi)部類時,他可能持有外部類的引用雷酪。如果這時Handler使用一個并非來自主線程的Looper或者MessageQueue時淑仆,那就沒有問題。否則你需要修改你的Handler哥力。具體步驟:
1. 將Handler聲明為靜態(tài)類糯景,
2. 當(dāng)你的Handler類需要引用外部類的成員時,使用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)存泄漏");
                }
            }
        }
    }

為以上代碼作以下幾點解釋:
1最住、上述Handler的作用钞澳,是在無內(nèi)存泄漏的情況下,為外部Activity的mTextView設(shè)置文本信息涨缚。
2轧粟、靜態(tài)類不持有外部類的對象,所以外部Activity可以隨意被回收脓魏,不會因delay的Message持有了Handler的引用兰吟,而Handler又持有Activity的引用,導(dǎo)致Activity被關(guān)閉后無法被GC回收茂翔。多次的打開和關(guān)閉混蔼,會造成OOM。
3珊燎、WeakReference是弱引用類型惭嚣,我們可以借助弱引用類型對外部非靜態(tài)變量進行操作,且Handler僅有一條弱引用指向了textView悔政,不會影響textView的回收晚吞。

3. IntentService

這是 Service 的子類,它使用工作線程逐一處理所有啟動請求谋国。如果您不要求服務(wù)同時處理多個請求槽地,這是最好的選擇。 您只需實現(xiàn)onHandleIntent()方法即可,該方法會接收每個啟動請求的 Intent捌蚊,使您能夠執(zhí)行后臺工作集畅。
以下是 IntentService 的實現(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òu)造函數(shù)和一個 onHandleIntent() 實現(xiàn)即可。
IntentService內(nèi)部使用Handler來實現(xiàn)逢勾,以下提供了Service類實現(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í)行更多工作溺拱。
但是逃贝,因為是由您自己處理對 onStartCommand() 的每個調(diào)用,因此可以同時執(zhí)行多個請求迫摔。此示例并未這樣做沐扳,但如果您希望如此,則可為每個請求創(chuàng)建一個新線程句占,然后立即運行這些線程(而不是等待上一個請求完成)沪摄。

推薦

《我的個人博客》

參考資料

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市纱烘,隨后出現(xiàn)的幾起案子杨拐,更是在濱河造成了極大的恐慌,老刑警劉巖擂啥,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哄陶,死亡現(xiàn)場離奇詭異,居然都是意外死亡哺壶,警方通過查閱死者的電腦和手機屋吨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來山宾,“玉大人至扰,你說我怎么就攤上這事∽拭蹋” “怎么了敢课?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绷杜。 經(jīng)常有香客問我翎猛,道長,這世上最難降的妖魔是什么接剩? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任切厘,我火速辦了婚禮,結(jié)果婚禮上懊缺,老公的妹妹穿的比我還像新娘疫稿。我一直安慰自己培他,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布遗座。 她就那樣靜靜地躺著舀凛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪途蒋。 梳的紋絲不亂的頭發(fā)上猛遍,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天,我揣著相機與錄音号坡,去河邊找鬼懊烤。 笑死,一個胖子當(dāng)著我的面吹牛宽堆,可吹牛的內(nèi)容都是我干的腌紧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼畜隶,長吁一口氣:“原來是場噩夢啊……” “哼壁肋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起籽慢,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤浸遗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后箱亿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乙帮,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年极景,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驾茴。...
    茶點故事閱讀 39,696評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玄帕,到底是詐尸還是另有隱情驹愚,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布峡捡,位于F島的核電站击碗,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏们拙。R本人自食惡果不足惜稍途,卻給世界環(huán)境...
    茶點故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望砚婆。 院中可真熱鬧械拍,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迄损,卻和暖如春定躏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芹敌。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工痊远, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人党窜。 一個月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓拗引,卻偏偏與公主長得像,于是被迫代替她去往敵國和親幌衣。 傳聞我的和親對象是個殘疾皇子矾削,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,592評論 2 353

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