在Android中,UI线程不能执行耗时操作的,否则可能会出现ARN。而在工作线程中是不能更新UI的,否则程序会抛出异常。这样,我们就需要在工作线程中执行完耗时操作后来通知UI线程更新UI。这个时候就可以使用Handler。
Handler的简单用法
通常我们使用Handler会在Activity里面新建一个类继承Handler,并重写它的handleMessage()方法。然后在声明的时候直接初始化,或者是在onCreate()中初始化Handler的实例。
class MainActivity : AppCompatActivity() {
private lateinit var btn: Button
private val handler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initViews()
}
private fun initViews() {
btn = findViewById(R.id.btn)
btn.setOnClickListener {
Thread(Runnable {
//子线程模拟耗时操作
Thread.sleep(3000L)
//耗时操作完成通知UI线程
handler.sendEmptyMessage(0)
}).start()
}
}
inner class MyHandler : Handler() {
override fun handleMessage(msg: Message) {
//更新UI
}
}
}
Handler机制几个比较相关的类
Handler 主要用于创建消息,发送消息,处理消息,删除消息
Looper 在该类内部维护了一个MessageQueue(消息队列),Handler发送的消息会发送到这个消息队列里
MessageQueue 消息队列
Message 消息
Handler
我们首先来看看我们使用Handler时常用的构造方法(Hanlder还有别的构造方法)
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
...
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;
mCallback = callback;
mAsynchronous = async;
}
在上面构造方法中,主要是分别为mLooper,mQueue,mCallback这三个属性赋值。其中mLooper赋值为当前线程保存Looper对象。mQueue赋值为当前线程保存的Looper对象的MessageQueue,mCallback在这里被赋值为null。
首先来看一下为mLooper属性数值的Looper.myLooper()方法:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这个方法很简单,直接获取当前线程保存的Looper对象。sThreadLocal是一个ThreadLocal对象,该对象可用于在一个线程中存储变量。
然后判断了一下mLooper是不是空。如果是空的话则会抛出异常。这里就说明如果我们的当前线程没有创建Looper对象的话是无法直接使用Handler的。那为啥可以在主线程中直接使用Handler而不抛出异常呢?这是因为Android系统在应用程序启动时已经在主线程创建了一个Looper对象。
主线程中的Looper对象
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().setMessageLoggong(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在这个main()方法中我们看到首先会调用Looper.prepareMainLooper(),接着后来调用Looper.loop()。那这两个方法的作用是什么?为什么调用这两个方法后我们就可以直接在主线程中使用Handler。
首先我们先来看Looper.prepareMainLooper()。该方法的作用主要是创建一个Looper对象。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//为sMainLooper赋值
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建一个Looper对象并保存进sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
首先会获取sThreadLocal中的Looper对象,如果不为空的话则会抛出异常。这表明prepare()方法只能被调用一次。同时也保证每一个线程只会拥有一个Looper实例。最后将创建的Looper实例放入sThreadLocal中。下面的是Looper的构造方法,在Looper的构造方法中创建了一个消息队列并赋值给mQueue属性。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
接着是Looper.loop()。该方法的作用是不断的从MessageQueue中取出消息来执行。
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;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
...
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
首先会通过myLooper()获取当前线程的Looper对象,如果为空的话则会抛出异常。这说明该方法必须在prepae()方法之后执行。接着获取该Looper对象的MessageQueue(消息队列)。然后进入一个无线循环体中,在该循环体中,会从MessageQueue中取出一个消息。该方法可能会被阻塞。如果消息不为空,接着会调用msg.target.dispatchMessage(msg)方法。msg.target是一个Handler对象。最后释放消息所占据的资源。
在loop()方法执行后,我们就可以用Handler对象的sendMessage(Message msg)或sendEmptyMessage(int what)方法来发送一个消息了。
不管是用上面的哪种方法发送的消息最终都会调用到sendMessageAtTime()方法
public boolean sendMessageAtTime(@NonNull 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);
}
该方法内部直接获取了MessageQueue,接着调用了enqueueMessage()方法。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在该方法中首先为msg.target属性赋值为this,也就是把当前的Handler对象作为msg的target属性。前面说到Looper的loop方法时将取出的消息交由msg的target去执行。这里就是为target属性赋值。接着,调用queue.enqueueMessage()方法将Message添加到消息队列里面。
在loop()方法中,取出消息后,会回调创建这个消息的Handler的dispathMessage()方法。
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先判断了一下msg.callback是不是为空,那这个msg.callback是什么呢?其实这个msg.callback是一个Runnable对象,当我们通过Handler的post()方法发送消息时
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到当我们使用post()方法发送消息时,会将Runnable对象赋值给msg的callback属性。在这里注意一下,产生一个Message对象,可以直接调用构造方法,也可以使用obtain()静态方法。推荐使用obtain()静态方法,因为Message内部会维护一个消息池,避免重新创建Message对象时的内存重新分配。
当msg.callback不为空时,会直接执行它的run()方法。
private static void handleCallback(Message message) {
message.callback.run();
}
而当msg.callback为空时,会首先检查一下mCallback是不是为空,这个mCallback是一个Handler.Callback对象。该属性是在Handler的构造方法时初始化的,通常情况下,该属性为空。在mCallback为空时会调用handleMessage(msg)方法。该方法时一个空方法,因为具体的实现是由我们自己控制的。
public void handleMessage(@NonNull Message msg) {
}
到这里,Handler的工作流程是,
1.首先,Looper会调用prepare()方法在线程中保存一个Looper实例。该实例中会保存一个MessageQueue对象。因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中也只有一个实例。
2.调用Looper.loop()方法,让当前线程进入到一个无线循环,不断的从MessageQueue中取出消息,然后回调msg.target,dispatchMessage(msg)方法。
3 在Handler的构造方法中,会首先获取当前线程中保存的Looper实例。进而与Looper实例中的MessageQueue相关联。
4 通过调用Handler的sendMessage()方法或post()方法,会给msg的target属性赋值为Handler自身,然后会加入到MessageQueue中。
5 从MessageQueue中取出msg,并回调该handler实例的dispatchMessage()方法。
6 通过判断msg中的callback属性是否为空,并最终决定是执行handleCallback()方法还是handleMessage()方法。
使用Handler时的一些注意点
如果按上面的方法使用Handler,编译器会提示我们Handler会造成内存泄露。造成内存泄露的原因是因为,主线程Looper实例的生命周期为整个应用的生命周期,如果我们关闭Handler所在的Activity时,此时Looper的MessageQueue里面存在还没有执行的Message对象时。由于Looper持有了MessageQueue,MessageQueue又持有了Message,Message又持有了Handler,Handler又持有了Activity。所以会造成我们的Activity内存泄露。解决办法也挺简单,将Handler改为静态内部类,在Handler内部使用软引用来持有Activity。在Activity的onDestory()方法执行时,将MessageQueue里面的Message清空。
class MainActivity : AppCompatActivity() {
private lateinit var btn: Button
private val handler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initViews()
}
private fun initViews() {
btn = findViewById(R.id.btn)
btn.setOnClickListener {
Thread(Runnable {
//子线程模拟耗时操作
Thread.sleep(3000L)
//耗时操作完成通知UI线程
handler.sendEmptyMessage(0)
}).start()
}
}
override fun onDestroy() {
super.onDestroy()
handler.removeCallbacksAndMessages(null)
}
class MyHandler(activity: MainActivity) : Handler() {
private val weakReference: WeakReference<MainActivity> = WeakReference(activity)
override fun handleMessage(msg: Message) {
val mainActivity = weakReference.get()
if (mainActivity != null) {
//更新UI
}
}
}
}