【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更适合一对一线程之间的通信,代码逻辑更简单,不需要数据的传递;

相关推荐
安卓理事人1 小时前
安卓LinkedBlockingQueue消息队列
android
万能的小裴同学2 小时前
Android M3U8视频播放器
android·音视频
q***57743 小时前
MySql的慢查询(慢日志)
android·mysql·adb
JavaNoober3 小时前
Android 前台服务 "Bad Notification" 崩溃机制分析文档
android
城东米粉儿4 小时前
关于ObjectAnimator
android
zhangphil5 小时前
Android渲染线程Render Thread的RenderNode与DisplayList,引用Bitmap及Open GL纹理上传GPU
android
火柴就是我6 小时前
从头写一个自己的app
android·前端·flutter
lichong9517 小时前
XLog debug 开启打印日志,release 关闭打印日志
android·java·前端
用户69371750013847 小时前
14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)
android·后端·kotlin
火柴就是我7 小时前
NekoBoxForAndroid 编译libcore.aar
android