前言
作爲一個Android程序員Handler機制使用的十分頻繁,基本使用方法我不會再去累贅,本篇文章意在闡述自己在使用過程中遇到的問題以及閱讀源碼後得到的收穫。
首先可以先思考下面這幾個問題(文末我附了自己的見解)
- 子線程有哪些更新UI線程的方法?
- Activity的runOnUiThread(Runnable action) 是如何實現在子線程中更新UI的?
- Handler導致的內存泄露問題?
源碼分析
從使用流程進行源碼分析,使用Handler一般就是以下幾個過程(子線程更新UI線程)
1、在主線程新建Handler並重寫hanleMessage方法
2、創建子線程,在子線程中創建Message對象
3、使用Handler的sendMessage方法發送消息
4、從MessageQueue中取出消息來更新ui
非常簡單就能實現子線程更新UI線程的功能,下面我們從源碼中分析一下是如何實現的
- 首先創建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: " +
klass.getCanonicalName());
}
}
// 1. 指定Looper對象,myLooper方法返回的是當前線程的Looper對象,也就是主線程的Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//2. 獲取主線程的MessageQueue
mCallback = callback; //3. 將handle的callback複製給mCallback,這裏注意最後dispatchMessage的時候就是調
// 用的它的 handleMessage方法
mAsynchronous = async;
}
至此可以看出Handle取得了主線程的Looper對象和MessageQueue對象
- 然後是第二步創建子線程並創建message對象
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.obj = "s";
handler.sendMessage(message);
}
}).start();
- 第三步我們調用handler.sendMessage將Message對象發送出去
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);//調用sendMessageDelayed,第二個參數爲停留的時間爲 0,也就是直接發送
}
v
v
v
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);//最終會調用sendMessageAtTime
}
v
v
v
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue; //獲取Handler對應想成的MessageQueue
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);//調用Handler的enqueueMessage方法
}
v
v
v
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//將Message的target賦值給當前Handler
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//調用MessageQueue的enqueueMessage將Message加入到MessageQueue 中
}
此時MessageQueue中就已經有了一個"持有當前Handler的Message對象"(將handler賦值給了Message的targer字段)。
然後要做的事情就是從MessageQueue中取出Message對象,並調用Message的target對象的dispatchMessage方法拿到數據。
因爲我們是在主線程中創建的Handler
所以Activity建立的時候就已經初始化的Looper對象,也就是調用了Looper.prepareMainLooper()、Looper.loop()
Looper.loop()的作用就是不斷的從MessageQueue中讀取Message對象
public static void main(String[] args) {
... // 僅貼出關鍵代碼
Looper.prepareMainLooper();
// 1. 類似於Looper.prepare()爲主線程創建1個Looper對象、
ActivityThread thread = new ActivityThread();
// 2. 創建主線程
Looper.loop();
// 3.不斷的從MessageQueue中讀取Message對象
}
v
v
v
public static void loop() {
// 獲取當前Looper
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;
// 獲取Looper實例中的MessageQueue
for (;;) {
// 從消息隊列中取出消息
Message msg = queue.next();
if (msg == null) {
return;
}
// 拿到Message的target(也就是Handler)並調用Handler的dispatchMessage方法
msg.target.dispatchMessage(msg);
// 釋放消息佔據的資源
msg.recycle();
}
}
v
v
v
public void dispatchMessage(Message msg) {
// 如果msg.callback屬性不爲空,則代表使用了post(Runnable r)發送消息
// 則執行handleCallback(msg),即回調Runnable對象裏複寫的run()
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果msg.callback屬性爲空,則代表使用了sendMessage(Message msg)發送消息
// 則回調複寫的handleMessage(msg)
handleMessage(msg);
}
}
v
v
v
//handler.sendMessage(Message message)
public void handleMessage(Message msg) {
... // 創建Handler實例時複寫
}
//handler.post(Runnable runnable)
private static void handleCallback(Message message) {
message.callback.run();
//runnable的run方法需要執行的代碼
}
問題解答
1. 子線程有哪些更新UI線程的方法?
- 主線程中定義Handler,子線程通過mHander發送消息,主線程Handler的handleMessage更新UI
- 用Activity對象的runOnUiThread方法(也是調用handler.post)
- Handler.post(Runnable r)
- 子線程中創建Handler,傳入getMainLooper(也就是將主線程的Looper交給子線程使用)
2. Activity的runOnUiThread(Runnable action) 是如何實現子線程更新UI的?
- runOnUiThread程序首先會判斷當前線程是否是UI線程,如果是就直接運行,如果不是則mHandler.post(runnable);
- post會調用sendMessageDelayed(getPostMessage( r ), 0);並將runnable封裝成Message對象將runnable賦值給Message的callback
- 再經過加入到MessageQueue->looper循環取出,調用callback就能在主線程運行了
- 源碼如下:
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);//如果不在主線程則使用post方法
} else {
action.run();
}
}
v
v
v
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);//getPostMessage返回一個Message對象
}
v
v
v
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();//創建一個Message對象,並設置callback爲傳入的runnable,與前面的Handler的
// dispatchMessage方法結合理解,就會調用handleCallback來執行runnable內容了
m.callback = r;
return m;
}
3. Handler導致的內存泄露問題?
原因:
關閉Activity的時候,Handler消息隊列 還有未處理的消息 /或者正在處理消息時,消息隊列中的Message持有Handler實例的引用,然而Handler又持有Activity的引用,這樣就導致Activity無法回收
解決方法:
使用靜態內部類的Handler+弱引用
public class MainActivity extends AppCompatActivity {
private myHandler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new myHandler(this);
new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = Message.obtain();
msg.what = 1;
handler.sendMessage(msg);
}
}.start();
}
private static class myHandler extends Handler{
// 定義 弱引用實例
private WeakReference<Activity> reference;
public myHandler (Activity activity) {
// 使用WeakReference弱引用持有Activity實例
reference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i("xiaoqiang", "收到消息");
break;
}
}
}
}