Handler匣屡、Looper、messagequeue源碼分析及使用(1)
Handler拇涤、Looper捣作、messagequeue源碼分析及使用(2)
一、什么是handler
handler通過發(fā)送和處理Message/Runnable對象來關聯(lián)相應線程的MessageQueue鹅士。
- 可以讓對應的Message/Runnable在未來的某個時間點進行相應處理券躁。
- 讓自己想要處理的耗時操作放在子線程,讓更新ui的操作放在主線程。
簡單直白的來說也拜,因為android中不能在子線程中更新ui以舒,同時主線程又不能做耗時操作(會堵塞ui),而handler可以很方便的幫助我們在主線程和子線程中切換慢哈,達到在主線程中更新ui蔓钟,在子線程中做耗時操作,handler在這里充當?shù)木褪且粋€消息的發(fā)送者和處理者的身份岸军。
二奋刽、handler的使用
1、如何創(chuàng)建主線程的handler艰赞,實現(xiàn)在主線程更新UI佣谐,在子線程做耗時操作?
代碼 2-1
public class UiHandlerActivcity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ui_handler_activcity);
mUiTextView = findViewById(R.id.ui_textview);
startThread();
}
private static final int MSG_WHAT = 0x100;
private final String TAG = this.getClass().getSimpleName();
private TextView mUiTextView;
private Thread mThread = null;
private Handler mUiHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "UI線程 ID=" + Looper.getMainLooper().getThread().getId());
Log.d(TAG, "Handler's handleMessage() 運行的線程 ID=" + Thread.currentThread().getId());
if (msg.what == MSG_WHAT) {
mUiTextView.setText(String.valueOf(msg.arg1));
}
}
};
private void startThread() {
mThread = new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Thread's run() 運行的線程 ID=" + Thread.currentThread().getId());
try { // 模擬耗時操作
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message message = Message.obtain();
message.what = MSG_WHAT;
message.arg1 = 500;
mUiHandler.sendMessage(message);
}
});
mThread.start();
}
}
打印結(jié)果:
02-02 12:27:59.384 25774-25791/com.ktln.must D/UiHandlerActivcity: Thread's run() 運行的線程 ID=2232
02-02 12:28:04.384 25774-25774/com.ktln.must D/UiHandlerActivcity: UI線程 ID=1
02-02 12:28:04.384 25774-25774/com.ktln.must D/UiHandlerActivcity: Handler's handleMessage() 運行的線程 ID=1
從運行結(jié)果來看方妖,mUiHandler.handleMessage()
的運行線程和UI線程是同一個狭魂,而mThread.run()
則運行在非主線程中。
2党觅、如何創(chuàng)建非UI線程的handler雌澄,實現(xiàn)UI線程發(fā)送消息通知非UI線程做耗時操作?
代碼 2-2
public class NoUiHandlerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_no_ui_handler);
startThread();
}
private static final String TAG = NoUiHandlerActivity.class.getSimpleName();
private void startThread() {
MyThread myThread = new MyThread();
myThread.start();
myThread.getHandler().post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "UI線程 ID=" + Looper.getMainLooper().getThread().getId());
Log.d(TAG, "Runnable's run() 運行的線程 ID=" + Thread.currentThread().getId());
}
});
}
private static class MyThread extends Thread {
private Handler mHandler;
public MyThread() { super(); }
@Override
public void run() {
Looper.prepare();
synchronized (this) {
mHandler = new Handler(); // 或者 mHandler = new Handler(Looper.myLooper());
notifyAll();
}
Looper.loop();
}
public Handler getHandler() {
synchronized (this) {
// 因為異步執(zhí)行杯瞻,在其他線程調(diào)用時候handler可能還沒有創(chuàng)建好镐牺,這是使用會報空指針錯誤,
// 所以通過代碼塊加鎖魁莉,wait和notifyAll睬涧,來解決這一問題
while (mHandler == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mHandler;
}
}
}
打印結(jié)果:
02-02 14:11:21.054 31781-31804/com.ktln.must D/NoUiHandlerActivity: UI線程 ID=1
02-02 14:11:21.054 31781-31804/com.ktln.must D/NoUiHandlerActivity: Runnable's run() 運行的線程 ID=2320
從運行結(jié)果來看,UI線程的ID=1
旗唁,而Runnable.run()的運行線程ID=2320
畦浓,所以可以斷定這個handler是運行在非UI線程中的。
三检疫、創(chuàng)建handler的重要步驟
-
Looper.prepare();
讶请,給目標線程創(chuàng)建一個Looper
對象,如果目標線程已經(jīng)存在Looper
對象屎媳,則不需要再創(chuàng)建夺溢; -
Looper.loop();
,讓目標線程的Looper
對象烛谊,循環(huán)讀取消息隊列MessageQueue
风响; -
new Handler(Looper.myLooper());
,使用目標Looper
對象創(chuàng)建Handler
對象晒来,再使用這個handler
對象發(fā)送消息(Message/Runnable)钞诡;
你可能會問上面代碼2-1中沒有構(gòu)造Looper
啊,為什么也能正常執(zhí)行?
通過Android應用程序的啟動荧降,我們知道android應用程序的入口接箫,即ActivityThread的main方法,來看下源碼
public static void main(String[] args) {
..........
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
...........
Looper.loop();
}
- 其中調(diào)用了
Looper.prepareMainLooper();
創(chuàng)建Looper
朵诫,Looper.loop();
開啟輪詢辛友,由此可見所有的Handler創(chuàng)建都是一個流程; - 并且通過
ActivityThread
入口的main()
能夠看出剪返,只要Looper.loop();
停止輪詢废累,我們的android應用程序也就退出了,即Looper.quit();
就會結(jié)束應用脱盲,退出程序邑滨。
四、使用handler中的內(nèi)存泄漏問題
對handler熟悉的同學一定會發(fā)現(xiàn)钱反,我上面寫的測試代碼都存在一個問題掖看,那就是有可能發(fā)生內(nèi)存泄漏!C娓纭哎壳!
1、為什么會發(fā)生內(nèi)存泄漏那尚卫?
我們拿代碼2-1
來說归榕,private Handler mUiHandler = new Handler() ;
由于mUiHandler
不是一個靜態(tài)內(nèi)部類,所以mUiHandler
隱匿的持有UiHandlerActivcity
的引用吱涉,當UiHandlerActivcity
釋放時刹泄,mUiHandler
如果內(nèi)部正在做一些耗時操作,這時會導致UiHandlerActivcity
無法及時回收邑飒,而導致UiHandlerActivcity
停留在堆內(nèi)存中循签,繼而導致內(nèi)存泄漏级乐。
2疙咸、怎么解決內(nèi)存泄漏問題?
其實百度一下风科,你就會發(fā)現(xiàn)很多相關的文章撒轮,我這里總結(jié)下。
- 聲明一個靜態(tài)內(nèi)部類的handler對象
-
Activity
的onDestroy()
方法中調(diào)用Handler.removeCallbacksAndMessages(null);
- 如果你在靜態(tài)內(nèi)類的
handler
中使用外部類贼穆,需要使用弱引用题山,即WeakReference<>
3、修改代碼2-1
故痊,防止內(nèi)存泄漏
public class UiHandlerActivcity extends AppCompatActivity {
private static final int MSG_WHAT = 0x100;
private static final String TAG = UiHandlerActivcity.class.getSimpleName();
private TextView mUiTextView;
private Thread mThread = null;
private static Handler mUiHandler = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ui_handler_activcity);
mUiTextView = findViewById(R.id.ui_textview);
mUiHandler = new UiHandler(this);
startThread();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mUiHandler != null)
mUiHandler.removeCallbacksAndMessages(null);
}
private void startThread() {
mThread = new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Thread's run() 運行的線程 ID=" + Thread.currentThread().getId());
try {
// 模擬耗時操作
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (mUiHandler != null) {
Message message = Message.obtain();
message.what = MSG_WHAT;
message.arg1 = 500;
mUiHandler.sendMessage(message);
mUiHandler.removeCallbacksAndMessages(null);
}
}
});
mThread.start();
}
private static final class UiHandler extends Handler {
private WeakReference<UiHandlerActivcity> reference = null;
public UiHandler(UiHandlerActivcity activcity) {
super();
this.reference = new WeakReference<>(activcity);
}
@Override
public void handleMessage(Message msg) {
if (reference == null || reference.get() == null) { return; }
UiHandlerActivcity activcity = reference.get();
Log.d(TAG, "UI線程 ID=" + Looper.getMainLooper().getThread().getId());
Log.d(TAG, "Handler's handleMessage() 運行的線程 ID=" + Thread.currentThread().getId());
if (msg.what == MSG_WHAT) {
activcity.mUiTextView.setText(String.valueOf(msg.arg1));
activcity.startActivity(new Intent(activcity, NoUiHandlerActivity.class));
}
}
}
}
Handler顶瞳、Looper、messagequeue源碼分析及使用(1)
Handler、Looper慨菱、messagequeue源碼分析及使用(2)