编辑
2021-11-10
客户端技术
00
请注意,本文编写于 683 天前,最后修改于 105 天前,其中某些信息可能已经过时。

目录

Handler 使用方式
方式一 post(Runnable)
方式二 sendMessage(Message)
Handler 存在的问题
Handler 通信机制
1、Handler 发送消息
2、消息入队列
3、Looper 处理消息
4、handler处理消息
Handler 进行子线程通信
HandlerThread 本质
HandlerThread 用法
Handler 在系统中的身影

本文主要介绍Handle的原理、如何处理Handler可能导致的内存泄漏问题,需要深入理解Handler的机制,当我们在创建Handler的时候,要明白现在用的是哪个Looper?其他线程如何使用Looper进行通信,释放Looper的区别,以及HandlerThread的原理与用法等等内容。

Handler 使用方式

假若子线程允许访问 UI,则在多线程并发访问情况下,会使得 UI 控件处于不可预期的状态。传统解决办法:加锁,但会使得UI访问逻辑变的复杂,其次降低 UI 访问的效率。于是在Android中采用单线程模型处理 UI 操作,通过 Handler 切换到 UI 线程,解决子线程中无法访问 UI 的问题。

java
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.main_tv); new Thread(()-> { textView.setText(""); }).start(); } }

上面的代码会抛出CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

方式一 post(Runnable)

创建一个 Handler,通过 handler.post/postDelay,投递创建的 Runnable,在 run 方法中进行更新 UI 操作。

java
public class MainActivity extends AppCompatActivity { Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView textView = findViewById(R.id.main_tv); new Thread(()-> { handler.post(()-> { textView.setText(""); }); }).start(); } }

方式二 sendMessage(Message)

java
public class MainActivity extends AppCompatActivity { private static final int UPDATE_CONTENT_CODE = 101; Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case UPDATE_CONTENT_CODE: textView.setText(""); break; default: break; } } }; private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.main_tv); new Thread(()-> { Message msg = Message.obtain(); msg.what = UPDATE_CONTENT_CODE; // handler.sendEmptyMessage(UPDATE_CONTENT_CODE); handler.sendMessage(msg); }).start(); } }

Handler 存在的问题

当我们直接写出如下代码时:

java
Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } };

AndroidStudio会提示我们:This Handler class should be static or leaks might occur (anonymous android.os.Handler)即可能会发生内存泄漏问题!

此时就相当于这个Handler是一个内部类,非static内部类或者匿名内部类都会持有外部类的引用(textView)。如果此时Activty退出了, handler持有他的引用,则这个Activity 并不会被销毁,其实还是在内存中,所以就造成了内存泄漏 (Memory Leak) 的问题。

可以使用静态的内部类 + 虚引用可以解决这个问题:

java
public class MainActivity extends AppCompatActivity { private static final int UPDATE_CONTENT_CODE = 101; private TextView textView; private final Handler handler = new MyHandler(this); // 设置为静态内部类 static class MyHandler extends Handler { private final WeakReference<MainActivity> mActivityWR; // 在构造方法中传入需持有的Activity实例 public MyHandler(MainActivity activity) { // 使用WeakReference弱引用持有Activity实例 mActivityWR = new WeakReference<>(activity); } @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); MainActivity activity = mActivityWR.get(); if(activity != null) { switch (msg.what) { case UPDATE_CONTENT_CODE: activity.textView.setText(""); break; default: break; } } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); textView = findViewById(R.id.main_tv); new Thread(()-> { Message msg = Message.obtain(); msg.what = UPDATE_CONTENT_CODE; handler.sendMessage(msg); }).start(); } @Override protected void onDestroy() { super.onDestroy(); // 清除子线程提交的任务 handler.removeCallbacks(postRunnable); // 清除对应的消息 handler.removeMessages(UPDATE_CONTENT_CODE); } }

另外还有一种解决方式:

java
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); // 外部类Activity生命周期结束时,同时清空消息队列 & 结束Handler生命周期 }

这种方式会导致外部类Activity生命周期结束时消息队列中的所有消息被清空,所以更推荐使用静态内部类 + 弱引用的方式。

为什么这两种方式可以解决呢?先来看看Handler 通信机制吧:

Handler 通信机制

1、创建Handler,并采用当前线程的Looper创建消息循环系统;

2、Handler通过sendMessage(Message)或Post(Runnable)发送消息,调用enqueueMessage把消息插入到消息链表中;

3、Looper循环检测消息队列中的消息,若有消息则取出该消息,并调用该消息持有的handler的dispatchMessage方法,回调到创建Handler线程中重写的handleMessage里执行。

1、Handler 发送消息

java
handler.sendMessage(msg);

调用链如下:sendMessage -> sendMessageDelayed -> sendMessageAtTime -> enqueueMessage -> queue.enqueueMessage。其实就是Handler的MessageQueue插入了一个新的的Message对象。

对于postRunnable而言,通过post投递该runnable,调用getPostMessage,通过该runnable构造一个message,再通过 sendMessageDelayed投递,接下来和sendMessage的流程一样了。

java
public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); }

2、消息入队列

在enqueueMessage中,通过MessageQueue入队列,并为该message的target赋值为当前的handler对象,记住msg.target很重要,之后Looper取出该消息时,还需要由msg.target.dispatchMessage回调到该handler中处理消息:

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

在MessageQueue中,由Message的消息链表进行入队列。

3、Looper 处理消息

再说处理消息之前,先看Looper是如何构建与获取的:

1、构造Looper时,构建消息循环队列,并获取当前线程

java
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }

但该函数是私有的,外界不能直接构造一个Looper,而是通过Looper.prepare来构造的:

java
public static void prepare() { prepare(true); } 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)); }

这里创建Looper,并把Looper对象保存在sThreadLocal中,那sThreadLocal是什么呢?

java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

它是一个保存Looper的TheadLocal实例,而ThreadLocal是线程私有的数据存储类,可以来保存线程的Looper对象,这样Handler就可以通过ThreadLocal来保存于获取Looper对象了。

那么Looper在loop中如何处理消息呢?

在loop中,一个循环,通过next取出MessageQueue中的消息,若取出的消息为null,则结束循环,返回。设置消息为空,可以通过MessageQueue的quit和quitSafely方法通知消息队列退出。若取出的消息不为空,则通过msg.target.dispatchMessage回调到handler中去。

java
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" Long.toHexString(ident) + " to 0x" Long.toHexString(newIdent) + " while dispatching to " msg.target.getClass().getName() + " " msg.callback + " what=" + msg.what); } msg.recycleUnchecked(); } }

4、handler处理消息

Looper把消息回调到handler的dispatchMessage中进行消息处理:

若该消息有callback,即通过Post(Runnable)的方式投递消息,因为在投递runnable时,把runnable对象赋值给了message的callback。

若handler的mCallback不为空,则交由通过callback创建handler方式去处理。

否则,由最常见创建handler对象的方式,在重写handlerMessage中处理。

java
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }

Handler 进行子线程通信

java
public class OtherActivity extends AppCompatActivity { private static final String TAG = "OtherActivity"; Handler handler1; Handler handler2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_other); new ThreadA().start(); new ThreadB().start(); } class ThreadA extends Thread { @Override public void run() { super.run(); Looper.prepare(); handler1 = new Handler(Looper.myLooper()){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } if(handler2 != null) handler2.sendEmptyMessage(2) ; Looper.loop(); } } class ThreadB extends Thread { @Override public void run() { super.run(); Looper.prepare(); handler2 = new Handler(Looper.myLooper()){ @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } if(handler1 != null) handler1.sendEmptyMessage(5) ; Looper.loop(); } } }

1、调用Looper类的 prepare() 方法可以为当前线程创建一个消息循环,调用loop() 方法使之处理信息,直到循环结束。

2、Handler有几个构造重载,如果构造时不提供Looper类对象参数,默认会获取当前线程的Looper对象,即将当前线程的消息循环作为Handler关联的消息循环。

3、消息处理机制中,消息存放在一个消息队列中,而线程围绕这个队列进入一个无限循环,直到程序退出。

如果队列中有消息,线程就会把消息取出来,并分发给相应的Handler进行处理;

如果队列中没有消息,线程就会进入空闲等待状态,等待下一个消息的到来;

HandlerThread 本质

在上面利用Handler完成两个线程通信的代码中,需要调用Looper.prepare() 为一个线程开启一个消息循环,默认情况下Android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。 然后通过Looper.loop() 让Looper开始工作,从消息队列里取消息,处理消息。

所以要使用Handler完成线程之间的通信,首先需要调用Looper.prepare() 为该线程开启消息循环,然后创建Handle,然后调用 Looper.loop() 开始工作。这都是很常规的流程,所以:

HandlerThread 已经帮我们做好了这些事情,HandlerThread 本质上就是一个普通Thread,只不过内部建立了Looper。

HandlerThread 用法

java
public class OtherActivity extends AppCompatActivity { private static final String TAG = "OtherActivity"; private Handler handler1; private Handler handler2; private HandlerThread handlerThread1; private HandlerThread handlerThread2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_other); // 创建HandlerThread handlerThread1 = new HandlerThread("handle-thread-1"); handlerThread2 = new HandlerThread("handle-thread-2"); // 开启HandleThread handlerThread1.start(); handlerThread2.start(); handler1 = new Handler(handlerThread1.getLooper()) { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; handler2 = new Handler(handlerThread2.getLooper()) { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); Log.i(TAG, "handleMessage: ThreadName = " + Thread.currentThread().getName() + " msg.what = " + msg.what); } }; handler2.sendEmptyMessage(2); handler1.sendEmptyMessage(5); } // 释放资源 @Override protected void onDestroy() { super.onDestroy(); handlerThread1.quit(); handlerThread2.quitSafely(); } }

创建 Looper 并执行 loop() 的线程在任务结束的时候,需要手动调用 quit。反之,线程将由于 loop() 的轮询一直处于可运行状态,CPU 资源无法释放。更有可能因为 Thread 作为 GC Root 持有超出生命周期的实例引发内存泄漏。

SDK 推荐使用 quitSafely() 去终止 Looper,原因是其只会剔除执行时刻晚于当前调用时刻的 Message。这样可以保证 quitSafely 调用的那刻,满足执行时间条件的 Message 继续保留在队列中,在都执行完毕才退出轮询。

那么主线程需要 quit 吗?其实不需要,在内存不足的时候 App 由 AMS 直接回收进程。因为主线程极为重要,承载着 ContentProvider、Activity、Service 等组件生命周期的管理,即便某个组件结束了,它仍有继续存在去调度其他组件的必要! 换言之,ActivityThread 的作用域超过了这些组件,不该由这些组件去处理它的结束。比如,Activity destroy 了,ActivityThread 仍然要处理其他 Activity 或 Service 等组件的事务,不能结束。

Handler 在系统中的身影

其实Android本身就是一个巨大的消息处理机,ActivityThread类是Android APP进程的初始类,它的main函数是这个APP进程的入口。APP进程中UI事件的执行代码段都是由ActivityThread提供的。也就是说,主线程实例是存在的,只是创建它的代码我们不可见。ActivityThread的main函数就是在这个主线程里被执行的。

java
public final class ActivityThread { //... private static ActivityThread sCurrentActivityThread; public static ActivityThread currentActivityThread() { return sCurrentActivityThread; } private void attach(boolean system) { sCurrentActivityThread = this; //... } public static void main(String[] args) { //.... // 创建Looper和MessageQueue对象,用于处理主线程的消息 Looper.prepareMainLooper(); // 创建ActivityThread对象 ActivityThread thread = new ActivityThread(); // 建立Binder通道 (创建新线程) thread.attach(false); Looper.loop(); //消息循环运行 throw new RuntimeException("Main thread loop unexpectedly exited"); } }

Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施即可。

本文作者:Tim

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!