Android学习之异步消息处理机制
•前言
我们在开发 APP 的过程中,经常需要更新 UI;
但是 Android 的 UI 线程是不安全的;
如果想更新 UI 线程,必须在进程的主线程中;
这里我们引用了异步消息处理机制来解决之一问题。
•异步消息的组成
概念
Android 的异步消息处理机制主要由 4 个部分组成:Message、Handler、MessageQueue 和 Looper。
Message
- Message 是线程之间传递信息的机制
- 它可以在内部携带少量的信息,用于在不同线程之间交换数据
- Message 可以用 what、arg1、arg2、obj 字段携带信息
- 其中 arg1、arg2 主要用于携带整型数据,obj 携带 Object 对象
Handler
- Handler 是消息的处理者,它主要用于发送和处理消息
- 发送消息使用 Handle 的 sendMessage() 方法
- 而发出的消息经过一系列的辗转处理后,最终会传递到 Handler 的 handleMessage() 方法中
MessageQueue
- MessageQueue 是消息队列的意思,它主要用于存放所有通过 Handler 发送的消息
- 这部分消息会一直存在于 MessageQeue 中,直至被 Looper 发送至 handleMessage() 处理
- 每个线程只会有一个 MessageQueue 对象
Looper
- Looper 是每个 MessageQueue 的消息管家,调用 Looper 中的 loop() 方法后,就会进入到一个无限循环当中
- 把 MessageQueue 中的消息取出,并传递到 Handle 的 handleMessage() 方法中进行处理
- 每个线程当中只会有一个 Looper 对象
Thread,Looper 与 Handler 之间的对应关系
1个线程(Thread)只能绑定 1个循环器(Looper),但可以有多个处理者(Handler)
1个循环器(Looper) 可绑定多个处理者(Handler)
1个处理者(Handler) 只能绑定1个1个循环器(Looper)
•异步消息的处理流程
文字描述
首先需要在主线程中创建一个 Handler 对象,并重写 handleMessage() 方法,我们主要在 handleMessage() 中进行一系列的操作;
当子线程中需要进行 UI 更新时,就在子线程中创建一个 Message 对象,并通过 Handler 将这条消息发送出去;
经 Handler 发送的消息会被添加到 MessageQueue 中等待被处理;
而 Looper 会一直尝试从 MessageQueue 中取出待处理的消息;
最后 Looper 会将消息发送到 Handler 的 handleMessage() 方法中进行处理。
由于 Handler 是在主线程中创建的,所以此时 handleMessage() 方法中的代码也会在主线程中运行,
于是我们就可以安心的进行 UI 更新操作了。
图示
一条 Message 经过这样一个流程的辗转调用后,从子线程进入到了主线程,从不能更新 UI 变成了可以更新 UI;
接下来们通过代码进一步理解;
通过内部类的方式创建Handler对象
新建一个项目,并选择 Empty Activity;
这样,Android Studio 为我们自动生成了 MainActivity.java 和 activity_main.xml 文件;
在 activity_main.xml 中添加如下代码;
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="default" android:textSize="20sp" android:textColor="@color/black"/> </RelativeLayout>在该布局中,我只放置了一个 TextView 控件,并初始化 text 的值为 default;
接下来修改 MainActivity.java 中的代码;
MainActivity.java
public class MainActivity extends AppCompatActivity { private Handler handler; private TextView tv; //自定义MyHandler类,继承自Handler类并重写 handleMessage() 方法 private class MyHandler extends Handler{ //通过重写 handlerMessage() 方法 //从而确定更新 UI 的操作 @Override public void handleMessage(@NonNull Message msg) { /* 根据不同线程发送过来的消息,执行不同的 UI 操作 根据 Message 对象的 what 属性,标识不同的消息 */ switch(msg.what){ case 1: tv.setText("我是线程A"); break; case 2: tv.setText("我是线程B"); break; default: } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = findViewById(R.id.tv); //在主线程中创建Handler实例 handler = new MyHandler(); //通过继承Thread类实现多线程 new Thread(){ @Override public void run() { try { sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //创建所需的消息对象 Message msg = Message.obtain(); msg.what = 1;//消息标识 msg.obj = "A";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中 handler.sendMessage(msg); } }.start(); new Thread(){ @Override public void run() { try { sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } //创建所需的消息对象 Message msg = Message.obtain(); msg.what = 2;//消息标识 msg.obj = "B";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中 handler.sendMessage(msg); } }.start(); } }代码分析
通过继承 Handler 创建了一个 MyHandler 类,并重写了 handlerMessage() 方法;
在该方法中通过判断 msg.what 的不同对 TextView 实施不同的操作;
在 onCreate() 方法中,创建了 Handler 实例,并通过 new Thread() 来创建子线程;
在子线程中通过创建 Message 对象 msg,并通过 handler.sendMessage(msg) 来向主线程发送子线程的意图;
最后,在 MyHandler 类中通过 switch() 处理不同的 msg.what;
运行效果
通过匿名内部类的方式创建Handler对象
在上述 MainActivity.java 代码中,我们是通过内部类的方式创建了 MyHandler 对象;
接下来,我们通过匿名内部类的方式创建 Handler 对象;
修改 MainActivity.java 中的代码;
MainActivity.java
public class MainActivity extends AppCompatActivity { private Handler handler; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = findViewById(R.id.tv); //在主线程中创建Handler实例 handler = new Handler(){ //通过重写 handlerMessage() 方法 //从而确定更新 UI 的操作 @Override public void handleMessage(@NonNull Message msg) { /* 根据不同线程发送过来的消息,执行不同的 UI 操作 根据 Message 对象的 what 属性,标识不同的消息 */ switch(msg.what){ case 1: tv.setText("我是线程A"); break; case 2: tv.setText("我是线程B"); break; default: } } }; //通过继承Thread类实现多线程 new Thread(){ @Override public void run() { try { sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //创建所需的消息对象 Message msg = Message.obtain(); msg.what = 1;//消息标识 msg.obj = "A";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中 handler.sendMessage(msg); } }.start(); new Thread(){ @Override public void run() { try { sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } //创建所需的消息对象 Message msg = Message.obtain(); msg.what = 2;//消息标识 msg.obj = "B";//消息内存存放 //在工作线程中,通过 Handler 发送消息到消息队列中 handler.sendMessage(msg); } }.start(); } }实现的效果和上面内部类的是一样的;
不过,通过匿名内部类的方式创建 Handler 对象的方法被 Android Studio 嫌弃了;
•实战演练
预期效果图
开淦
新建一个项目,命名为 Handle,选择 Empty Activity;
在 activity_main.xml 中添加如下代码;
activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/img" android:layout_width="164dp" android:layout_height="156dp" android:layout_centerInParent="true" android:src="@drawable/girl_0"/> </RelativeLayout>在该布局中,我添加了一个 ImageView 控件,并使其居中显示;
修改 MainActivity.java 中的代码;
MainActivity.java
public class MainActivity extends AppCompatActivity { private Handler handler; private int[] imgId = new int[]{ R.drawable.girl_0, R.drawable.girl_1, R.drawable.girl_2, R.drawable.girl_3, R.drawable.girl_4, R.drawable.girl_5, R.drawable.girl_6, R.drawable.girl_7, }; private int imgStart = 0; private ImageView img; private class MyHandler extends Handler{ @Override public void handleMessage(@NonNull Message msg) { if(msg.what == 0) img.setImageResource(imgId[imgStart++%8]); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); img = (ImageView) findViewById(R.id.img); handler = new MyHandler(); new Thread(){ @Override public void run() { while(true){ try { sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } handler.sendEmptyMessage(0); } } }.start(); } }分析
在该代码中,通过定义整型数组 imgId 来存储帧动画素材,并通过内部类来创建 Handler 对象;
在新建的线程 new Thread() 中重写了 run() 方法;
并在该方法中通过 while(true) 中的 handler.sendEmptyMessage(0) 方法每隔 500毫秒 让 handler 发送一个空信息;
以此来实现帧动画的特效;
这样,就效果图中的效果就实现了;
需要注意的是,一定要添加 sleep() 方法,并且至少要让子线程休眠 1ms;
当然,也可以通过使用定时器实现每隔 500毫秒 发送一个空消息的效果;
修改 MainActivity.java 中的代码;
MainActivity.java
public class MainActivity extends AppCompatActivity { private Handler handler; private int[] imgId = new int[]{ R.drawable.girl_0, R.drawable.girl_1, R.drawable.girl_2, R.drawable.girl_3, R.drawable.girl_4, R.drawable.girl_5, R.drawable.girl_6, R.drawable.girl_7, }; private int imgStart = 0; private ImageView img; private class MyHandler extends Handler{ @Override public void handleMessage(@NonNull Message msg) { if(msg.what == 0) img.setImageResource(imgId[imgStart++%8]); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); img = (ImageView) findViewById(R.id.img); handler = new MyHandler(); //使用定时器,每隔500毫秒让handler发送一个空信息 new Timer().schedule(new TimerTask(){ @Override public void run() { handler.sendEmptyMessage(0); } },0,500); } }该代码的运行效果与上一个的运行效果一样;
•声明
参考资料
【异步消息处理机制】
【Android 异步通信:图文详解Handler机制工作原理】
【Android 异步通信:手把手教你使用Handler消息传递机制(含实例Demo)】
在此感谢大佬的帮助!