Android Handler详解

本期主要内容

  • 1: Handler是什么?
  • 2:为什么要使用Handler?
  • 3: Handler /Looper/ MessageQueue/Message究竟是做什么的?
  • 4:Handler如何去实现发送和处理消息

1、Handler是什么?

  • Handler 是一个消息分发对象。handler是Android给我们提供用来更新UI的一套机制,也是一套消息处理机制,我们可以发消息,也可以通过它处理消息。
  • 1:To schedule messages and runnables to be executed as some point in the future 定时任务
  • 2: To enqueue an action to be performed on a different thread than your own 在不同线程中执行任务

2、为什么要使用Handler?

  • 最根本的目的就是为了解决多线程并发的问题!打个比方,如果在一个activity中有多个线程,并且没有加锁,就会出现界面错乱的问题。但是如果对这些更新UI的操作都加锁处理,又会导致性能下降。处于对性能的问题考虑,Android给我们提供这一套更新UI的机制我们只需要遵循这种机制就行了。不用再去关系多线程的问题,所有的更新UI的操作,都是在主线程的消息队列中去轮训的。
  • 它把消息发送给Looper管理的MessageQueue,并负责处理Looper分发给他的消息。

3、Handler 、 Looper 、Message 这三者都与Android异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?

img

Handler 、 Looper 、Message.png

  • 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环。若消息队列为空,线程则会阻塞等待。
  • 那么Android消息机制主要是指Handler的运行机制,Handler运行需要底层的MessageQueue和Looper支撑。其中MessageQueue采用的是单链表的结构,Looper可以叫做消息循环。由于MessageQueue只是一个消息存储单元,不能去处理消息,而Looper就是专门来处理消息的,Looper会以无限循环的形式去查找是否有新消息,如果有的话,就处理,否则就一直等待着。
  • 我们知道,Handler创建的时候会采用当前线程的Looper来构造消息循环系统,需要注意的是,线程默认是没有Looper的,如果需要使用Handler就必须为线程创建Looper,因为默认的UI主线程,也就是ActivityThread,ActivityThread被创建的时候就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

Handler类包含如下方法用于发送、处理消息:

  • void handleMessage(Message msg):处理消息的方法。该方法通常用于被重写。
  • final boolean hasMessages(int what):检查消息队列中是否包含what属性为指定值的消息。
  • final boolean hasMessages(int what, Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
  • 多个重载的Message obtainMessage():获取消息。
  • sendEmptyMessage(int what):发送空消息。
  • final boolean sendEmptyMessageDelayed(int what, long delayMillis):指定多少毫秒后发送空消息。
  • final boolean sendMessage(Message msg):立即发送消息。
  • final boolean sendMessageDelayed(Message msg, long delayMillis):指定多少毫秒后发送消息。
  • Message:Handler接收和处理的消息对象。
    • 2个整型数值:轻量级存储int类型的数据。
    • 1个Object:任意对象。
    • replyTo:线程通信时使用。
    • what:用户自定义的消息码,让接收者识别消息。
  • MessageQueue:Message的队列。
    • 采用先进先出的方式管理Message。
    • 每一个线程最多可以拥有一个。
  • Looper:消息泵,是MessageQueue的管理者,会不断从MessageQueue中取出消息,并将消息分给对应的Handler处理。
    • 每个线程只有一个Looper。
    • Looper.prepare():为当前线程创建Looper对象。
    • Looper.myLooper():可以获得当前线程的Looper对象。
    • Handler:能把消息发送给MessageQueue,并负责处理Looper分给它的消息。

4:Handler如何去实现发送和处理消息

1: 使用Thread发送消息

img

Thread

2: 使用Runnable发送消息

img

Runnable.png

3: 使用Handler下载文件并更新进度条
1: 首先配置权限信息 读写权限问题

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.MOUNT_FORMAT_FILESYSTEMS" />

2: 在Android 6.0之后必须动态检测权限。所以在下载操作开始之前需要进行权限判断

img

Android6.0.png

img

发送消息,准备更新UI操作.png

img

handleMessage.png

权限问题处理方法

img

权限问题处理方法.png

4: 使用 handler实现倒计时功能
首先强调一下需要注意的问题吧
Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。

img

Handler

但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告:

img

warning.png

以下几种情况都可能存在内存泄漏的情况

1.当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。

2.当一个Handler在主线程进行了初始化之后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上已经发送的消息已经包含了一个Handler实例的引用,只有这样Looper在处理到这条消息时才可以调用Handler#handleMessage(Message)完成消息的正确处理。

3.在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。

解决方式

  • 要解决这种问题,思路就是不适用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用弱引用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。

img

🤢.png

面试要点

  • 这里 我们可以逐步分析:
public Handler(Callback callback, boolean async) {
        
       //得到主线程(已经被ActivityThread初始化好)的looper
       mLooper = Looper.myLooper();
       if (mLooper == null) {
           throw new RuntimeException(
               "Can't create handler inside thread that has not called Looper.prepare()");
       }
       
       //得到主线程(已经被ActivityThread初始化好)的looper的MessageQueue,注释:①
       mQueue = mLooper.mQueue;
       mCallback = callback;
       mAsynchronous = async;
}

注意,看到了吗? 这里是核心:mLooper = Looper.myLooper();,

public static Looper myLooper() {
     return sThreadLocal.get();
}

而这个Looper对象,是在我们启动程序的时候,也就是ActivityThread 中的main方法帮我们初始化的,也就是我们主线程的Looper。

public static void prepareMainLooper() {
     ···
     prepare(false);
     ···
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
          throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

我们在子线程 使用 handler的实例进行 sendMessage(Message msg) 的时候:

    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        //这里的代码和上面的注释①对应。
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

最终走向enqueueMessage:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

最终把我们的Message,放进了主线程的MessageQueue里面进行循环!

link

posted @ 2022-11-17 11:41  鲸小鱼-  阅读(2031)  评论(0编辑  收藏  举报