Android進階知識:Handler相關

胖宅老鼠發表於2019-04-10

1、前言

Handler在Android中的地位不必說了,學習安卓開發必然跳不過Handler,講解Handler的文章也是非常的多,這裡我對我所瞭解的Handler這種Android中多執行緒間的通訊方式的相關知識做一個總結。

2、Handler使用

Handler作為執行緒間通訊的方式,最常使用的地方就是子執行緒更新UI。因為Android的UI控制元件不是執行緒安全的,如果在多執行緒下併發訪問可能會導致UI控制元件處於不可預期的狀態。所以在子執行緒想要更新UI的時候會使用handler.sendMessage(Message msg)方法通知主執行緒更新。
關於Handler的常用方法,除了sendMessage系列方法,handler還有一個post系列方法,可以在子執行緒中通過handler.post(Runnable r)方法進行一些在主執行緒的操作。
sendMessage系列方法:

public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)
public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
public final boolean sendMessageDelayed(Message msg, long delayMillis)
public final boolean sendMessageAtFrontOfQueue(Message msg)
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
複製程式碼

post系列方法:

public final boolean post(Runnable r)
public final boolean postAtTime(Runnable r, long uptimeMillis)
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean postDelayed(Runnable r, Object token, long delayMillis)
public final boolean postAtFrontOfQueue(Runnable r)
複製程式碼

簡單運用:

 private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_SEND:
                    mTitleText.setText((String) msg.obj);
                    break;
            }
        }
    };
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                           //子執行緒耗時操作
                          Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                mTitleText.setText("post");
                            }
                        });
                    }
                }).start();
                break;
            case R.id.button2:
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            //子執行緒耗時操作
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //傳送訊息
                        Message message = Message.obtain();
                        message.what = MESSAGE_SEND;
                        message.obj = "sendMessage";
                        handler.sendMessage(message);
                    }
                }).start();
                break;
        }
    }
複製程式碼

3、Handler原始碼閱讀

Android進階知識:Handler相關
學會使用後下一步就是學習原理 ,學習原理就免不了要閱讀原始碼。 從上面的圖已經能看出Handler的基本工作流程。過程中主要涉及了以下四個類:

  • Handler
  • Looper
  • MesageQueue
  • Message

接下來首先就從Handler看起。

Handler的構造方法:

使用Handler第一步就是建立一個Handler物件,從而首先呼叫的就是Handler的構造方法。當然Handler構造方法有很多的不同引數的過載,這裡只看最主要的兩個。

 public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
 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());
            }
        }
        //獲取當前執行緒的Looper
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //給Handler中的成員變數初始化
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
複製程式碼

第一個是傳參帶有Looper的所呼叫的方法,其中就是做了些初始化的操作,呼叫這個方法建立的Handler的Looper就是作為引數傳入的Looper。
第二個構造方法中第一個if判斷當前Handler類是否有記憶體洩漏的隱患,主要是檢驗當前類是否是匿名類、成員類、區域性類是否靜態修飾等。接著通過Looper.myLooper()方法獲取當前執行緒中的Looper物件。接下來判斷如果這個Looper為空,說明當前Handler初始化所線上程沒有Looper,會丟擲Exception。這裡就決定了Handler初始化所線上程必須有Looper,所以在子執行緒中建立Handler之前先要通過Looper.prepare()方法建立Looper。接著就是對Handler中一些成員變數進行初始化,將Looper中的訊息佇列引用傳遞給mQueue,將構造中傳入的callback初始化等。
來看下Looper的myLooper()方法:

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

這裡是從ThreadLocal中獲取到一個Looper。關於ThreadLocal的相關知識點可以看Android進階知識:ThreadLocal

Handler建立之後,在需要進行主執行緒操作的時候,我們會使用handler的sendMessage系列方法,或者post系列方法。這裡同樣有很多過載,具體的方法在前文中已經列舉。這裡先看post方法:

post系列方法:
 public final boolean post(Runnable r){
    return  sendMessageDelayed(getPostMessage(r), 0);
 }
複製程式碼

可以看到不管哪個post方法中,都是通過getPostMessage()方法構建一個Message最終還是呼叫對應的sendMessage方法。

  private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
複製程式碼

getPostMessage方法中通過Message.obtain()獲取一個Message將傳入的Runnable賦給Message中的callback,接著返回這個Message。

sendMessage系列方法:

因為post方法的最後又都呼叫了對應的sendMessage方法,所以接下來看sendMessage方法的實現:

 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);
    }
    
複製程式碼

這裡看到經過層層呼叫最終執行了enqueueMessage方法。這裡要注意的是sendMessageDelayed方法中設定的延遲時間是通過SystemClock.uptimeMillis() + 延遲時間來計算的。
SystemClock.uptimeMillis()方法是獲取從開機到現在的毫秒數,與System.currentTimeMillis()獲取從1970年1月1日到現在的毫秒數不同,後者會受到手機系統時間影響,而系統時間可以手動修改。sendMessageAtTime方法中對MessageQueue進行是否為null的判斷,為null丟擲異常,這裡的MessageQueue就是在Handler建構函式中Looper中的Queue。

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

複製程式碼

enqueueMessage方法中通過msg.target=this這一句,將Handler的引用傳遞給了Message中的target。接著呼叫了MessageQueue的enqueueMessage方法。接下來進入MessageQueue。

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            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;
    }
複製程式碼

MessageQueue的enqueueMessage方法中,傳入的msg就是要插入到訊息佇列的新訊息,when是這個任務的延時時間。方法裡首先對訊息裡的target也就是Handler進行空判斷,為空丟擲異常。接著判斷了這個訊息是否被使用和訊息佇列是否退出等。看到 msg.when = when;這一行將延遲時間傳遞儲存到訊息內部,下一行定義了一個臨時指標p用來指向mMessage,這個mMessage就是訊息佇列的首結點,接下來的這個if-else做的就是將新訊息根據他的when大小,將他按順序加入到佇列中合適位置上。這裡可以看出這個訊息佇列實際上是個連結串列,每個Message是一個結點,結點中有一個next指標存放下一個結點位置。這裡的新訊息的新增,就是向這個連結串列中插入一個節點。
先看if中判斷,p==null即首結點為null,when=0及延時時間為0,立即執行,when<p.when即新節點的延時時間小於當前連結串列首結點的延時時間,這三種情況下直接將新訊息節點插到連結串列頭部,即msg.next=p;mMessages=msg這兩行的操作,然後喚醒訊息執行緒處理新訊息。else就要將新節點根據when的大小插入到連結串列中合適位置,這裡又定義了一個臨時指標prev,指向p指向的前一個節點,看到for (;;) 迴圈中,將p指標不斷向後移,直到p等於null即連結串列結尾或者新結點的when<p.when的時候,即這個連結串列是按照節點when的從小到大的順序排列插入的。此時break出迴圈,將新節點插入到此處,即msg.next=p;prev.next=msg。到此訊息傳送加入訊息佇列的過程節結束了。

Android進階知識:Handler相關

取出處理訊息:

有往訊息佇列里加訊息,就有從訊息佇列取訊息。誰來取呢?就是Looper,之前看到在Handler的構造方法裡,通過Looper.myLooper()方法獲取到當前執行緒(handler建立所線上程)的Looper物件。而且還知道了建立Handler的執行緒必須存在一個Looper物件否則會丟擲異常。這也是我們不能在子執行緒裡直接建立使用Handler的原因。那麼為什麼主執行緒可以直接建立Handler呢?是因為主執行緒中有Looper。那麼主執行緒的Looper又是哪來的呢?這需要看到ActivityThread類裡的程式碼。

 public static void main(String[] args) {
        ......
       
        Looper.prepareMainLooper();

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

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

       ......
    }
複製程式碼

ActivityThread中main方法在app啟動時呼叫,這裡省去了一些無關程式碼,只看與主執行緒Looper相關的。可以看到在main方法裡呼叫了Looper.prepareMainLooper()方法,之後獲取了一個主執行緒的Handler,接著呼叫了Looper.loop()方法。一個方法一個方法來看,先是prepareMainLooper()方法:

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));
    }
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
複製程式碼

prepareMainLooper()方法中調了prepare(false)方法,這裡quitAllowed傳的是false,這個標記會傳遞到MessageQueue中,這裡說明主執行緒的訊息佇列是不允許退出的。prepare()方法裡初始化了new了一個Looper物件,並將它新增到當前執行緒的ThreadLocal中。接著到Looper的建構函式中看看:

  private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
複製程式碼

Looper的建構函式很簡單,建立了MessageQueue訊息佇列儲存了一個當前執行緒的物件。從這裡看出來建立一個Handler需要傳入建立執行緒的Looper,而建立Looper又對應建立了一個MessageQueue。下面回到main方法中看thread.getHandler()這個獲取主執行緒Handler方法:

   final Handler getHandler() {
        return mH;
    }
複製程式碼

這裡直接返回mH這個Handler,那麼這個Handler是在哪裡建立的呢?

 final H mH = new H();
複製程式碼

跟蹤下去發現這個mH是ActivityThread類的成員變數,並且直接初始化。所以這個Handler就是在main方法中建立ActivityThread物件時就初始化了。最後呼叫Looper.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;
        ......
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ......
            try {
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ......            
        }
    }
複製程式碼

這裡同樣省略了部分程式碼,來看主要的流程,首先還是獲取當前執行緒Looper對空做了校驗。然後從Looper中獲取到MessageQueue,接著進入for迴圈,呼叫queue.next()從訊息佇列中取出訊息,根據註釋,這個方法有可能會阻塞,如果返回的msg為null,說明訊息佇列正在退出。接著在try程式碼塊中呼叫了msg.target.dispatchMessage(msg)方法,這個msg.target就是在前面enqueueMessage方法中設定的傳送訊息的Handler,所以這裡呼叫了Handler的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方法中對callback進行了判斷,這裡有兩個callback,msg.callback是對應Handler中的post方法,將傳入的Runnable存入Message的callback中,如果呼叫post方法msg.callback不為空呼叫handleCallback方法,最終會執行Runnable的run方法,開始執行post時傳進來的任務。

private static void handleCallback(Message message) {
        message.callback.run();
    }
複製程式碼

第二個mCallback,對應的是建立Handler時的傳參,如果不為空會執行mCallback.handleMessage方法。如果初始化時沒傳mCallback,就會執行handleMessage(msg)方法:

   /**
     * Subclasses must implement this to receive messages.
     */
    public void handleMessage(Message msg) {
    }
複製程式碼

這個方法就是我們自己需要實現的處理訊息的方法,也是我們最常用重寫的方法。至此UI主執行緒中建立Handler,Looper,並且Looper開啟輪詢到呼叫了Handler的dispatchMessage處理訊息的過程就結束了。

子執行緒Handler使用:

回到上面說的,這是主執行緒中Handler、Looper的初始化,那麼要在子執行緒使用Handler該怎麼做呢?

 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                Handler handler = new Handler(Looper.myLooper()) {
                    @Override
                    public void handleMessage(Message msg) {
                        //處理訊息
                    }
                };
                Looper.loop();
            }
        }).start();
複製程式碼

其實和主執行緒一樣,因為子執行緒中沒有Looper所以需要我們自己建立Looper並且呼叫Looper.loop()方法開始輪詢。這裡的Looper.prepare()方法和prepareMainLooper()方法一樣最終會呼叫prepare(boolean quitAllowed)方法,這時傳入的quitAllowed為true,表示訊息佇列可以退出。
至此Handler機制相關類Handler、Looper、MessageQueue的主要方法原始碼都看完了,他們之間的工作流程相互關係也都清楚了。

Message類:

其實還剩一個Message訊息類,Message類中主要看一個obtain()方法,Message除了可以通過new來建立,還可以通過obtain()方法來獲得,並且obtain()方法是從全域性池中獲取Message物件,能避免重新分配物件。

  public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
複製程式碼

這裡看到只要sPool不等於null,就從sPool頭上去一個訊息節點返回。所以使用obtain方法,相對直接new一個Message能減少記憶體分配。

4、Handler相關面試題

Q1:Handler機制中涉及到了哪些類?作用分別是什麼?

A1:主要涉及到Handler、Looper、MessageQueue、Message這四個類。 Handler:傳送訊息到訊息佇列。
Looper:從訊息佇列中取出訊息,交給Handler的dispatchMessage方法處理。
MessageQueue:訊息佇列儲存管理訊息插入取出。
Message:訊息類,攜帶著訊息資料。

Q2:MessageQueue 中的 Message有順序嗎?如果有是按什麼順序排列的?

A2:通過之前的原始碼閱讀知道,是有順序的,是根據Message.when這個相對時間排列的。

Q3:子執行緒中可以建立 Handler 物件嗎?

A3:同樣從原始碼中可以知道,子執行緒中不能直接建立Handler,Handler建立需要指定一個Looper,子執行緒中沒有Looper,需要先建立Looper,呼叫Looper.loop方法。

Q4:MessageQueue內部實現是一個佇列嗎?

A4:不是,內部實現其實是一個單連結串列。

Q5:Looper的quit方法和quitSafely方法有什麼區別?

A5:quit方法會清空訊息佇列中的所有訊息,quitSafely方法只會清除所有延遲訊息,非延遲訊息還是分發出去交給Handler處理。具體還是看原始碼:

    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }
複製程式碼

這裡實際上是呼叫了MessageQueue的quit方法:

 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();
            }
            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

複製程式碼

quit方法傳入的是false呼叫的是removeAllMessagesLocked()方法,quitSafely傳入的是true呼叫的是removeAllFutureMessagesLocked方法。

 private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }
複製程式碼

removeAllMessagesLocked方法中直接將所有訊息清空。

private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }
複製程式碼

removeAllFutureMessagesLocked方法中先做判斷如果首節點when大於當前時間說明全是延遲訊息,就同樣呼叫removeAllMessagesLocked處理全部清空,否則迴圈找到佇列中when大於now也就是大於當前時間的節點位置,將該節點訊息同其後的所有訊息清空。

Q6:為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死?

A6:這個涉及到Linux的Epoll機制。簡單來說就是Android應用程式的主執行緒在進入訊息迴圈過程前,會在內部建立一個Linux管道(Pipe),這個管道的作用是使得Android應用程式主執行緒在訊息佇列為空時可以進入空閒等待狀態,並且使得當應用程式的訊息佇列有訊息需要處理時喚醒應用程式的主執行緒。
具體解釋:Android中為什麼主執行緒不會因為Looper.loop()裡的死迴圈卡死?

5、Handler引發記憶體洩漏

Handler導致的記憶體洩露,是平時寫程式碼不注意非常容易出現的問題,而且記憶體洩露多了對應用效能影響較大,所以單獨研究下。
一般我們使用Handler更新UI都是這樣的:

public class HandlerActivity extends Activity {
    private TextView mTextView;
    private final int MESSAGE_SEND = 0x01;
    private MyHandler handler = new MyHandler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mTextView = findViewById(R.id.textView);
        Message obtain = Message.obtain();
        obtain.what = MESSAGE_SEND;
        obtain.obj = "文字";
        handler.sendMessageDelayed(obtain, 1000 * 60 * 10);
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == MESSAGE_SEND) {
                mTextView.setText((String) msg.obj);
            }
        }
    }
}

複製程式碼

這裡看到進入Activity就傳送了一個延遲訊息,現實開發中可能是網路有延遲又或者進入一個介面後立刻離開這時資料還沒載入好,只要是耗時任務還沒有完成,當前的Activity又需要銷燬,這時候因為此時MyHander,它隱式持有外部類的引用,當Activity銷燬時,此時非同步耗時任務還沒有結束,仍然持有Activity的引用,使得Activity無法回收,造成記憶體洩漏。 通過整合LeakCanary可以檢測到記憶體洩漏,如下圖:

LeakCanary
同樣通過AndroidStudio Profiler可以檢視到記憶體洩露:
Android進階知識:Handler相關
通過多次開啟關閉HandlerActivity,然後觀察記憶體情況,可以發現即使在我手動GC多次後,仍然存在多個例項沒有被回收的現象。

記憶體洩漏解決方法:

  1. 將Handler定義為靜態,靜態內部類不會持有外部類的引用。
  2. 因為靜態內部類不持有外部類引用,所以在Handler中無法訪問外部類的成員,需要用一個外部類的弱引用來訪問外部成員,又因為是弱引用,在GC時可以將其回收,不會造成記憶體洩露。
  3. 在Activity的onDestory方法中呼叫removeCallbacksAndMessages方法清除訊息佇列。

解決記憶體洩漏:

public class WeakHandlerActivity extends Activity {
    private TextView mTextView;
    private static final int MESSAGE_SEND = 1;
    private MyHandler handler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        mTextView = findViewById(R.id.textView);
        Message obtain = Message.obtain();
        obtain.what = MESSAGE_SEND;
        obtain.obj = "文字";
        handler.sendMessageDelayed(obtain, 1000 * 60 * 10);
    }
   //靜態內部類
    static class MyHandler extends Handler {
        private final WeakReference<WeakHandlerActivity> mActivty;
        public MyHandler(WeakHandlerActivity activity) {
        //初始化Activity的弱引用
            mActivty = new WeakReference<WeakHandlerActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            WeakHandlerActivity activity = mActivty.get();
            if (activity != null) {
                if (msg.what == MESSAGE_SEND) {
                    activity.mTextView.setText((String) msg.obj);
                }
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //清除訊息佇列
        handler.removeCallbacksAndMessages(null);
    }
}
複製程式碼

通過這三個方法結合使用,就可以解決Handler導致的記憶體洩漏的問題。這次再通過Profiler來檢視記憶體情況:

Android進階知識:Handler相關
可以看到在多次開啟關閉介面後,仍然會存在多個WeakHandlerActivity例項。
Android進階知識:Handler相關
但是在GC過後記憶體中的WeakHandlerActivity已經被全部回收,不會繼續佔用記憶體,造成洩漏。

6、總結

  • Handler是Android提供的一種執行緒間通訊方式。因為Android中UI控制元件不是執行緒安全的,多執行緒併發訪問會出現同步問題,所以如果子執行緒想更新UI通常通過Handler來完成執行緒間通訊。

  • Handler的工作流程主要是由Handler傳送訊息,將訊息新增到訊息佇列MessageQueue中,再通過輪詢器Looper從訊息佇列中取出訊息,交給Handler去分發處理訊息對應任務。

  • Handler使用時容易發生記憶體洩露,記得通過靜態內部類+弱引用的方式使用Handler,並且在Activity的onDestory方法裡記得清除訊息佇列。

相關文章