Handler、Looper、messagequeue源码分析及使用(1)
Handler、Looper、messagequeue源码分析及使用(2)
一、什么是handler
handler通过发送和处理Message/Runnable对象来关联相应线程的MessageQueue。
- 可以让对应的Message/Runnable在未来的某个时间点进行相应处理。
- 让自己想要处理的耗时操作放在子线程,让更新ui的操作放在主线程。
简单直白的来说,因为android中不能在子线程中更新ui,同时主线程又不能做耗时操作(会堵塞ui),而handler可以很方便的帮助我们在主线程和子线程中切换,达到在主线程中更新ui,在子线程中做耗时操作,handler在这里充当的就是一个消息的发送者和处理者的身份。
二、handler的使用
1、如何创建主线程的handler,实现在主线程更新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();
}
}
打印结果:
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
从运行结果来看,mUiHandler.handleMessage()
的运行线程和UI线程是同一个,而mThread.run()
则运行在非主线程中。
2、如何创建非UI线程的handler,实现UI线程发送消息通知非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) {
// 因为异步执行,在其他线程调用时候handler可能还没有创建好,这是使用会报空指针错误,
// 所以通过代码块加锁,wait和notifyAll,来解决这一问题
while (mHandler == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mHandler;
}
}
}
打印结果:
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
从运行结果来看,UI线程的ID=1
,而Runnable.run()的运行线程ID=2320
,所以可以断定这个handler是运行在非UI线程中的。
三、创建handler的重要步骤
-
Looper.prepare();
,给目标线程创建一个Looper
对象,如果目标线程已经存在Looper
对象,则不需要再创建; -
Looper.loop();
,让目标线程的Looper
对象,循环读取消息队列MessageQueue
; -
new Handler(Looper.myLooper());
,使用目标Looper
对象创建Handler
对象,再使用这个handler
对象发送消息(Message/Runnable);
你可能会问上面代码2-1中没有构造Looper
啊,为什么也能正常执行?
通过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();
}
- 其中调用了
Looper.prepareMainLooper();
创建Looper
,Looper.loop();
开启轮询,由此可见所有的Handler创建都是一个流程; - 并且通过
ActivityThread
入口的main()
能够看出,只要Looper.loop();
停止轮询,我们的android应用程序也就退出了,即Looper.quit();
就会结束应用,退出程序。
四、使用handler中的内存泄漏问题
对handler熟悉的同学一定会发现,我上面写的测试代码都存在一个问题,那就是有可能发生内存泄漏!!!
1、为什么会发生内存泄漏那?
我们拿代码2-1
来说,private Handler mUiHandler = new Handler() ;
由于mUiHandler
不是一个静态内部类,所以mUiHandler
隐匿的持有UiHandlerActivcity
的引用,当UiHandlerActivcity
释放时,mUiHandler
如果内部正在做一些耗时操作,这时会导致UiHandlerActivcity
无法及时回收,而导致UiHandlerActivcity
停留在堆内存中,继而导致内存泄漏。
2、怎么解决内存泄漏问题?
其实百度一下,你就会发现很多相关的文章,我这里总结下。
- 声明一个静态内部类的handler对象
-
Activity
的onDestroy()
方法中调用Handler.removeCallbacksAndMessages(null);
- 如果你在静态内类的
handler
中使用外部类,需要使用弱引用,即WeakReference<>
3、修改代码2-1
,防止内存泄漏
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)
网友评论