編輯:關於Android編程
Handler
每個初學Android開發的都繞不開Handler這個“坎”,為什麼說是個坎呢,首先這是Android架構的精髓之一,其次大部分人都是知其然卻不知其所以然。今天看到Handler.post這個方法之後決定再去翻翻源代碼梳理一下Handler的實現機制。
異步更新UI
先來一個必背口訣“主線程不做耗時操作,子線程不更新UI”,這個規定應該是初學必知的,那要怎麼來解決口訣裡的問題呢,這時候Handler就出現在我們面前了(AsyncTask也行,不過本質上還是對Handler的封裝),來一段經典常用代碼(這裡忽略內存洩露問題,我們後面再說):
首先在Activity中新建一個handler:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case 0: mTestTV.setText("This is handleMessage");//更新UI break; } } };
然後在子線程裡發送消息:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子線程有一段耗時操作,比如請求網絡 mHandler.sendEmptyMessage(0); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
至此完成了在子線程的耗時操作完成後在主線程異步更新UI,可是並沒有用上標題的post,我們再來看post的版本:
new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000);//在子線程有一段耗時操作,比如請求網絡 Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { mTestTV.setText("This is post");//更新UI } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
從表面上來看,給post方法傳了個Runnable,像是開了個子線程,可是在子線程裡並不能更新UI啊,那麼問題來了,這是怎麼個情況呢?帶著這個疑惑,來翻翻Handler的源碼:
先來看看普通的sendEmptyMessage是什麼樣子:
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); }
將我們傳入的參數封裝成了一個消息,然後調用sendMessageDelayed:
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
再調用sendMessageAtTime:
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); }
好了,我們再來看post():
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0);//getPostMessage方法是兩種發送消息的不同之處 }
方法只有一句,內部實現和普通的sendMessage是一樣的,但是只有一點不同,那就是 getPostMessage(r) 這個方法:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
這個方法我們發現也是將我們傳入的參數封裝成了一個消息,只是這次是m.callback = r,剛才是msg.what=what,至於Message的這些屬性就不看了
Android消息機制
看到這裡,我們只是知道了post和sendMessage原理都是封裝成Message,但是還是不清楚Handler的整個機制是什麼樣子,繼續探究下去。
剛才看到那兩個方法到最終都調用了sendMessageAtTime
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,看名字應該是把消息加入隊列的意思,點進去看下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
mAsynchronous這個異步有關的先不管,繼續將參數傳給了queue的enqueueMessage方法,至於那個msg的target的賦值我們後面再看,現在繼續進入MessageQueue類的enqueueMessage方法,方法較長,我們看看關鍵的幾行:
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;
果然像方法名說的一樣,一個無限循環將消息加入到消息隊列中(鏈表的形式),但是有放就有拿,這個消息怎樣把它取出來呢?
翻看MessageQueue的方法,我們找到了next(),代碼太長,不贅述,我們知道它是用來把消息取出來的就行了。不過這個方法是在什麼地方調用的呢,不是在Handler中,我們找到了Looper這個關鍵人物,我叫他環形使者,專門負責從消息隊列中拿消息,關鍵代碼如下:
for (;;) { Message msg = queue.next(); // might block ... msg.target.dispatchMessage(msg); ... msg.recycleUnchecked(); }
簡單明了,我們看到了我們剛才說的msg.target,剛才在Handler中賦值了msg.target=this,所以我們來看Handler中的dispatchMessage:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
1.msg的callback不為空,調用handleCallback方法(message.callback.run())
2.mCallback不為空,調用mCallback.handleMessage(msg)
3.最後如果其他都為空,執行Handler自身的 handleMessage(msg) 方法
msg的callback應該已經想到是什麼了,就是我們通過Handler.post(Runnable r)傳入的Runnable的run方法,這裡就要提提java基礎了,直接調用線程的run方法相當於是在一個普通的類調用方法,還是在當前線程執行,並不會開啟新的線程。
所以到了這裡,我們解決了開始的疑惑,為什麼在post中傳了個Runnable還是在主線程中可以更新UI。
繼續看如果msg.callback為空的情況下的mCallback,這個要看看構造方法:
1. public Handler() { this(null, false); } 2. public Handler(Callback callback) { this(callback, false); } 3. public Handler(Looper looper) { this(looper, null, false); } 4. public Handler(Looper looper, Callback callback) { this(looper, callback, false); } 5. public Handler(boolean async) { this(null, async); } 6. 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; } 7. public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
具體的實現就只有最後兩個,已經知道mCallback是怎麼來的了,在構造方法中傳入就行。
最後如果這兩個回調都為空的話就執行Handler自身的handleMessage(msg)方法,也就是我們熟知的新建Handler重寫的那個handleMessage方法。
Looper
看到了這裡有一個疑惑,那就是我們在新建Handler的時候並沒有傳入任何參數,也沒有哪裡顯示調用了Looper有關方法,那Looper的創建以及方法調用在哪裡呢?其實這些東西Android本身已經幫我們做了,在程序入口ActivityThread的main方法裡面我們可以找到:
public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); ...
總結
已經大概梳理了一下Handler的消息機制,以及post方法和我們常用的sendMessage方法的區別。來總結一下,主要涉及四個類Handler、Message、MessageQueue、Looper:
新建Handler,通過sendMessage或者post發送消息,Handler調用sendMessageAtTime將Message交給MessageQueue
MessageQueue.enqueueMessage方法將Message以鏈表的形式放入隊列中
Looper的loop方法循環調用MessageQueue.next()取出消息,並且調用Handler的dispatchMessage來處理消息
在dispatchMessage中,分別判斷msg.callback、mCallback也就是post方法或者構造方法傳入的不為空就執行他們的回調,如果都為空就執行我們最常用重寫的handleMessage。
最後談談handler的內存洩露問題
再來看看我們的新建Handler的代碼:
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { ... } };
當使用內部類(包括匿名類)來創建Handler的時候,Handler對象會隱式地持有Activity的引用。
而Handler通常會伴隨著一個耗時的後台線程一起出現,這個後台線程在任務執行完畢後發送消息去更新UI。然而,如果用戶在網絡請求過程中關閉了Activity,正常情況下,Activity不再被使用,它就有可能在GC檢查時被回收掉,但由於這時線程尚未執行完,而該線程持有Handler的引用(不然它怎麼發消息給Handler?),這個Handler又持有Activity的引用,就導致該Activity無法被回收(即內存洩露),直到網絡請求結束。
另外,如果執行了Handler的postDelayed()方法,那麼在設定的delay到達之前,會有一條MessageQueue -> Message -> Handler -> Activity的鏈,導致你的Activity被持有引用而無法被回收。
解決方法之一,使用弱引用:
static class MyHandler extends Handler { WeakReference<Activity > mActivityReference; MyHandler(Activity activity) { mActivityReference= new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg) { final Activity activity = mActivityReference.get(); if (activity != null) { mImageView.setImageBitmap(mBitmap); } } }
以上就是對Android handler 消息機制的資料整理,後續繼續補充相關資料,謝謝大家對本站的支持!
我寫一個開源的安卓文件器。源代碼在github:源代碼 # File_Explorer# Android 文件管理器包常用ui控件有:1.側滑菜單sliding
介紹有時候由於需要一些自定義之後的開源庫,無法使用jCenter裡面的官方庫,又懶得自己搭建Maven倉庫,所以我們想要自己在項目裡面直接導入本地的AAR庫。通用方法和問
矩形碰撞 原理: 兩個矩形位置的四種情況,不是這四種情況則碰撞 圓形碰撞 原理: 利用兩個圓心之間的距離進行判定.當兩個圓心的距離小於半徑之和則碰撞.像素碰撞 :不適用
在這篇文章中,我將介紹如何實現列表中的視頻播放。在流行的應用,如Facebook,Instagram的或Magisto的工作原理相同:Facebook的:Ma