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异步消息处理线程相关的概念。那么什么叫异步消息处理线程呢?
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发送消息
Thread
2: 使用Runnable发送消息
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之后必须动态检测权限。所以在下载操作开始之前需要进行权限判断
Android6.0.png
发送消息,准备更新UI操作.png
handleMessage.png
权限问题处理方法
权限问题处理方法.png
4: 使用 handler实现倒计时功能
首先强调一下需要注意的问题吧
Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。
Handler
但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告:
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设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。
🤢.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里面进行循环!