[TOC]
Handler可以说是Android框架里面很精髓的一部分了,面试必问,用的也最多,这篇文章带你彻底搞清楚Handler。
Handler是什么? 提到Handler大家一定不陌生,我们经常用它来切换线程,或者是说做一些延时任务等等。最常用的地方可能就是在网络请求中去切换到主线程中去操作UI。为什么要切换到主线程去操作UI呢?在这之前我们知道在Android里面所有的View都是线程不安全的,意思就是你不能多线程去操作UI,这是Android不允许的,它规定了你只能在主线程去操作UI。
总结一句话就是:Handler就是用于线程间通信,解决子线程无法访问UI的问题
Handler的几种常见使用方法 注意:Handler的无参构造已经弃用
1.作为内部类 作为内部类的使用方法相信大家在熟悉不过了:
class MainActivity : AppCompatActivity () { private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } private val myHandler = MyHandler() override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(R.layout.activity_main) mBinding.button.setOnClickListener { myHandler.sendEmptyMessage(0 ); } } inner class MyHandler () : Handler(Looper.myLooper()!!) { override fun handleMessage (msg: Message ) { super .handleMessage(msg) Toast.makeText(this @MainActivity , "处理消息" , Toast.LENGTH_SHORT).show() } } }
2.作为匿名内部类 class MainActivity : AppCompatActivity () { private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } private val myHandler = object :Handler(Looper.myLooper()!!){ override fun handleMessage (msg: Message ) { super .handleMessage(msg) Toast.makeText(this @MainActivity , "处理消息" , Toast.LENGTH_SHORT).show() } } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(mBinding.root) mBinding.button.setOnClickListener { myHandler.sendEmptyMessage(0 ); } } }
作为匿名内部类和作为内部类的方法差不多,不过是把内部类的名字去掉了。
3.作为静态内部类 作为静态类的使用方法就是把内部类变为静态类:
class MainActivity : AppCompatActivity () { private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } private val myHandler = MyHandler(this ) override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(mBinding.root) mBinding.button.setOnClickListener { myHandler.sendEmptyMessage(0 ) } } class MyHandler (context: Context) : Handler(Looper.myLooper()!!) { private val weakContext = WeakReference(context) override fun handleMessage (msg: Message ) { super .handleMessage(msg) Toast.makeText(weakContext.get (), "处理消息" , Toast.LENGTH_SHORT).show() } } }
在kotlin中不加inner关键字的内部类默认就是静态的。这里为什么用WeakReference去引用context?这是防止内存泄漏,等会说。
Handler的内存泄漏问题
内存泄漏可以说是面试必问的,也是我们开发者所必须熟知的。
什么是内存泄漏?
那么什么是内存泄漏呢?就是程序中已经动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费。
简单来说就是一个对象该被回收却没有被回收,造成了内存浪费。最常见的就是长生命周期引用短生命周期。
比如你的activity实例被一个静态变量引用,当你的activity销毁的时候,activity对象应该被回收,而静态变量会一直存在程序中,所以JVM会认为你的activity被一个静态变量引用,认为它还有用,它就不会回收activity实例,从而造成内存泄漏。
Handler虽然好用,但是也容易用错,最容易犯的错误就是内存泄漏。
在上述的1和2的使用方法都是会出现内存泄漏的情况的。为什么呢?因为内部类和匿名内部类(lamda表达式,回调也是一样的)会持有外部内的引用。正是因为有外部类的引用,所以你的Hander作为内部类才能拿到外部类的变量,比如context,view等等。但是也正是因为有这个引用,比如内部类Handler持有外部类Activity的引用,会导致你的内存泄漏。举个例子,我们知道Hander发送消息到处理消息都可能会有延迟,这就有可能Handler的存在时间比Activity的时间还长,假如此时你发送了一个延时消息,但是消息还没处理你就推出界面,这时候就会内存泄漏。因为Handler的生命周期比Acivity的生命周期长嘛。
如何检测内存泄漏 检测内存泄漏有一个很方便的开源库——-LeakCanary:
使用:
在build.gradle引入依赖:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
在重新运行你的APP,这时候你的桌面就会出现金丝雀的图标:
然后对着你的APP一顿测试,假如有内存泄漏就会弹出一个小黄鸟提示,然后你会看到这样的通知:
点击这个通知它会进行分析,分析完成之后再点进去就会进去小黄鸟应用,里面是你的APP所有的内存泄漏的记录:
点进去你就会看到泄漏对象的引用链:
接下来就靠你自己分析引用链来判断是哪里内存泄漏了。
解决Handler的内存泄漏: 上述说了,1和2会发生内存泄漏,是因为内部Handler引用了外部activity,那怎么解决呢?既然是Handler存在时间比Activity长那就缩短Handler的存在时间嘛,所以在onDestory中去移除所有发送的消息:
override fun onDestroy () { super .onDestroy() myHandler.removeCallbacksAndMessages(null ) }
这样在Activity被销毁的时候Handler所有的消息都被移除了,也就不存在对Activity的引用了,内存泄漏也就解决了。
而在3中Handler是用做静态内部类的,作为静态类的话就没有与外部类连接的通道了,这时候就只能传参,但为了防止使用强引用造成内存泄漏这里改为弱引用。Java里的四种引用这里做一个简单的介绍:
强引用(Strong Reference):强引用在代码中普遍的存在,类似于“Object obj = new Object()”,只要某个对象有强引用与之关联,JVM则无法回收该对象,即使在内存不足的情况下,JVM宁愿抛出OOM错误,也不会回收这种对象。
软引用(Soft Reference):软引用常常用来描述一些有用但是非必需的对象。对于软引用关联的对象,会在JVM内存不足时既OOM之前将这些对象列入回收范围,进行二次回收。如果这时回收还是没有足够的内存才会造成内存溢出异常。在JDK1.2之后,提供了SoftReference类来实现软引用。软引用一般用于网页的缓存图片的缓存等等比较耗时的操作,但是这些操作目前一般使用LruChche来实现,因此目前代码中很少见到SoftReference。
弱引用(Weak Reference):被弱引用关联的对象只能生产到下一次垃圾收集发生之前。当垃圾收集器工作室,无论内存是否足够,都会回收弱引用关联的对象。可以使用WeakReference类来实现弱引用。
虚引用(Phantom Reference):虚引用也称之为幽灵引用,或者幻影引用,它是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响。也无法通过虚引用来取得一个对象的实例,为一个对象设置为虚引用的唯一目的是希望这个对象被回收器回收时能收到一个系统通知,在JDK1.2之后,提供了Phantom Reference类来实现虚引用。
正式因为使用activity的context容易泄漏,我们toast一般使用application的context。因为application的生命周期一般都比较长,它是伴随你的app整个应用的。那么怎么使用呢?要使用,要知道application在哪里。在我们的注册文件,也就是声明activity的地方:
<manifest xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" > <application android:allowBackup ="true" android:dataExtractionRules ="@xml/data_extraction_rules" android:fullBackupContent ="@xml/backup_rules" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:supportsRtl ="true" android:theme ="@style/Theme.Teach" tools:targetApi ="31" > <activity android:name =".SecondActivity" android:exported ="false" /> <activity android:name =".MainActivity" android:exported ="true" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > </application > </manifest >
这个application就是系统默认的application,要使用我们自定义的application肯定的继承系统的application然后把context暴露出来:
class App : Application () { companion object { lateinit var mContext: App private set } override fun onCreate () { super .onCreate() mContext = this } }
Application本身就是context。但是同时也要注意不要让application去引用任何短生命周期对象。然后在注册文件中去替换掉系统的application:
<manifest xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:tools ="http://schemas.android.com/tools" > <application android:name =".App" android:allowBackup ="true" android:dataExtractionRules ="@xml/data_extraction_rules" android:fullBackupContent ="@xml/backup_rules" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:supportsRtl ="true" android:theme ="@style/Theme.Teach" tools:targetApi ="31" > <activity android:name =".SecondActivity" android:exported ="false" /> <activity android:name =".MainActivity" android:exported ="true" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > </application > </manifest >
然后在toast里面去用context:
class SecondActivity : AppCompatActivity () { private val mBinding by lazy { ActivitySecondBinding.inflate(layoutInflater) } private val myHandler = MyHandler() override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(mBinding.root) mBinding.button2.setOnClickListener { myHandler.sendEmptyMessageDelayed(0 , 10000 ) } } class MyHandler () : Handler(Looper.myLooper()!!) { override fun handleMessage (msg: Message ) { super .handleMessage(msg) Toast.makeText(App.mContext, "处理消息" , Toast.LENGTH_SHORT).show() } } }
这样你的静态Handler就不需要和外部的Activity去通信。
浅析Handler 上面我们说到,Handler的无参构造已经废弃,取而代之的是有参构造,传入的参数是Looper.myLooper()。为什么要传这个参数呢?因为Handler的创建是根据Looper来获取的,而Looper是和Thread对应的。Google认为原来的无参构造你没有指定Looper,这容易导致Looper的获取为空从而导致崩溃。所以原来的无参被废弃,让你自己传Looper等于是应用崩溃了是你的原因而不是官方代码出问题了。
那么Handler是怎么保证在主线程运行的,Looper、Thread、Handler又是怎么样的一个关系呢?听我慢慢讲来。
在子线程中创建Handler 在构建Handler的时候,我们会获取Looper并且传进去,那么Looper肯定会初始化。
我们创建的Handler一般都是主线程的Handler,那在子线程怎么创建Handler呢?
有人说直接new啊:
class MainActivity : AppCompatActivity () { private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(mBinding.root) mBinding.button.setOnClickListener { Thread{ val handler = Handler(Looper.myLooper()!!) }.start() } } }
那当你点击按钮的时候你的app就崩了,并且给你报Looper为空的错误:
java.lang.NullPointerException at com.wssg.teach.MainActivity.onCreate$lambda$1$lambda$0(MainActivity.kt:20) at com.wssg.teach.MainActivity.$r8$lambda$nMCVh1lEoyHQZBD9S2vny8Epw_k(MainActivity.kt) at com.wssg.teach.MainActivity$$ExternalSyntheticLambda0.run(D8$$SyntheticClass) at java.lang.Thread.run(Thread.java:761)
说明咱们的Looper还没初始化,子线程创建不了Handler,正确的Handler的创建方式:
class MainActivity : AppCompatActivity () { private val mBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } override fun onCreate (savedInstanceState: Bundle ?) { super .onCreate(savedInstanceState) setContentView(mBinding.root) var handler: Handler? = null mBinding.button.setOnClickListener { Thread { Looper.prepare() handler = object : Handler(Looper.myLooper()!!) { override fun handleMessage (msg: Message ) { super .handleMessage(msg) Log.d("RQRQRQ" , "handleMessage: 收到消息!!!!" ) } } Looper.loop() }.start() Thread { handler?.sendEmptyMessage(0 ) }.start() } } }
这里只是举个例子怎么在子线程创建Handler并且发送消息给它。首先开启了一个线程,Looper.prepare()就是初始化Looper,这样在下面创建Handler的时候Looper.myLooper才不会为null,然后Looper.loop()是开始接收Handler收到消息,没这行代码是收不到消息的,然后又开启了子线程给Handler发送一个消息,之后你就能在子线程看到消息收到了:
2023-04-06 22:23:38.673 5646-5706 RQRQRQ com.wssg.teach D handleMessage: 收到消息!!!!
日志也确实如此。
总结Handler的创建:
1.Looper.prepare() 初始化Looper
2.new Handler(Looper.myLooper) 创建Handler
3.Looper.loop() 开始接收消息
4.在其他地方发送消息
分析消息的插入流程 但是我们平常使用的时候并没有去初始化Looper.prepare(),而且也没有报错,那Android底层肯定初始化了Looper。那么在哪里呢?
我们在学java的时候总是会有一个main函数作为程序运行的入口对不对。而我们之前写Android也是用java写的,并且源码很多也是java,那源码里面肯定有一个main函数。在哪里呢?在ActivityThread里面。ActivityThrea也就是Android的Ui线程。我们在Android Studio里面全局搜索进去ActivityThread的源码,然后在代码里面搜索main函数:
一眼看去就看到了Looper.prepareMainLooper()和Looper.loop();两行代码,果然是在这初始化了Looper。而Looper.prepareMainLooper();本身也是调用Looper.prepare():
@Deprecated public static void prepareMainLooper () { prepare(false ); synchronized (Looper.class) { if (sMainLooper != null ) { throw new IllegalStateException ("The main Looper has already been prepared." ); } sMainLooper = myLooper(); } }
Looper初始化流程我们弄清楚了,我们看看具体的流程。我们先看我们常用的Handler的发送消息的方法,Handler有多个发送消息的方法:
sendMessage,sendEmptyMessage,post…….等等等等都是调用的sendMessageDelayed,sendMessageDelayed调用的是sendMessageAtTime():
public boolean sendMessageAtTime (@NonNull 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 (@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this ; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true ); } return queue.enqueueMessage(msg, uptimeMillis); }
可以看到,this也就是Handler存到了message的target,给了个唯一的Id,mAsynchronous是用于判断是否是加急消息,加急消息之后说。然后就是调用queue.enqueueMessage(msg, uptimeMillis);,queue是什么呢?是MessageQueue,就是MessageQueue的enqueueMessage:
boolean enqueueMessage (Message msg, long when) { if (msg.target == null ) { throw new IllegalArgumentException ("Message must have a target." ); } synchronized (this ) { if (msg.isInUse()) { throw new IllegalStateException (msg + " This message is already in use." ); } if (mQuitting) { IllegalStateException e = new IllegalStateException ( msg.target + " sending message to a Handler on a dead thread" ); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false ; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break ; } if (needWake && p.isAsynchronous()) { needWake = false ; } } msg.next = p; prev.next = msg; } if (needWake) { nativeWake(mPtr); } } return true ; }
Handler为null就抛出异常,然后有个关键字synchronized,synchronized是什么呢?它就是一把锁。我们用handler经常跨线程操作嘛,而synchronized就是保证线程安全的一个关键字,你可以理解为当一个线程在操作synchronized括号里的东西的时候,其他线程也执行到这里了就会进行等待,等到前一个把synchronized括号里的操作执行完了其他线程才会被唤醒执行这里面得代码。以此来保证一个数据同一时间只能被一个线程操作。
然后是判断message是否在使用,在使用就会抛出异常。如果正在停止,而这里又是异步操作,这里就会产出一个异常,再回收Message,当我们调用Looper.quit()去结束Looper的时候就会走到这里。而回收Message其实就是清除Message携带的所有信息:
@UnsupportedAppUsage void recycleUnchecked () { flags = FLAG_IN_USE; what = 0 ; arg1 = 0 ; arg2 = 0 ; obj = null ; replyTo = null ; sendingUid = UID_NONE; workSourceUid = UID_NONE; when = 0 ; target = null ; callback = null ; data = null ; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this ; sPoolSize++; } } }
清除所有的信息之后就把Message加入到sPool中。为什么要这么做呢?为了Message的复用。要知道Handler是Android框架经常用的东西,每次都new一个message多麻烦,也耗性能。Message是一个链表结构,把当前message回收后把当前的message插入表头。那我们怎么拿回收的Message呢?用Message.obtain():
public static Message obtain () { synchronized (sPoolSync) { if (sPool != null ) { Message m = sPool; sPool = m.next; m.next = null ; m.flags = 0 ; sPoolSize--; return m; } } return new Message (); }
从回收池里面拿,拿不到再new。事实上也推荐这种获取message的方式,在源码里也能经常看到,比如sendEmptyMessageDelayed:
public final boolean sendEmptyMessageDelayed (int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
简单了解了message,我们再看看enqueueMessage关键的部分:
boolean enqueueMessage (Message msg, long when) { ...... synchronized (this ) { ...... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break ; } if (needWake && p.isAsynchronous()) { needWake = false ; } } msg.next = p; prev.next = msg; } if (needWake) { nativeWake(mPtr); } } return true ; }
标记message在使用,把时间存到Message。mMessages是一个要处理的消息链表。
首先是第一个分支,p引用mMessages的表头。如果表头为空,发生时间为0(也就是需要立即执行)或者发生时间比mMessages表头早的话就把当前的Message作为表头插入消息链表。这里的needWake是用于唤醒线程的,这里把mBlocked是否需要锁住当前Message操作赋值给了needWake。被锁住了那肯定就需要唤醒嘛。
一般走的都是第二个分支,needWake需要唤醒的条件为当前Message是否被锁住,Handler是否为null,Message是否为异步消息。之后就是一个死循环去遍历链表的所有结点,插入结点的位置为,发生时间早于后一个结点或者是末尾。最后就是唤醒线程。
这里有两个疑问点:1.什么是异步消息,有什么用? 2.为什么要唤醒线程 我们先看什么是异步消息
什么是异步消息,同步屏障? Handler一般有三种消息:同步消息,异步消息,同步屏障
我们平常所发送的消息都是同步消息。
同步屏障用于处理异步消息。什么意思呢?
我们知道屏幕是有刷新率的,60hz,120hz,144hz等等。以60hz来说,它的所有界面都需要在16ms内画完,假如此时需要处理的消息太多了,16ms处理不完,此时界面就会发送卡顿现象。这时候就需要异步消息了。在发送异步消息后,如果在取消息过程中遇到了同步屏障就会去寻找异步消息,找到异步消息就返回,没有异步消息才会去找同步消息。简而言之,同步屏障就是一个标记,遇到这个标记就忽略同步消息去找异步消息。
怎么创建异步消息?
1.创建Handler的时候声明:
@UnsupportedAppUsage public Handler (@NonNull Looper looper, @Nullable Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
传入的async参数就代表此Handler发送的消息是否是异步消息。
private boolean enqueueMessage (@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this ; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true ); } return queue.enqueueMessage(msg, uptimeMillis); }
在插入消息的时候会去调用msg.setAsynchronous(true);将异步Handler发送的消息设置成异步消息
2.当然我们传Message的时候也可以直接将它设置成异步消息:
val msg = Message.obtain()msg.isAsynchronous = true
怎么发送同步屏障?通过postSyncBarrier()方法,但是postSyncBarrier()方法是隐藏的只能通过反射调用:
private var token: Int = 0 @SuppressLint("DiscouragedPrivateApi" ) @RequiresApi(Build.VERSION_CODES.M) fun postSyncBarrier () { val method = MessageQueue::class .java.getDeclaredMethod("postSyncBarrier" ); token = method.invoke(Looper.getMainLooper().queue) as Int } @RequiresApi(Build.VERSION_CODES.M) fun removeSyncBarrier () { val method = MessageQueue::class .java.getDeclaredMethod("removeSyncBarrier" , Int ::class .java); method.invoke(Looper.getMainLooper().queue, token) }
而postSyncBarrier()就是target也就是handler为null的message:
private int postSyncBarrier (long when) { synchronized (this ) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null ; Message p = mMessages; if (when != 0 ) { while (p != null && p.when <= when) { prev = p; p = p.next; } } if (prev != null ) { msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; } }
到这里我们简单分析了一下Message的插入流程,那么我们现在来看看Looper.prepare()和Looper.loop干了什么事情。
Looper.prepare() 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)); }
接收一个boolean值,quitAllowed是否允许这个Looper停止嘛。判断ThreadLocal是否为null,不为null抛出异常,为null就new 一个Looper设置到ThreadLocal里面。
ThreadLocal是什么呢?是一个以线程为作用域存储数据的一个类 ,什么叫以线程为作用域?就是说对于同一个ThreadLocal变量,不同的线程从里面去取数据能取到不同的值,就比如A线程在这个sThreadLocal里面存了A线程的Looper,B线程在这个sThreadLocal里面也存了B线程的Looper,然后A线程去和这个sThreadLocal说,我要取Looper出来,sThreadLocal会把A线程的Looper取出来,而不会取到B线程的Looper,B线程同理.构造这个ThreadLocal里面的泛型的实际类型就表明了这个ThreadLocal希望为线程存储什么类型的数据。:
public T get () { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) { ThreadLocalMap.Entry e = map.getEntry(this ); if (e != null ) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } private T setInitialValue () { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) map.set(this , value); else createMap(t, value); return value; } public void set (T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null ) map.set(this , value); else createMap(t, value); } public void remove () { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null ) m.remove(this ); } ThreadLocalMap getMap (Thread t) { return t.threadLocals; } void createMap (Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap (this , firstValue); }
ThreadLocal创建Map获取Map都是通过Thread去操作的,创建获取的都是Thread的ThreadLocalMap,map的key是ThreadLocal,value是泛型也就是Looper。也就是说一个ThreadLocal变量通过不同的线程获取不同的ThreadLocalMap,从而获取到自己的Looper
而ThreadLocalMap是ThreadLocal的静态内部类,是一个对键值对的弱引用包装:
为什么不直接用map而是要用个Entry去维护呢?这主要是一个不破坏原则,为了扩展WeakReference去弱化引用。他的设计很完美,感兴趣可以研究一下。
那我们现在知道了Looper.prepare就是用ThreadLocal去用线程获取map把Looper存进去key是ThreadLocal,那我们看看new Looper干了啥:
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)); }
private Looper (boolean quitAllowed) { mQueue = new MessageQueue (quitAllowed); mThread = Thread.currentThread(); }
MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }
new 了一个MessageQueue,并记录quitAllowed的值。
好了,prepare我们分析清清楚了。总结一下:
ThreadLocal通过Thread去获取对应的ThreadLocalMap,通过Map去存Looper实例,key是ThreadLocal,new Looper的时候初始化了MessageQueue消息队列把是否允许停止的值记录在了这里。也难怪没初始化Looper的时候我们调用Looper.myLoop()会报空指针异常:
public static @Nullable Looper myLooper () { return sThreadLocal.get(); }
没初始化之前sThreadLocal.get()获取的肯定为空。
接下来分析Looper.loop()
Looper.loop() @SuppressWarnings("AndroidFrameworkBinderIdentity") public static void loop () { final Looper me = myLooper(); if (me == null ) { throw new RuntimeException ("No Looper; Looper.prepare() wasn't called on this thread." ); } if (me.mInLoop) { Slog.w(TAG, "Loop again would have the queued messages be executed" + " before this one completed." ); } me.mInLoop = true ; Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); final int thresholdOverride = SystemProperties.getInt("log.looper." + Process.myUid() + "." + Thread.currentThread().getName() + ".slow" , 0 ); me.mSlowDeliveryDetected = false ; for (;;) { if (!loopOnce(me, ident, thresholdOverride)) { return ; } } }
抛弃对代码健壮性的处理,关键的代码只有这么几行:
@SuppressWarnings("AndroidFrameworkBinderIdentity") public static void loop () { final Looper me = myLooper(); ...... ...... for (;;) { if (!loopOnce(me, ident, thresholdOverride)) { return ; } } } public static @Nullable Looper myLooper () { return sThreadLocal.get(); }
就是开启了一个死循环,一直调用loopOnce(),如果返回flase就结束死循环。那我们看看loopOnce:
private static boolean loopOnce (final Looper me, final long ident, final int thresholdOverride) { Message msg = me.mQueue.next(); if (msg == null ) { return false ; } ...... try { msg.target.dispatchMessage(msg); if (observer != null ) { observer.messageDispatched(token, msg); } dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0 ; } catch (Exception exception) { if (observer != null ) { observer.dispatchingThrewException(token, msg, exception); } throw exception; } finally { ThreadLocalWorkSource.restore(origWorkSource); if (traceTag != 0 ) { Trace.traceEnd(traceTag); } } ...... msg.recycleUnchecked(); return true ; }
这里除去了无关的代码,关键的就两行Message msg = me.mQueue.next();和msg.target.dispatchMessage(msg),先拿消息,然后处理消息。
看看Message msg = me.mQueue.next();
@UnsupportedAppUsage Message next () { ...... for (;;) { if (nextPollTimeoutMillis != 0 ) { Binder.flushPendingCommands(); } nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this ) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null ; Message msg = mMessages; if (msg != null && msg.target == null ) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null ) { if (now < msg.when) { nextPollTimeoutMillis = (int ) Math.min(msg.when - now, Integer.MAX_VALUE); } else { mBlocked = false ; if (prevMsg != null ) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null ; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { nextPollTimeoutMillis = -1 ; } if (mQuitting) { dispose(); return null ; } ........ } for (int i = 0 ; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null ; boolean keep = false ; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception" , t); } if (!keep) { synchronized (this ) { mIdleHandlers.remove(idler); } } } ...... } }
又是一个死循环,每次进入循环都会调用native层的方法睡眠当前线程,而睡眠时间是在for循环中去计算的。首先是判断是不是遇到了同步屏障,是就去找异步消息而忽略同步消息。之后拿到了消息会判断跟当前的时间大小,比当前时间晚,就会睡眠直到消息的执行时间。否则把当前的message从消息队列中删除并返回。如果没拿到消息说明没有消息要处理了,就一直睡眠,直到有消息进来。如果中途调用Looper.quit就直接退出程序。
最后是没有任何消息需要处理才会去处理IdleHandler,那么它是什么呢?它就是一个优先级最低的Handler,当你有一个不那么重要的任务需要处理的时候你就可以用它。怎么使用呢?使用MessageQueue添加:
Looper.myQueue().addIdleHandler { ... ... false }
最后再看看
Looper.quit() public void quit () { mQueue.quit(false ); }
void quit (boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException ("Main thread not allowed to quit." ); } synchronized (this ) { if (mQuitting) { return ; } mQuitting = true ; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } nativeWake(mPtr); } }
就是把mQuitting的值设为true,然后移除所有的Message,唤醒线程结束程序。
好了整体的流程我们大概讲完了,我们再来捋一捋,ThreadLocal对应线程的ThreadLcoalMap,ThreadLocalMap对应保存了ThreadLocal和Looper,Looper对应一个MessageQueue。Looper.loop里面有个死循环一直取消息,取不到消息就睡眠当前线程,取到就回调dispatchMessage方法。sendMessage就是在MessageQueue里面插入消息。
我们再看看HandlerThread和IntentService
HandlerThread public class HandlerThread extends Thread { int mPriority; int mTid = -1 ; Looper mLooper; private @Nullable Handler mHandler; public HandlerThread (String name) { super (name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } public HandlerThread (String name, int priority) { super (name); mPriority = priority; } protected void onLooperPrepared () { } @Override public void run () { mTid = Process.myTid(); Looper.prepare(); synchronized (this ) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1 ; } public Looper getLooper () { if (!isAlive()) { return null ; } boolean wasInterrupted = false ; synchronized (this ) { while (isAlive() && mLooper == null ) { try { wait(); } catch (InterruptedException e) { wasInterrupted = true ; } } } if (wasInterrupted) { Thread.currentThread().interrupt(); } return mLooper; } @NonNull public Handler getThreadHandler () { if (mHandler == null ) { mHandler = new Handler (getLooper()); } return mHandler; } public boolean quit () { Looper looper = getLooper(); if (looper != null ) { looper.quit(); return true ; } return false ; } public boolean quitSafely () { Looper looper = getLooper(); if (looper != null ) { looper.quitSafely(); return true ; } return false ; } public int getThreadId () { return mTid; } }
Handler就是一个内部封装了Looper的Thread,方便我们创建handler,使用:
val handlerThread = HandlerThread("测试线程" )handlerThread.start() val mHandler = object :Handler(handlerThread.looper){ override fun handleMessage (msg: Message ) { super .handleMessage(msg) } }
还有一个使用HandlerThread的IntentService,不过在API26也就是Android 8以后被废弃了,官方推荐使用WorkManager。这里还是贴一下源码吧:
public abstract class IntentService extends Service { private volatile Looper mServiceLooper; @UnsupportedAppUsage private volatile ServiceHandler mServiceHandler; private String mName; private boolean mRedelivery; private final class ServiceHandler extends Handler { public ServiceHandler (Looper looper) { super (looper); } @Override public void handleMessage (Message msg) { onHandleIntent((Intent)msg.obj); stopSelf(msg.arg1); } } public IntentService (String name) { super (); mName = name; } public void setIntentRedelivery (boolean enabled) { mRedelivery = enabled; } @Override public void onCreate () { super .onCreate(); HandlerThread thread = new HandlerThread ("IntentService[" + mName + "]" ); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler (mServiceLooper); } @Override public void onStart (@Nullable Intent intent, int startId) { Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; mServiceHandler.sendMessage(msg); } @Override public int onStartCommand (@Nullable Intent intent, int flags, int startId) { onStart(intent, startId); return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; } @Override public void onDestroy () { mServiceLooper.quit(); } @Override @Nullable public IBinder onBind (Intent intent) { return null ; } @WorkerThread protected abstract void onHandleIntent (@Nullable Intent intent) ; }
其实就是一个HandlerThread的Service,现在也很少用它来做后台任务了。
Handler的讲解差不多是这些了,接下来我们来看几个常见面试题吧:
Question MessageQueue是干嘛呢?用的什么数据结构来存储数据?
MessageQueue就是一个用于存储消息、用链表实现的特殊队列结构。
Handler中延时消息是怎么实现的?
MessageQueue是一个按照消息时间排列的一个链表结构,根据消息的when字段插入即可。
MessageQueue的消息怎么被取出来的?
通过Looper的next方法取消息,里面是一个死循环,保证一定可以取到一条消息,如果没有可用消息,那么就阻塞在这里,一直到有新消息的到来。
阻塞的情况有俩种 没有消息 和当前消息还没有到要发送的时间
ThreadLocal运行机制?这种机制设计的好处?
ThreadLocal中有一个ThreadLocalMap变量,这个变量存储着键值对形式的数据。
key为this,也就是当前ThreadLocal变量。
value为T,也就是要存储的值。
每个线程中都有一个ThreadLocalMap,这样带来的好处就是,在不同的线程,访问同一个ThreadLocal对象,但是能获取到的值却不一样。
为什么ThreadLocalMap要弱引用ThreadLocal?(需要懂内存泄漏的相关知识)
因为ThreadLocalMap如果是强引用ThreadLocal的话,假如我们将 ThreadLocal置为null,会因为ThreadLocalMap持有了ThreadLocal的引用而无法被GC
为什么不能在子线程中更新UI?
因为Android中的UI控件不是线程安全的。
如果通过加锁来实现UI控件的线程安全会导致UI访问的效率降低影响用户体验。
Looper中的quitAllowed字段是啥?有什么用?
是否允许退出的标志字段。在quit方法中有被用到,如果这个字段为false,代表不允许退出,就会报错。
quit方法就是退出消息队列,终止消息循环。
首先设置了mQuitting字段为true。
然后判断是否安全退出,如果安全退出,就清空所有的延迟消息,之前没处理的非延迟消息还是需要处理
如果不是安全退出,就直接清空所有的消息
当调用了quit方法之后,mQuitting为true,enqueuemessage方法中消息就发不出去了,会报错。next方法返回null,那么loop方法中就会退出死循环。
Handler、Looper、MessageQueue、线程是一一对应关系吗?
一个线程只会有一个Looper对象,所以线程和Looper是一一对应的。
MessageQueue对象是在new Looper的时候创建的,所以Looper和MessageQueue是一一对应的。
Handler的作用只是将消息加到MessageQueue中,并后续取出消息后,根据消息的target字段分发给当初的那个handler,所以Handler对于Looper是可以多对一的,也就是多个Hanlder对象都可以用同一个线程、同一个Looper、同一个MessageQueue。
总结:Looper、MessageQueue、线程是一一对应关系,而他们与Handler是可以一对多的。
Looper.loop方法是死循环,为什么不会卡死(ANR)?
1.主线程需要这样的死循环来处理View界面的变化
2.而且没有消息的时候,handler会阻塞,主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以死循环也不会特别消耗CPU资源。
3.在收到跨线程消息后,会交给主线程的Hanlder再进行消息分发。所以Activity的生命周期都是依靠主线程的Looper.loop,当收到不同Message时则采用相应措施,比如收到msg=H.LAUNCH_ACTIVITY,则调用ActivityThread.handleLaunchActivity()方法,最终执行到onCreate方法。
4.真正导致ANR的原因不是死循环,而是因为在某个消息处理的时候操作时间过长
HandlerThread和IntentService的原理
HandlerThread就是一个封装了Looper的Thread类 通过获取 HandlerThread 的 looper 对象传递给 Handler 对象,可以在 handleMessage()方法中执行异步任务。 HandlerThread 与线程池不同,HandlerThread 背后只有一个线程,多任务时需要等待处理
IntentService 是一个继承了 Service 的抽象类,它封装了HandlerThread 和 Handler,当 IntentService 被第一次启动时,它的 onCreate()方法会被调用,onCreat()方法会创建一个HandlerThread,然后使用它的 Looper 来构造一个 Handler 对象,这样通过 handler 发送的消息最终都会在HandlerThread 中执行。
Handler内存泄漏的原理
内存泄漏的本质是因为长生命周期的对象持有了短生命周期对象的引用导致短生命周期的对象无法被正确回收。
Handler如果是activity的内部类,会导致handler持有activity的引用,而handler在发送message时,message会持有handler的引用,而message又被messageQueue引用,messagequeue又被looper引用,looper又被threadlocal引用,threadlocal又被主线程引用,从而导致handler内存泄漏。