Android学习之异步消息处理机制
阅读原文时间:2023年07月10日阅读:1

•前言

  我们在开发 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


<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"/>

  在该布局中,我只放置了一个 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


<ImageView  
    android:id="@+id/img"  
    android:layout\_width="164dp"  
    android:layout\_height="156dp"  
    android:layout\_centerInParent="true"  
    android:src="@drawable/girl\_0"/>

  在该布局中,我添加了一个 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);  
}  

}

  该代码的运行效果与上一个的运行效果一样;

•声明

参考资料

  【异步消息处理机制

  【Handler消息传递机制浅析

  【Android 异步通信:图文详解Handler机制工作原理

  【Android 异步通信:手把手教你使用Handler消息传递机制(含实例Demo)

  在此感谢大佬的帮助!