Android 线程间通信Handler的基本使用

Android系统是单线程模型,更新UI的操作只能在主线程内操作,并且如果在主线程内进行耗时操作容易造成应用程序无响应(ANR)。
一般的解决办法是:在主线程中开启子线程,子线程来进行耗时操作。
由于在Android中只能由主线程来更新UI,因此耗时操作产生结果后,子线程应该通知主线程进行相应的UI更新。
主(父)线程和子线程,以及子线程之间需要进行数据交换等通信,这个任务在Android中是交给Handler来完成的。

一、子线程通知主线程更新UI


方法1:sendMessage()

首先,在界面上放置一个TextView和Button。TextView用来显示通信结果,Button用来启动通信以及区分线程通信类别。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/main_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textSize="28sp"
        android:textAlignment="center"/>

    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="send"
        android:textSize="28sp"/>

</LinearLayout>

然后呢,在MainActivity代码中先定义一个整型常量 MSG_SEND = 0 作为区分通信Message类别的标识。

然后定义一个Handler类型的私有成员变量mHandler作为主线程和子线程通信的工具,mHandler在主线程中定义,因此是属于主线程的;稍后会看到子线程也是使用此变量来发送消息。

重写Handler类的handleMessage(@NonNull Message msg)方法:msg是接收到的Message消息,msg.what是消息发送方设定的消息类型,方法中使用switch区分msg.what的值,针对不同的消息类型进行不同处理。方法接收到MSG_SEND类型的消息时,将其传递的obj对象(实际上是字符串对象)字符串化并在mainTitle(TextView)上显示。

public class MainActivity extends AppCompatActivity {

    private final int MSG_SEND = 0; //  sendMessage
    private TextView mainTitle;
    private Button sendBtn;
    
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch(msg.what){
                case MSG_SEND:
                    // 对UI进行更新操作
                    mainTitle.setText(msg.obj.toString());
                    break;
            }
        }
    };
    ...
}

接下来在onCreate()方法中,进行视图控件的初始化操作,并在按钮事件中启动一个自定义的SendThread线程。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainTitle = findViewById(R.id.main_title);
        sendBtn = findViewById(R.id.btn_send);
        sendBtn.setOnClickListener(v -> {
            new SendThread().start(); //  启动子线程
        });
    }

最后呢,当然是自定义线程啦。

继承Thread并重写run()方法,首先使线程睡眠1秒钟,然后新建Message消息,类型what设置为MSG_SEND,携带的对象obj设置为字符串对象"sendMessage"(这里应该是用到了Java特性——装箱),最后通过调用mHandler.sendMessage(msg)来将msg发送给主线程。

public class MainActivity extends AppCompatActivity {
	...
    class SendThread extends Thread {
        @Override
        public void run() {
            //  线程睡眠1秒
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 发送信息
            Message msg = new Message();
            msg.what = MSG_SEND;
            msg.obj = "sendMessage";
            mHandler.sendMessage(msg);
        }
    }
}

最终效果:
sendMessage


方法2:post()

首先呢,在布局上再添加一个按钮,来启动post方法的子线程。

    <Button
        android:id="@+id/btn_post"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="post"
        android:textSize="28sp"/>

然后设置按钮事件,启动自定义PostThread线程。

		postBtn = findViewById(R.id.btn_post);
        postBtn.setOnClickListener(v -> {
            new PostThread().start();
        });

最后定义PostThread。

在run()方法中,调用mHandler的post方法,并传入Runnable匿名内部类(这里使用Lambda表达式进行了简化),在匿名内部类的run() 方法(实际上是在主线程中运行的)中可以直接调用主线程中定义的控件进行UI上的操作。

    class PostThread extends Thread {
        @Override
        public void run() {
            //  线程睡眠1秒
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mHandler.post(() -> {
                // 直接调用主线程的控件进行对应UI更新操作
                mainTitle.setText("Post success.");
            });
        }
    }

最终效果:
post


方法3:obtainMessage()

obtainMessage()和sendMessage()类似,只不过在子线程中发送消息的方式不同,这里也按流程走一遍。

首先呢,在布局上再添加一个按钮,来启动obtainMessage方法的子线程。

    <Button
        android:id="@+id/btn_obtain"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="obtain"
        android:textSize="28sp"/>

然后呢,在MainActivity代码中定义一个整型常量 MSG_OBTAIN = 1 作为区分obtainMessage方法的标识,并在handleMessage(@NonNull Message msg)对对应的消息进行处理。这里将消息携带的arg1整型参数显示在屏幕上。

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch(msg.what){
                case MSG_SEND:
                    // 对UI进行更新操作
                    mainTitle.setText(msg.obj.toString());
                    break;
                case MSG_OBTAIN:
                    mainTitle.setText(String.valueOf(msg.arg1));
                    break;
            }
        }
    };

然后设置按钮事件,启动自定义ObtainThread线程。

        obtainBtn = findViewById(R.id.btn_obtain);
        obtainBtn.setOnClickListener(v -> {
            new ObtainThread().start();
        });

最后定义ObtainThread。

这里调用了mHandler.obtainMessage(MSG_OBTAIN, 110, 0).sendToTarget()方法。

首先obtainMessage方法是获取Message的方法,有:
obtainMessage(),
obtainMessage(int what),
obtainMessage(int what, @Nullable Object obj),
obtainMessage(int what, int arg1, int arg2),
obtainMessage(int what, int arg1, int arg2, @Nullable Object obj)
一共5种构造方法,按需选择。

    class ObtainThread extends Thread{
        @Override
        public void run() {
            //  线程睡眠1秒
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mHandler.obtainMessage(MSG_OBTAIN, 110, 0).sendToTarget();
        }
    }

对于sendToTarget()方法呢,查看源码,实际上就是调用了sendMessage方法,但是此方法相对于sendMessage而言更简洁。

public void sendToTarget() { target.sendMessage(this); }

最终效果:

obtain

二、子线程间的相互通信


原理

其实在Handler通信机制中,存在这样的对应关系:
Thread——Looper——MessageQueue 是1对1对1的;
MessageQueue——message 和 Thread——Handler 是1对N的。
在创建主线程ActivityThread时默认会先将Looper准备好(Looper.prepareMainLooper()),所以在主线程中可以直接使用handler;
而对于子线程则需要手动来准备和运行Looper(Looper.prepare() 和 Looper.loop())。

主线程本质上是ActivityThread,其源代码的main方法如下:
/frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

从上述代码中可以看到,主线程也使用到了Looper,只不过是在主线程运行前已经准备好了Looper,无需手动准备和运行。

因此,这里讨论主线程向子线程发送消息的过程(即子线程持有Handler)。

使用

首先按照惯例添加发送按钮:

    <Button
        android:id="@+id/btn_sub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="sub"
        android:textSize="28sp"/>

然后,定义成员变量subThread,并在onCreate()方法中启动。
在按钮的事件中构建Message并通过subThread的mHandler成员进行发送。
(此处上下文是主线程,并且不持有Handler)

        subThread = new SubThread();
        subThread.start();
        subBtn = findViewById(R.id.btn_sub);
        subBtn.setOnClickListener(v -> {
            Message msg = new Message();
            msg.what = MSG_SEND;
            msg.obj = "subMessage";
            subThread.mHandler.sendMessage(msg);
        });

SubThread的定义如下,在run()方法开始时调用Looper.prepare(),使此线程初始化为一个Looper;
最后调用Looper.loop()方法运行消息队列;
对mHandler进行了初始化,并处理传递过来的消息(打印Log)。

    class SubThread extends Thread {
        public Handler mHandler;
        @Override
        public void run() {
            super.run();
            //  初始化此线程为Looper
            Looper.prepare();
            mHandler = new Handler() {
                @Override
                public void handleMessage(@NonNull Message msg) {
                    super.handleMessage(msg);
                    switch (msg.what) {
                        case MSG_SEND:
                            Log.d("SubThread", "msg = " + msg.obj);
                            break;
                        default:
                            break;
                    }
                }
            };
            // 在此线程中运行消息队列
            Looper.loop();
        }
    }

最终效果:
sub

三、Handler的内存泄漏


实际上本文的代码仍有不足之处,即存在内存泄漏的风险,AS也会智能进行提示(如下图)。

后续我会继续出一篇关于解决Handler内存泄漏的文章。
在这里插入图片描述


制作不易,如果觉得本文对你有所帮助,那就点个赞吧!
欢迎在评论区提出更好的方法!

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值