【Android】使用Handler做多个线程之间的通信

一:两个线程之间的通信

java 复制代码
    private void startHandler(){
        new Thread(new Runnable() {
            @Override
            public void run() {
//                //准备当前线程的looper
//                Looper.prepare();
//                //获取当前线程的实例
//                Looper looper = Looper.myLooper();
//
//                //在子线程中创建Handler,如果不传looper,默认是和子线程关联,子线程中去更新UI就会报错
//                Handler handler1 = new Handler();
//
//                Looper.loop();//开始循环发送消息

                // 有传Looper.getMainLooper(),表示和主线程关联
                Looper mainLooper = Looper.getMainLooper();
                Handler handler1 = new Handler(mainLooper);


                String id = etUserId.getText().toString();
                String urlAddress = "http://titok.fzqq.fun/addons/cms/api.user/userInfo?user_id=" + id + "&type=archives";

                try {
                    URL url = new URL(urlAddress);
                    HttpURLConnection connection = (HttpURLConnection)url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.setConnectTimeout(8000);
                    connection.setReadTimeout(8000);

                    InputStream inputStream = connection.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

                    StringBuilder builder = new StringBuilder();
                    String line;
                    while((line = reader.readLine() ) != null){
                        builder.append(line);
                    }


                    //返回主线程更新UI
                    handler1.post(new Runnable() {
                        @Override
                        public void run() {
                            String result = builder.toString();
                            Log.i(TAG, "run: 网络访问的结果是:" + result);
                        }
                    });

                    connection.disconnect();
                } catch (MalformedURLException e) {
                    throw new RuntimeException(e);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }

四种情况

  • Handler在哪个线程中创建,它就属于哪个线程

1:利用主线程Handler更新UI

之前更新UI信息是调用runOnUiThread()方法,今天学习到第二种方法,主线程中创建Handler(这里是成员变量),再在子线程中利用handler.post()方法更新UI

2:子线程中的Handler不传Looper

在子线程中创建Handler,进行UI更新就会报错;

因为这里的handler.post()是post给子线程了,子线程不可以更新UI的,所以报错

3:子线程中的Handler设置Looper

非要在子线程中进行post请求,就需要去获取子线程的looper

调整指定在子线程中进行handler的post请求,成功,没有日志打印(正常)

4:子线程利用主线程的Looper

子线程的Handler中传入主线程的looper也是可以正常在主线程中进行UI更新的

二:一些细节

1:Looper.prepare

作用 :为当前线程创建 Looper 并初始化消息循环相关的基础设施 。在 Android 中,主线程(UI 线程)的 Looper 是系统自动初始化好的,但子线程若要使用 Handler 进行消息循环,就必须手动调用 Looper.prepare()

内部逻辑 :它会在当前线程中创建一个 Looper 对象,同时该对象内部会维护一个 MessageQueue(消息队列),用于存储后续发送过来的 Message(消息) 。如果在已经存在 Looper 的线程中再次调用 Looper.prepare() ,会抛出 RuntimeException ,保证一个线程最多只有一个 Looper

2:Looper.myLooper();

作用 :获取当前线程关联的 Looper 对象 。如果当前线程还未通过 Looper.prepare() 初始化 Looper ,那么调用此方法会返回 null 。一般在需要明确拿到当前线程 Looper ,用于一些和 Looper 关联的操作(比如给 Handler 构造方法传特定 Looper 等场景 )时使用,不过很多时候如果只是在当前线程创建 HandlerHandler 内部会自动去获取当前线程的 Looper ,不一定需要显式调用这个方法来获取。

拓展:Looper.myLooper.quit()方法是退出线程,也有.quitSafely()安全退出这一说

3:Looper.loop()

作用 :开启 Looper 的消息循环,让当前线程进入一个不断从 MessageQueue 中取出消息并处理的循环过程 。

总结:Looper相当于可以往容器里放Message的工具,Handler相当于一个放Message的容器,.loop方法不断从队列里取出消息交给对应的Handler,Handler调用handleMessage方法处理消息

  • 普通线程执行完 run () 方法后就会终止,无法再次使用
  • 加入 Looper 后,Looper.loop () 会启动一个无限循环,让线程一直运行
  • 这个循环会不断从 MessageQueue 中取出消息并处理
  • 当你需要使用这个线程时,只需通过 Handler 发送消息即可
  • 线程会一直处于等待 - 处理消息的状态,直到调用 quit () 方法才会真正结束

三:多个线程间的通信

场景:(多对一)多个线程发送消息,一个线程来接收

1:延迟发送消息

java 复制代码
    private Handler handler = new Handler(Looper.getMainLooper());
java 复制代码
 else if (v.getId() == R.id.btn_post_delayed) {
            delayToast();
java 复制代码
private void delayToast() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(HandlerActivity.this, "我他喵延迟1s来啦", Toast.LENGTH_SHORT).show();
            }
        },1000);
    }

效果如下

2:处理消息队列

如果有多个子线程和主线程需要处理同一件事情的话,我们可以利用同一个Handler,这样的代码也会更加直观和清爽,对底层的性能消耗也会更小一点

(1)写法一

一般要去指定它是一个主线程,不传参的写法是已经过时的写法;

如果还需要处理一些其他的事情,只有一个handleMessage不够,那就这种写法

java 复制代码
    /**
     * 第一种处理接收消息的写法
     * 记得要传参,不传参的过时了
     */
    private Handler sendHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
        }
    };

(2)写法二

new 一个Handle.Callback()接口,重写handlerMessage方法

接收消息的一方

java 复制代码
    private Handler sendHandler1 = new Handler(new Handler.Callback(){

        @Override
        public boolean handleMessage(@NonNull Message msg) {
            switch(msg.what){
                case 123:
                    String data = (String) msg.obj;
                    Toast.makeText(HandlerActivity.this, data, Toast.LENGTH_SHORT).show();
                    return true;//回收Message
                case 234:
                    int num = (int) msg.obj;
                    Toast.makeText(HandlerActivity.this, num+"", Toast.LENGTH_SHORT).show();
                    return true;
            }
            return false;
        }
    });

返回ture底层会判断出当前消息已经被消费过了,就可以被回收掉了

发送消息的一方

1:Message详解

通信的桥梁就是Message

  • message.what 标记消息是从哪里被发送过来的,类似于请求码
  • message.obj Message中的obj,就是Object,海纳百川有容乃大,传什么类型都可以接受,再把这些数据发送出去

startTaskA和B两种发送消息的方式最大的不同就在于,创建Message的方式;

区别:new Message()是每次都去创建一个Message,代价太大;

obtatin(obtain 英 [əbˈteɪn] 得到)是从消息池中获取一个Message对象,可以减小创建对象的开销,更适合性能要求高的地方

java 复制代码
Message message = new Message();
Message message = sendHandler1.obtainMessage();

以下是详细代码

java 复制代码
    private void startTaskA(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                String data = "我是服务器返回的数据:{name = 我他喵莱纳}";
                //创建一个Message,通过sendHandler1对象发送消息
                Message message = new Message();
                message.what = 123;
                message.obj = data;
                sendHandler1.sendMessage(message);

            }
        }).start();
    }
java 复制代码
    private void startTaskB(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

                Message message = sendHandler1.obtainMessage();
                message.what = 234;
                message.obj = 520;
                sendHandler1.sendMessage(message);
            }
        }).start();
    }

四:post通信和Message通信对比

在HandlerMessage中处理通过sendMessage方法发送的消息,可以集中处理多个线程发来的消息;有很多任务需要同时去处理,需要把它写在一起,就用what进行区分

post更适合一对一线程之间的通信,代码逻辑更简单,不需要数据的传递;

相关推荐
DevRen9 小时前
实现Google原生PIN码锁屏密码效果
android·前端·kotlin
雨白9 小时前
自定义 ViewGroup:实现一个流式标签布局
android
没有了遇见9 小时前
免费替代高德 / 百度!Android 原生定位 + GeoNames 离线方案:精准经纬度与模糊位置工具包
android
mucheni9 小时前
迅为RK3588开发板安卓串口RS485App开发-硬件连接
android
Akshsjsjenjd10 小时前
使用ansible的playbook完成以下操作
android·ansible
QING61810 小时前
使用扩展函数为 AppCompatTextView 提供了多段文本点击区域设置功能
android·kotlin·app
一条上岸小咸鱼11 小时前
Flutter 类和对象(一):类
android·kotlin