深入探討Android異步精髓Handler

前言

眾所周知, Android 的UI是在其主線程中進行刷新的,所以Google建議開發人員切勿在主線程中進行耗時的操作否則很容易導致應用程序無響應(ANR)。鑒于此幾乎接近硬性的要求,我們常把耗時的操作(比如網絡請求)置于子線程中進行;但是子線程不能直接訪問UI。

至此,這個矛盾就凸顯出來了:

  • 主線程可以刷新UI,但不能執行耗時操作
  • 子線程可以執行耗時操作 ,但是不能直接刷新UI

嗯哼,那有沒有一個東西可以調和并化解這個矛盾呢?當然是有的,Google采用Handler把主線程和子線程精巧地聯系起來——子線程中進行耗時的業務邏輯,然后利用Handler通知主線程刷新UI。除此以外,還有別的方式可以實現類似的操作么?答案是肯定的,我們也可以利用AsyncTask或者IntentService進行異步的操作。這兩者又是怎么做到的呢?其實,在AsyncTask和IntentService的內部亦使用了Handler實現其主要功能。拋開這兩者不談,當我們打開Android源碼的時候也隨處可見Handler的身影。所以,Handler是Android異步操作的核心和精髓,它在眾多領域發揮著極其重要甚至是不可替代的作用。

在此,對Handler的工作原理和實現機制進行系統的梳理。

ThreadLocal簡介及其使用

對于線程Thread大家都挺熟悉的了,但是對于ThreadLocal可能就要陌生許多了。雖然我們對于它不太了解,但是它早在JDK1.2版本中就已問世并且被廣泛的使用,比如 hibernate ,EventBus,Handler都運用了ThreadLocal進行線程相關的操作。如果單純地從ThreadLocal這個名字來看,它帶著濃濃的“本地線程”的味道; 然而,喝一口之后才發現根本就不是這個味兒。其實,ThreadLocal并不是用來操作什么本地線程而是用于實現不同線程的數據副本。當使用ThreadLocal維護變量時,它會為每個使用該變量的線程提供獨立的變量副本;每一個線程都可以獨立地改變自己的副本并且不會影響其它線程所持有的對應的副本。所以,ThreadLocal的實際作用并不與它的名字所暗含的意義相吻合,或許改稱為ThreadLocalVariable(線程本地變量)會更合適一些。

接下來,我們通過一個實例來瞅瞅ThreadLocal的使用方式

/**

 * 原創作者:
 * 谷哥的小弟
 *
 * 博客地址:
 * http://blog.csdn.net/lfdfhl
 */
private void testThreadLocal(){
    mThreadLocal.set("東京熱");
    new HotThread1().start();
    new HotThread2().start();
    hot3=mThreadLocal.get();
    try{
        Thread.sleep(1000*4);
        Log.i(TAG,"HotThread1獲取到的變量值: "+hot1);
        Log.i(TAG,"HotThread2獲取到的變量值: "+hot2);
        Log.i(TAG,"MainThread獲取到的變量值: "+hot3);
    }catch (Exception e){

    }
}

private class HotThread1  extends Thread{
    @Override
    public void run() {
        super.run();
        mThreadLocal.set("北京熱");
        hot1=mThreadLocal.get();
    }
}

private class HotThread2  extends Thread{
    @Override
    public void run() {
        super.run();
        mThreadLocal.set("南京熱");
        hot2=mThreadLocal.get();
    }
}</code></pre> 

查看輸出結果:

HotThread1獲取到的變量值: 北京熱

HotThread2獲取到的變量值: 南京熱

MainThread獲取到的變量值: 東京熱

在這段代碼中使用ThreadLocal保存String類型的數據,并且在主線程和兩個子線程中為ThreadLocal設置了不同的值,然后再將這些值分別取出。結合輸出日志可以發現:在不同的線程中訪問了同一個ThreadLocal對象,但是通過mThreadLocal.get()得到的值卻是不一樣的;也就是說:它們之間沒有發生相互的影響而是保持了彼此的獨立。明白了ThreadLocal的這個特性之后,我們再去理解Looper的工作機制就會容易得多了。

Looper、線程、消息隊列的關系

Google官方建議開發人員使用Handler實現異步刷新UI,我們在平常的工作中也很好地采納了這個提議:首先在主線程中建立Handler,然后在子線程中利用handler.sendMessage(message)發送消息至主線程,最終消息在handleMessage(Message msg) {}得到相應的處理。這個套路,大家都再熟悉不過了;現在換個角度,我們試試在子線程中建立Handler

private class LooperThread  extends Thread{
        @Override
        public void run() {
            super.run();
            Handler handler=new Handler();
            //doing something
        }
    }

此處的代碼很簡單:LooperThread繼承自Thread,并且在其run( )方法中新建一個Handler。

嗯哼,再運行一下,喔哦,報錯了:

Can’t create handler inside thread that has not called Looper.prepare().

咦,有點出師不利呢,剛開始試就出錯了…….沒事,生活不就是無盡的挫折和希望嘛,這點小事嘛也不算。既然是在調用Handler的構造方法時報的錯那就從該構造方法的源碼入手,一探究竟:

public Handler() {
    this(null, false);
}

public Handler(Callback callback) {
    this(callback, false);
}

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

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

請注意第20行代碼:

如果mLooper == null那么系統就會拋出剛才的錯誤:Can’t create handler inside thread that has not called Looper.prepare()。這句話的意思是:如果在線程內創建handler必須調用Looper.prepare()。既然這個提示已經提示了我們該怎么做,那就加上這一行代碼:

private class LooperThread  extends Thread{
        @Override
        public void run() {
            super.run();
            Looper.prepare();
            Handler handler=new Handler();
            System.out.println("add code : Looper.prepare()");
            //doing something
        }
    }

嘿嘿,果然不再報錯了,運行一下:

既然Looper.prepare()解決了這個問題,那我們就去瞅瞅在該方法中做了哪些操作:

/**Initialize the current thread as a looper.
 * This gives you a chance to create handlers that then reference
 * this looper, before actually starting the loop. Be sure to call
 * loop() after calling this method, and end it by calling quit().
 */
public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

從這段源碼及其注釋文檔我們可以看出:

  1. 在prepare()中利用一個Looper來初始化當前線程或者說初始化一個帶有Looper的線程。

    請注意第14行代碼,它是這段源碼的核心,現對其詳細分析:

    sThreadLocal.set(new Looper(quitAllowed));

    在該行代碼中一共執行了兩個操作

    (1) 構造Looper

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

    在Looper的構造方法中初始化了一個消息隊列MessageQueue和一個線程Thread。從這可看出:一個Looper對應著一個消息隊列以及當前線程。

    當收到消息Message后系統會將其存入消息隊列中等候處理。至于Looper,它在Android的消息機制中擔負著消息輪詢的職責,它會不間斷地查看MessageQueue中是否有新的未處理的消息;若有則立刻處理,若無則進入阻塞。

    (2) 將此Looper保存到sThreadLocal中。

    此處的sThreadLocal是定義在Looper類中的一個ThreadLocal類型變量

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    Looper是framework中的一個類,sThreadLocal是它的一個static final變量。當在某一個Thread中執行Looper.prepare()時系統就會將與該Thread所對應的Looper保存到sThreadLocal中。不同的線程對著不同的Looper,但它們均由系統保存在sThreadLocal中并且互不影響,相互獨立;并且可以通過sThreadLocal.get()獲取不同線程所對應的Looper.

  2. 在調用prepare()方法后需要調用loop()方法開始消息的輪詢,并且在需要的時候調用quit()方法停止消息的輪詢
  3. 假若再次執行Looper.prepare()系統發現sThreadLocal.get()的值不再為null于是拋出異常:
    Only one Looper may be created per thread,一個線程只能創建一個Looper!

小結:

  1. 一個線程對應一個Looper
  2. 一個Looper對應一個消息隊列
  3. 一個線程對應一個消息隊列
  4. 線程,Looper,消息隊列三者一一對應

所以,在一個子線程中使用Handler的方式應該是這樣的:

class LooperThread extends Thread { 
    public Handler mHandler;
    public void run() { 
        Looper.prepare(); 
        mHandler = new Handler() { 
            public void handleMessage(Message msg) { 

            } 
        };
        Looper.loop(); 
      } 
  }

看到這個范例,有的人可能心里就犯嘀咕了:為什么我們平常在MainActivity中使用Handler時并沒有調用Looper.prepare()也沒有報錯呢?

這是因為UI線程是主線程,系統會自動調用Looper.prepareMainLooper()方法創建主線程的Looper和消息隊列MessageQueue

Message的發送和處理過程

在討論完Looper、線程、消息隊列這三者的關系之后我們再來瞅瞅Android消息機制中對于Message的發送和處理。

平常最常用的方式:

handler.sendMessage(message)——>發送消息

handleMessage(Message msg){}——>處理消息

先來分析消息的入隊。

Handler可以通過post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法發送消息,除了postAtFrontOfQueue()之外這幾個方法均會執行到sendMessageAtTime(Message msg, long uptimeMillis)方法,源碼如下:

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

public final boolean sendMessageAtFrontOfQueue(Message msg) {
    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, 0);
}

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

在這里可以看到sendMessageAtTime()內部又調用了enqueueMessage(),在該方法內的重要操作:

  • 第一步:
    給msg設置了target,請參見代碼第25行此處的this就是當前Handler對象本身。在這就指明了該msg的來源——它是由哪個Handler發出的,與此同時也指明了該msg的歸宿——它該由哪個Handler處理。不難發現,哪個Handler發出了消息就由哪個Handler負責處理。
  • 第二步:

    將消息放入消息隊列中,請參見代碼第29行在enqueueMessage(msg,uptimeMillis)中將消息Message存放進消息隊列中,距離觸發時間最短的message排在隊列最前面,同理距離觸發時間最長的message排在隊列的最尾端。若調用sendMessageAtFrontOfQueue()方法發送消息它會直接調用該enqueueMessage(msg,uptimeMillis)讓消息入隊只不過時間為延遲時間為0,也就是說該消息會被插入到消息隊列頭部優先得到執行。直覺告訴我們此處的消息隊列mQueue就是該線程所對應的消息隊列。可是光有直覺是不夠的甚至是不可靠的。我們再回過頭瞅瞅Handler的構造方法,從源碼中找到確切的依據

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

    (1) 獲取Looper,請參見代碼第10行

    (2) 利用Looper的消息隊列為mQueue賦值,請參見代碼第15行

    (3) 為mCallback賦值,,請參見代碼第16行

    (4) 為mAsynchronous賦值,,請參見代碼第17行

    嗯哼,看到了吧,這個mQueue就是從Looper中取出來的。在之前我們也詳細地分析了Looper、線程、消息隊列這三者的一一對應關系,所以此處的mQueue正是線程所對應的消息隊列。

看完了消息的入隊,再來分析消息的出隊。

請看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;

    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    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.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);
        }

        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        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();
    }
}

我們發現關于消息的處理是在一個死循環中就行的,請參見代碼第13-55行。也就是說在該段代碼中Looper一直在輪詢消息隊列MessageQueue。假若消息隊列中沒有未處理的消息(即queue.next()==null)則其進入阻塞block狀態,假若消息隊列中有待處理消息(即queue.next()!=null)則利用msg.target.dispatchMessage(msg)將該消息派發至對應的Handler。

到了這,可能有的人會有一個疑問:系統怎么知道把消息發送給哪個Handler呢?

嘿嘿,還記不記得enqueueMessage()中系統給msg設置了target從而確定了其目標Handler么?嗯哼,所以只要通過msg.target.dispatchMessage(msg)就可以將消息派發至對應的Handler了。那在dispatchMessage()中又會對消息做哪些操作呢?我們繼續跟進源碼

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

哇哈,看到這,心情就舒暢多了,基本上回到了我們熟悉的地方;在此處對Message消息進行了處理,我們來瞅瞅主要的步驟

  • 第一步:
    處理Message的回調callback,請參見代碼第3行
    比如調用handler.post(Runnable runnable)時,該runnable就會被系統封裝為Message的callback。
    關于這點在源碼中也有非常直觀的體現:
    private static Message getPostMessage(Runnable r) {
       Message m = Message.obtain();
       m.callback = r;
       return m;
    }
  • 第二步:
    處理Handler的回調callback,請參見代碼第6行
    比如執行Handler handler=Handler(Callback callback)時就會將callback賦值給mCallback,關于這點已經在介紹Handler構造方法時分析過了,不再贅述。
  • 第三步:
    調用handleMessage()處理消息Message,請參見代碼第10行

    handleMessage()的源碼如下:

    public void handleMessage(Message msg) {
    
    }

    嗯哼,它是一個空的方法。所以Handler的子類需要覆寫該方法,并在其中處理接收到的消息。

梳理Handler工作機制

至此,關于Handler的異步機制及其實現原理已經分析完了。在此,對其作一個全面的梳理和總結。

Android異步消息機制中主要涉及到:Thread、Handler、MessageQueue、Looper,在整個機制中它們扮演著不同的角色也承擔著各自的不同責任。

  • Thread負責業務邏輯的實施。
    線程中的操作是由各自的業務邏輯所決定的,視具體情況進行。
  • Handler負責發送消息和處理消息。
    通常的做法是在主線程中建立Handler并利用它在子線程中向主線程發送消息,在主線程接收到消息后會對其進行處理
  • MessageQueue負責保存消息。
    Handler發出的消息均會被保存到消息隊列MessageQueue中,系統會根據Message距離觸發時間的長短決定該消息在隊列中位置。在隊列中的消息會依次出隊得到相應的處理。
  • Looper負責輪詢消息隊列。
    Looper使用其loop()方法一直輪詢消息隊列,并在消息出隊時將其派發至對應的Handler.

為了更好地理解這幾者的相互關系及其作用,請參見如下示圖

使用Handler的錯誤姿勢及其潛在風險

關于Handler的具體用法,尤其是那些常規的使用方式在此就不再一一列舉了。

我們要討論和分析的是在開發中不恰當地使用Handler的方式及其帶來的潛在風險。

第一個問題:

利用handler.post(Runnable runnable)執行耗時操作

請看如下示例:

/**
 * 原創作者:
 * 谷哥的小弟
 *
 * 博客地址:
 * http://blog.csdn.net/lfdfhl
 */
public class MainActivity extends AppCompatActivity {
    private TextView mTextView;
    private Handler mHandler;
    private ImageView mImageView;
    private Resources mResources;
    private static final String TAG="stay4it";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }
    private void init(){
        mResources=getResources();
        mHandler=new Handler();
        mTextView=(TextView) findViewById(R.id.textView);
        mImageView =(ImageView) findViewById(R.id.imageView);
        mImageView.setOnClickListener(new OnClickListenerImpl());
        long threadID=Thread.currentThread().getId();
        Log.i(TAG,"主線程的線程ID="+threadID);
    }

    private class OnClickListenerImpl implements View.OnClickListener {
        @Override
        public void onClick(View v) {
            new TestThread().start();
        }
    }

    private class TestThread extends Thread{
        @Override
        public void run() {
            super.run();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    String text=mResources.getString(R.string.text);
                    long threadID=Thread.currentThread().getId();
                    Log.i(TAG,"在post(Runnable r)里的run()獲取到線程ID="+threadID);
                    mTextView.setText(text);
                }
            });
        }
    }
}

運行一下,觀察效果:

我們在開發中可能會做如上的操作:在主線程中創建Handler,然后在子線程里利用handler.post(Runnable runnable)執行某些操作甚至是耗時的操作。可是這么做合適么?我們來看看主線程的ID和在Runnable的run()方法里獲取到的線程ID,輸出日志如下:

主線程的線程ID=1

在post(Runnable r)里的run()獲取到線程ID=1

在這里我們發現在兩處獲得的線程ID是同一個值,也就是說Runnable的run()方法并不是在一個新線程中執行的,而是在主線程中執行的。

為什么明明把handler.post(Runnable runnable)放入到子線程中了但是Runnable的run()卻在主線程中執行呢?

其實,這個問題在之前的分析中已經提到了:調用handler.post(Runnable runnable)時,該runnable會被系統封裝為Message的callback。所以,handler.post(Runnable runnable)和handler.sendMessage(Message message)這兩個不同的方法在本質上是相同的——Handler發送了一條消息。在該示例中handler是在主線程中創建的,所以它當然會在主線程中處理消息;如此以來該Runnable亦會在主線程中執行;所以,在Runnable的run()方法中執行耗時的操作是不可取的容易導致應用程序無響應。

那么,調用view.post(Runnable runnable)會在子線程中執行還是主線程中執行呢?

我們來瞅瞅它的實現:

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    getRunQueue().post(action);
    return true;
}

看到這段源碼就無需再做過多的解釋了,它依然是在主線程中執行的,原理同上。

那么,調用Activity.runOnUiThread(Runnable runnable)方法會在子線程中執行還是主線程中執行呢?

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

嗯哼,這段源碼就更簡單了。如果當前線程是UI線程,那么該Runnable會立即執行;如果當前線程不是UI線程,則使用handler的post()方法將其放入UI線程的消息隊列中。

小結:

handler.post(Runnable runnable)、view.post(Runnable runnable)、Activity.runOnUiThread(Runnable runnable)的runnable均會在主線程中執行,所以切勿在其run()方法中執行耗時的操作

第二個問題:

Handler導致的潛在內存泄露

請看如下示例:

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

/**
 * 原創作者:
 * 谷哥的小弟
 *
 * 博客地址:
 * http://blog.csdn.net/lfdfhl
 */
public class MainActivity extends AppCompatActivity {
    private Handler mHandler;
    private static final String TAG="stay4it";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.i(TAG,"handle message");
            }
        };
        Message message=Message.obtain();
        message.what=9527;
        mHandler.sendMessage(message);

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {

            }
        }, 1000 * 20);
    }
}

以上是我們在工作中常見的對于Handler的使用方式,為了更形象地說明問題特意把Runnable所延遲時間設置得比較久。如此操作,猛地一看覺得沒啥不妥當的地方;但是簡單地分析一下這段代碼,卻發現它存在潛在的內存泄露風險。

  • 內部類new Handler(){}持有外部類MainActivity的引用
  • 內部類new Runnable(){}持有外部類MainActivity的引用
  • new Runnable(){}會被封裝成Message的callback且Message會持有Handler的引用
  • handler發送了延遲消息,所以消息隊列持該Runnable的引用
  • 綜合這幾者,可理解為消息間接地持有了MainActivity的引用

現在假設這么一種情況:進入該Activity后在20秒以內的任意時間旋轉屏幕,此時會導致Activity重新繪制。但是通過postDelayed()發出的Runnable還未被執行,所以消息隊仍列持有Runnable的引用,而Runnable也依然持有Activity的引用,故此時Activity所占內存并不能向期望的那樣被回收,這樣就可能會造成內存的泄漏。

優化該問題的方式有多種,在此展示其中一種比較好的實現方式:

/**
 * 原創作者:
 * 谷哥的小弟
 *
 * 博客地址:
 * http://blog.csdn.net/lfdfhl
 */
public class MainActivity extends AppCompatActivity {
    private Activity mActivity;
    private static final String TAG="stay4it";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(){
        mActivity=this;
        BetterHandler betterHandler = new BetterHandler(mActivity);
        Message message=Message.obtain();
        message.what=9527;
        betterHandler.sendMessage(message);
        betterHandler.postDelayed(new BetterRunnable(), 1000 * 20);
    }

    private static class BetterRunnable implements Runnable {
        @Override
        public void run() {
            Log.i(TAG,"Runnable run()");
        }
    }

    private static class BetterHandler extends Handler {
        private WeakReference<Activity> activityWeakReference;

        public BetterHandler(Activity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (activityWeakReference.get() != null) {
                Log.i(TAG,"handle message");
            }
        }
    }
}

看到這段代碼,我們發現了一個陌生的東西WeakReference。什么是WeakReference呢?它有什么特點呢?

從JDK1.2開始, Java 把對象的引用分為四種級別,這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。

  1. 強引用
    我們一般使用的就是強引用,垃圾回收器一般都不會對其進行回收操作。當內存空間不足時Java虛擬機寧愿拋出OutOfMemoryError錯誤使程序異常終止,也不會回收具有強引用的對象
  2. 軟引用
    如果一個對象具有軟引用(SoftReference),在內存空間足夠的時候GC不會回收它,如果內存空間不足了GC就會回收這些對象的內存空間。
  3. 弱引用
    如果一個對象具有弱引用(WeakReference),那么當GC線程掃描的過程中一旦發現某個對象只具有弱引用而不存在強引用時不管當前內存空間足夠與否GC都會回收它的內存。由于垃圾回收器是一個優先級較低的線程,所以不一定會很快發現那些只具有弱引用的對象。為了防止內存溢出,在處理一些占用內存大而且生命周期較長的對象時候,可以盡量使用軟引用和弱引用.
  4. 虛引用
    虛引用(PhantomReference)與其他三種引用都不同,它并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。所以,虛引用主要用來跟蹤對象被垃圾回收器回收的活動,在一般的開發中并不會使用它

嗯哼,在了解這四種引用之后我們繼續分析剛才的代碼。在該示例中做了如下主要操作:

  • 將Handler和Runnable定義為Activity的靜態內部類這兩者定義為靜態內部類后它們就不再持有外部類(Activity)的引用,具體代碼請參見示例中BetterHandler和BetterRunnable的實現
  • 在BetterHandler內使用弱引用WeakReference持有Activity在完成這兩步操作之后,我們再來分析剛才的場景:進入該Activity后在20秒以內的任意時間旋轉屏幕導致Activity重新繪制。此時,消息持有Handler的引用,但Handler對象不再持有Activity的強引用,所以系統可以回收該Activity從而避免了內存泄露的發生。對于這樣的做法,可能有的人覺得不是特別好理解,那我再換一種直白的通俗的描述:如果直接將Activity傳入BetterHandler中并且不對其使用WeakReference那么它依然是一個強引用,這和之前未優化的代碼相比是沒有任何差別的。假若把Activity傳進BetterHandler之后并用WeakReference“包裹”了它,使之不再是一個強引用而變成了一個弱引用。當Activity發生重繪時,GC發現對于這個Activity沒有強引用而只存在一個弱引用,那么系統就將其回收。
  • 在handleMessage( )對Activity進行非空判斷因為Activity可能已經被GC回收,所以在處理消息時要判斷Activity是否為null,即if(activityWeakReference.get() != null)從而避免異常的發生。

后語

我想能看到這的人已經不多了。

大家都知道,Handler在Android體系中占有具有舉足輕重的作用,只有掌握了Handler的實現原理和工作機制才可能更全面,更深入地掌握Android開發技能。在本文中我力圖詳細地闡述Handler相關技術,所以文章的篇幅偏長了。如果你耐著性子看到了此處,請為自己點個贊;你的堅持和努力不會白費,它們會讓你變得更好。

 

來自:http://www.androidchina.net/6053.html

 

 本文由用戶 BiancaCress 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!