揭秘Android Handler:让应用避免ANR的神器!

一 背景

  • 为什么Android要提供这个功能

Handler的主要作用是将某个任务切换到Handler所在的线程中去执行。因为Android规定访问UI只能通过主线程,如果子线程访问UI,程序会抛出异常;ViewRootImpl在checkThread方法中做了判断。 由于Android不建议在主线程进行耗时操作,否则可能会导致ANR。那我们耗时操作在子线程执行完毕后,我们需要将一些更新UI的操作切换到主线程当中去。所以系统就提供了Handler。

系统为什么不允许在子线程中去访问UI呢? 因为Android的UI控件不是线程安全的,多线程并发访问可能会导致UI控件处于不可预期的状态,为什么不加锁?因为加锁机制会让UI访问逻辑变得复杂;其次锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。所以Android采用了高效的单线程模型来处理UI操作。

  • Handler实现过程

Handler是如何实现这一切的呢?其实它利用了Looper和MessageQueue这两个角色。当你创建一个Handler时,它会绑定到创建它的线程的Looper上。通过向Handler发送消息或者Runnable任务,这些消息会被加入到绑定线程的MessageQueue中。Looper不断地循环遍历这个队列,一旦发现有新消息,就会取出来交给Handler处理。这样,就实现了线程间的通信和任务调度。

例如,我们在后台线程执行了一个耗时的数据处理任务,任务完成后,我们想要更新界面上的一个进度条。这时,我们就可以创建一个Handler,利用它将更新UI的代码安全地切换回主线程,避免直接在后台线程操作UI而导致的异常。

二 具体示例

  • 具体应用Handler示例

我们在一个后台线程中执行一个耗时的任务,然后在任务完成后更新UI。

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private TextView textView;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.text_view);
// 创建Handler实例并重写handleMessage方法处理消息
handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// 更新UI组件
textView.setText("任务完成");
}
};
// 启动一个新线程来执行耗时任务
new Thread(new Runnable() {
@Override
public void run() {
// 执行耗时操作
performLongRunningTask();
// 耗时任务执行完毕后,通过handler发送一个空消息通知UI线程更新UI
handler.sendEmptyMessage(0);
}
}).start();
}
private void performLongRunningTask() {
// 这里模拟一些耗时的任务
try {
Thread.sleep(5000); // 5秒的延时模拟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

  • 主线程Handler示例

Android的主线程就是ActivityThread,主线程的入口方法为 main(String[] args) ,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。

public static void main(String[] args) {
...
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();//创建主线程的Looper
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();//开启looper
throw new RuntimeException("Main thread loop unexpectedly exited");
}

主线程的消息循环开始后,ActivityThread还需要一个Handler来和消息队列进行交互,这个Handler就是ActivityTread.H,它内部定义了一组消息类型,主要包含四大组件的启动和停止过程。AMS以进程间通信的方式回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发送消息,H收到消息后会将ApplicationThread中的逻辑切换到ActivityTread中去执行,即切换到主线程中去执行。四大组件的启动过程基本上都是这个流程。

三 底层实现原理

  • 消息队列的工作原理

1. 消息队列指的是MessageQueue,主要包含两个操作:插入和读取。读取操作本身会伴随着删除操作。

2. MessageQueue内部通过一个单链表的数据结构来维护消息列表,这种数据结构在插入和删除上的性能比较有优势。

3. 插入和读取对应的方法分别是: enqueueMessage 和 next 方法。enqueueMessage()的源码实现主要操作就是单链表的插入操作. next()的源码实现也是从单链表中取出一个元素的操作,next()方法是一个无线循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next()方法会返回这条消息并将其从单链表中移除。

  • Looper的工作原理

1. Looper在Android的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立即处理,否则就一直阻塞在那里。每个 Looper 对象应该都是对应一个独有的 MessageQueue 实例和 Thread 实例,这样子线程和主线程才可以互相发送 Message 交由对方线程处理

2. 通过 Looper.prepare() 方法即可为当前线程创建一个Looper,再通过 Looper.loop() 开启消息循环。 prepareMainLooper() 方法主要给主线程也就是ActivityThread创建Looper使用的,本质也是通过prepare方法实现的。通过 ThreadLocal 来为不同的线程单独维护一个 Looper 实例,每个线程通过 prepare()方法来初始化本线程独有的 Looper 实例 ,再通过 myLooper()方法来获取和当前线程关联的 Looper 对象

3. Looper提供quit和quitSafely来退出一个Looper,区别在于quit会直接退出Looper,而quitSafely会把消息队列中已有的消息处理完毕后才安全地退出。Looper退出后,这时候通过Handler发送的消息会失败,Handler的send方法会返回false。

4. 在子线程中,如果手动为其创建了Looper,在所有事情做完后,应该调用Looper的quit方法来终止消息循环,否则这个子线程就会一直处于等待状态;而如果退出了Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper。

5. loop()方法会调用MessageQueue的 next() 方法来获取新消息,而next是是一个阻塞操作,但没有信息时,next方法会一直阻塞在那里,这也导致loop方法一直阻塞在那里。如果MessageQueue的next方法返回了新消息,Looper就会处理这条消息:
msg.target.dispatchMessage(msg) ,这里的msg.target是发送这条消息的Handler对象,这样Handler发送的消息最终又交给Handler来处理了。

  • Handler的工作原理

Handler的工作用来向 MessageQueue 发送消息了 主要包含消息的发送和接收过程。通过post的一系列方法和send的一系列方法来实现。发送过程仅仅是向消息队列中插入了一条消息。MessageQueue的next方法就会返回这条消息给Looper,Looper拿到这条消息就开始处理,最终消息会交给Handler的dispatchMessage()来处理,这时Handler就进入了处理消息的阶段。dispatchMessage()方法运行在Looper所在的线程上。通过post的一系列方法和send的一系列方法来实现。

可以发送延时消息,以此来执行延时任务。用 Message 内部的 when 字段来标识希望任务执行时的时间戳即可除了可以发送 Message 类型的消息外

还可以发送 Runnable 类型的消息。Handler 内部将 Runnable 包装为 Message 即可

可以实现线程切换,即从子线程发送的 Message 可以在主线程被执行,反过来也一样。子线程可以向一个特定的 mainMessageQueue 发送消息,然后让主线程负责循环从该队列中取消息并执行即可,这样就实现了线程切换了

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//Message的callback是一个Runnable,
//也就是Handler的 post方法所传递的Runnable参数
handleCallback(msg);
} else {
//如果给Handler设置了Callback的实现,
//则调用Callback的handleMessage(msg)
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//调用Handler的handleMessage方法来处理消息,
//该Handler子类需重写handlerMessage(msg)方法
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
//直接执行Runnable中的run()方法
message.callback.run();
}
public interface Callback {
//不想继承Handler子类,可以通过Callback来实现handleMessage
public boolean handleMessage(Message msg);
}
//默认空实现
public void handleMessage(Message msg) {
}
  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值