Android进阶——强大的异步不可或缺的组件Handler详解

Posted CrazyMo_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶——强大的异步不可或缺的组件Handler详解相关的知识,希望对你有一定的参考价值。

引言

从一开始开通博客就计划一周至少写两篇博客,把自己的以前记在本地下的知识点和经验,全部搬到自己的博客里,无论是复杂难易程度只要是自己想写的就搬到这里来,好好总结下形成自己的技术体系,因为有些东西不用就会一时之间想不起来了。ORZ…好了进入正题,对于Java开发来说Thread比较简单,直接匿名创建重写run方法,调用start方法执行即可。或者从Runnable接口继承,但对于android来说在底层UI操作部分代码没有设计成为线程安全类型,所以需要引入一些同步的机制来实现线程间的通信使其刷新。Handler就是在这样的设计背景下应运而生。

一Handler概述

Handler跟多线程,消息队列联系很紧密,其主要的功能就是发送和处理Message,也可以分发Runnable对象。 每个Handler实例,都会绑定到创建他的线程中(默认是位于主线程,但是我们可以通过Looper对象来指定到其他线程)。

二 Handler的作用

  • 执行计划任务,比如在预定的实现某些任务,模拟定时器等等。
  • 线程之间的互相通信,在app启动的时候,会创建一个主线程(即UI线程),主线程创建了之后还会创建一个消息队列来处理各种消息,当我们创建了子线程时,可以在子线程中拿到主线程创建的Handler对象,当我们需要通信时就可以,通过该对象像主线程的消息队列发送消息,主线程接收到消息之后就会处理。
  • 确保操作始终在某个特定的线程中运行。比如说当我们访问数据库并加载数据时,除了初始化要加载外,每当我们收到数据改变的消息通知时也需要重新加载,为了保证数据的有效性,减少不必要的查询操作。

三 Handler机制重要的四种角色参与者的关系

一个消息循环的机制,简单来理解就是往消息队列里添加数据,然后异步的等待消息的返回。当消息队列为空的时候就会挂起线程,等待新的消息的加入。在线程里产生一个Looper对象,从字面上不难理解,可以看成在线程里一直循环运行,不断地检索MessageQueue,不断地取出消息,得到消息之后发送到Handler,这样Handler通过回调handleMessage方法就能处理消息了

  • Looper:Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。我们的主线程(UI线程)就是一个消息循环的线程。Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。一句话每一个消息循环线程都可以产生一个Looper对象,用于管理线程里的MessageQueue
  • Handler:你可以构造Handler对象来于Looper沟通以便Push新的消息到MessageQueue 里或者接收Looper(从MessageQueue中取出)发送过来的消息
  • MessageQueue: 用于存放线程放入的消息
  • 线程:产生Looper和MessageQueue

四得到Handler对象

  • 默认的构造方法Handler() 把当前Handler与当前线程和当前线程的消息队列绑定,这也是为什么默认的Handler一定运行在主线程中
  • 可以触发回调Handler(Handler.Callback callback)
  • Handler(Looper looper) 把Handler绑定到Looper所属的线程和消息队列中
  • Handler(Looper looper, Handler.Callback callback)

五 Handler常用API

  • 获取Looper对象: Looper getLooper()
  • 处理系统的消息:void dispatchMessage(Message msg) 一般不宜修改
  • 处理消息:void handleMessage(Message msg) 必须实现
  • 从消息队列中得到新的Message对象:Message obtainMessage()及重载
  • 发送Runnable对象:boolean post(Runnable r) 及重载形式
  • 发送消息:boolean sendEmptyMessage()及重载
  • 发送自定义Message:boolean sendMessage(Message msg)
  • removeCallbacks(Runnable r) 、removeCallbacksAndMessages(Object token) 、removeMessages(int what) 等移除系列,移除了之后就不会去执行handleMessage(Message msg)

六 Handler的简单应用

1同一个HandlerThread里可以有多个Handler

package cmo.learn.mutiplethread;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class HandlerActivity extends Activity {
    private Handler mainHandler1;
    private Handler mainHandler2;
    Button mBtn1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        // 在主线程中初始化Handler
        mainHandler1 = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("HandlerTest", "主线程的Handler1对象接收到消息" + msg.what);
            }

        };

        mainHandler2 = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("HandlerTest", "主线程的Handler2对象接收到消息" + msg.what);
            }
        };
    }

    public void testHandler(View view) {
        switch (view.getId()) {
        case R.id.btn_handler01:
            new Thread(new Runnable() {

                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        i++;
                    }
                    //mainHandler1.sendEmptyMessage(1);
                    mainHandler2.sendEmptyMessage(1);
                }

            }).start();
            break;
        case R.id.btn_handler02:
            break;
        case R.id.btn_handler03:
            break;
        default:
            break;
        }
    }
}

运行结果:

当使用mainHandler1.sendEmptyMessage(1)发送消息时,mainHandler1接收到消息并处理
这里写图片描述
当使用mainHandler2.sendEmptyMessage(1)发送消息时,mainHandler2接收到消息并处理
这里写图片描述

2 Handler创建到任意指定线程中

2.1 直接在在新的线程中创建Handler和发送消息,这是不允许的

/*运行时报java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()异常*/
new Thread(new Runnable(){

                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        i++;
                    }
                    Handler handler=new Handler();//Handler放到当前新线程中
                    handler.sendEmptyMessage(3);
                }
            }).start();

2.2 继承HandlerThread封装自己的HandlerThread,并且在HandlerThread.onLooperPrepared()方法中调用new Handler创建Handler对象并完成发送消息和回调处理

    private class MyHandlerThread extends HandlerThread{

        public MyHandlerThread(String name) {
            super(name);
        }

        @Override
        protected void onLooperPrepared() {
            super.onLooperPrepared();
            for (int i = 0; i < 1000; i++) {
                i++;
            }
            Looper loop=this.getLooper();
            new Handler(loop){

                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    Log.d("HandlerTest", Thread.currentThread().getName()+"线程的Handler对象接收到消息" + msg.what);
                }

            }.sendEmptyMessage(3);
        }
    }
        public void testHandler(View view) {
        switch (view.getId()) {
        case R.id.btn_handler01:
            //在新的线程中创建Handler和发送消息
            new Thread(new Runnable(){

                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        i++;
                    }
                    Handler handler=new Handler();//Handler放到当前新线程中
                    handler.sendEmptyMessage(3);
                }

            }).start();

            break;
        case R.id.btn_handler02:
            MyHandlerThread myThread=new MyHandlerThread("子线程");//调用
            myThread.start();
            break;
        default:
            break;
        }
    }

运行结果
这里写图片描述

3 利用Handler(Looper loop)指定Handler运行的线程

3.1 使用Looper.getMainLooper()

case R.id.btn_handler02:
            /*MyHandlerThread myThread=new MyHandlerThread("子线程");
            myThread.start();*/
            //使用MainLooper来指定
            new Thread(new Runnable(){

                @Override
                public void run() {
                    Handler handler;

                    handler=new Handler(Looper.getMainLooper()){

                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            Log.d("HandlerTest", Thread.currentThread().getName()+"的Handler对象接收到消息" + msg.what);
                        }
                    };
                    handler.sendEmptyMessage(4);
                }
            }).start();

运行结果:
这里写图片描述

3.2 使用Looper.prepare()和Looper.loop()

//使用Looper.prepare()和Looper.loop()来指定运行的线程
            new Thread(new Runnable(){

                @Override
                public void run() {
                    Handler loophandler;
                    Looper.prepare();//必须执行
                    loophandler=new Handler(){

                        @Override
                        public void handleMessage(Message msg) {
                            super.handleMessage(msg);
                            Log.d("HandlerTest", Thread.currentThread().getName()+"的Handler对象接收到消息" + msg.what);
                        }

                    };
                    loophandler.sendEmptyMessage(5);
                    Looper.loop();
                }

            }).start();

运行结果:
这里写图片描述
通过以上两个小实验,我们可以知道,通过指定Looper对象,我们就可以控制Handler运行在指定的线程。

4 同一个Handler中不同的消息不可能同时执行,也不会交替执行

首先封装我们自己的一个Handler来处理两条消息

    private class ProcessMessageHandler extends Handler{

        public ProcessMessageHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d("HandlerTest", "Start:ProcessMessageHandler对象接收到第一条消息" + msg.what);
            try{
                Thread.sleep(5000);//阻塞5秒,是为了避免UI被阻塞,把Handler放到了另外的线程
            }catch(Exception e){
                e.printStackTrace();
            }
            Log.d("HandlerTest", "End :ProcessMessageHandler对象接收到第二条消息" + msg.what);
        }
    }

模拟同时发送两条信息

HandlerThread threadProcess=new HandlerThread("Process");
            threadProcess.start();//启动HandlerThread
            Handler handlerProcess=new ProcessMessageHandler(threadProcess.getLooper());
            handlerProcess.sendEmptyMessage(6);//发送两条消息,每条消息都执行6s
            handlerProcess.sendEmptyMessage(7);

运行结果:

两条消息之间是不可能出现交替处理的,只有我们处理完一条消息之后,才会再从MessageQueue中读取下一条消息。
这里写图片描述

5 对于我们正在执行的Message,我们能否Remove掉?

5.1 Remove掉一个还没有开始执行的Message

首先还是先启动一条HandlerThread

private Handler workMsgHandler;
HandlerThread workingMsgThread=new HandlerThread("WorkingMsg");
        workingMsgThread.start();
        workMsgHandler=new ProcessMessageHandler(workingMsgThread.getLooper());

发送消息

workMsgHandler.sendEmptyMessage(10);//发送消息
workMsgHandler.sendEmptyMessage(12);//发送消息

执行Remove:

workMsgHandler.removeMessages(12);//5S前移除消息what为12

运行结果对比:

未移除what为12的消息:
这里写图片描述
移除了what为12的消息:
这里写图片描述

5.2Remove掉一个正在执行的Message

首先还是先启动一条HandlerThread

private Handler workMsgHandler;
HandlerThread workingMsgThread=new HandlerThread("WorkingMsg");
        workingMsgThread.start();
        workMsgHandler=new ProcessMessageHandler(workingMsgThread.getLooper());

发送消息之后

workMsgHandler.sendEmptyMessage(10);//发送消息

马上执行移除消息

workMsgHandler.removeMessages(10);//5S前移除消息what为10

运行结果:

很明显无法Remove掉正在执行的Message,如果呗Remove掉的话“End……”就不会被打印
这里写图片描述

5.3小结

一旦Message已经被执行,在执行的过程中不会被Remove掉;能Remove的只有未被执行的Message即Remove只能移除消息队列中的Message。

6同一个HandlerThread中使用多个Handler的MessageQueue不会混淆

7 利用弱引用去封装自己的Handler

虽然在Android中已经不推荐使用 WeakReference(防止内存泄漏,要保证内存被虚拟机回收,即弱引用关联的对象在JVM进行垃圾回收时总会被回收),甚至在高级版本中据说已经完全删除了弱引用,但是个人觉得若是用得恰当对自己开发的代码质量的提高有显著的效果。反之,则坑人坑己。any way,慎用为妙。

通过弱引用得到Activity的对象,进而在封装的Handler中使用Activity的方法交互

static class MyHandler extends Handler{
    private final WeakReference<Activity> mAct;//声明弱引用
    //声明构造方法供外部调用构MyHandler对象
    public MyHandler(MainActivity act){
        mAct=new  WeakReference<Activity>(act);
    }
    @Ovrride
    public void handleMessage(Message msg){
        //在这里获取MainActivity的引用并强转,调用相关方法showMessage
        ((MainActivity)mAct.get()).showMessage();
    }
}

使用自己封装的Handler发送消息

private MyHandler handler=new MyHandler();

8、Handler 发送封装后的Message

Message message = new Message();
Bundle bundle = new Bundle();
bundle.putInt("biz", ConstantUtil.MSG_SEND_TXT);
bundle.putString("content", question);
message.setData(bundle);
mHandler.sendControlMessage(message);

总结

Handler实例与消息处理是相关联的,发送和接收都要求匹配,只能依附在Handler线程或其子类中,所以Handler的所有操作都是在一个线程中。推荐一个GC在线源码阅读网站

以上是关于Android进阶——强大的异步不可或缺的组件Handler详解的主要内容,如果未能解决你的问题,请参考以下文章

Vue进阶-异步动态加载组件

Vue进阶-异步动态加载组件

Vue进阶-异步动态加载组件

Android自己定义组件系列——进阶实践

我的Android进阶之旅关于Android使用bindService()绑定服务,onServiceConnected()方法是异步回调的问题以及借鉴NotificationManager来优化(代

我的Android进阶之旅关于Android使用bindService()绑定服务,onServiceConnected()方法是异步回调的问题以及借鉴NotificationManager来优化(代