一、Looper 既然是不停的死循环为啥,当前UI未发生anr卡死?
答:线程的死循环与安卓的ANR不是同一个概念。
线程是一段可执行的代码,当代码执行完后,线程生命周期便该终止了,线程退出。
对于安卓的应用主线程,我们是绝不希望会被运行一段时间,自己就退出,那么如何保证能一直存活呢?
简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。当然并非简单地死循环,无消息处理时线程会休眠。
但这里可能又引发了另一个问题,既然是死循环又如何去处理其他任务呢?
答案就是创建新的线程。ActivityThread #main中thread.attach(false)便会创建一个Binder线程(具体是指ApplicationThread,Binder的服务端,用于接收系统服务AMS发送来的事件),该Binder线程通过Handler将Message发送给主线程,处理安卓中的各种事件。
Looper不会一直消耗系统资源,当 Looper的 MessageQueue中没有消息时,或者定时消息没到执行时间时,当前持有 Looper的线程就会进入阻塞状态。真正会卡死主线程的操作是在回调方法onCreate/onStart/onResume等操作时间过长,会导致掉帧,甚至发生ANR,looper.loop本身不会导致应用卡死,然后这个 ANR 完全是两个概。
1、ANR拓展
(2)哪些场景会造成ANR?
- Service Timeout:比如前台服务在20s内未执行完成
对于前台服务,则超时为SERVICE_TIMEOUT = 20s
对于后台服务,则超时为SERVICE_BACKGROUND_TIMEOUT = 200s
- BroadcastQueue Timeout:比如前台广播在10s内未执行完成
对于广播队列有两个: foreground队列和background队列:
对于前台广播,则超时为BROADCAST_FG_TIMEOUT = 10s
对于后台广播,则超时为BROADCAST_BG_TIMEOUT = 60s
- ContentProvider Timeout:内容提供者,在publish过超时10s
- InputDispatching Timeout: 输入事件分发超时5s,包括按键和触摸事件(Activity中的事件)。
具体的触发、时间监控都在AMS中。如四大组件的创建超时也会引起ANR,具体可自行查看相应的逻辑。
二、Handler使用过程中会引起内存泄漏你是如何处理的?
1、内存泄漏的原因
非静态内部类是持有外部类的引用的,所以非静态的handler持有Activity的引用。当MessageQueue中有延时、或者未处理的消息时。Activity是不能被及时销毁的。
因为Message中持有handle的引用的。handler持有activity的引用。这样当 handle发送消息后立刻关闭当前的activity,activity得不到销毁。就会造成内存泄漏,内存泄漏多了就会oom。
2、解决方案
(1)静态类+弱引用
静态类不持有外部类的强引用,这一样Handle就不会持有Activity的引用了。弱引用可以保证对象被及时回收,这样可以保证Activity对象被及时回收。
(2)及时移除消息队列中的消息
消息队列中的message还是持有Handler的引用的,所以还要及时清除handle发送的消息,调用removeCallbacksAndMessages清除Message和Runnable。
三、Looper对象与当前线程的绑定使用到了ThreadLocal,说说ThreadLocal的工作原理
1、首先简介下吧
ThreadLocal是一个线程内部的数据存储类,他的作用域范围就是线程内。通过他可以在指定的线程中存储数据,数据存储后也只能在指定的线程中获得。在其他线程中获取不到。
2、使用场景介绍下
我们日常开发中遇到的很少,但是某些特殊场景下还是会碰到的如安卓的Handler源码、ActivityThread、AMS中就有体现。以Handler为例构造handler对象时会通过ThreadLocal来获取当前线程的Looper对象。
(1) 一般来说某些数据的作用域为线程,且不同的线程有不同的数据副本时就可以考虑使用ThreadLocal
(2)复杂逻辑下对象的传递。线程内监听器的使用。如果不使用ThreadLocal的话有两种解决方案:
a、将监听器通过方法参数在函数调用中传递:有局限性,当函数调用栈很深时通过这种方式是不能接受的。
b、将监听器设置为静态供线程内访问:这样是可行的,但是监听器器的数量与线程有关,代码扩展性比较差。以为监听器属于某个线程。所以添加线程同样需要添加一份线程内的监听器。
3、ThreadLocal 为啥要使用,有替代方案吗?
以handler的例子为准,由于Looper是与线程绑定的。如果不使用ThreadLocal的方式那就要提供一个全局(静态)的HashMap。
把Looper与对应的线程绑定。供Handler查找。这一样的话就必须创建一个类似LooperManager的管理类来维护。然而使用ThreadLocal以当前线程为key值,绑定Looper比较方便。少了些维护工作。
3、原理简介
ThreadLocal set方法可以看出 ThreadLocal 是通过ThreadLocalMap 进行存储数据的。具体就是把当前的ThreadLocal作为key,要存的对象作为value存入ThreadLocalMap 中。ThreadLocalMap 中通过Entry数组进行数据存储维护。
而Thread类有个ThreadLocalMap 类型的成员,当ThreadLocal进行set时实际就是调用当前线程的ThreadLocalMap 进行存储数据的。每个线程通过ThreadLocalMap与ThreadLocal绑定。这样就可确保每个线程都有自己的一份ThreadLocal对象。
Threadlocal get方法 首先获取当前线程的ThreadLocalMap对象。从ThreadLocalMap中取值,最终是调用的ThreadLocalMap的getEntry方法,通过当前的threadLocal为key进行查询。
四、安卓系统为啥不允许在子线程中更新UI
安卓的UI访问不是线程安全的(所有操作未加锁),所以在多线程并发访问时就会出现不可预期的结果。
那为什么安卓不在UI操作上加锁呢?这样多线程的操作不就安全了?
加锁有两个弊端,首先会让UI访问的逻辑变得复杂,其次加锁会降低UI访问的效率(其他的线程需要进行等待)所以最简单的方式就是采用单线程模型来处理UI对于开发者只需要在需要时坐下线程切换即可。
HandlerThread
还记得如何让主线程发送消息,子线程处理消息的栗子吗。明白了Handler的原理后你可能心里就知道在子线程中如何做了:
(1)子线程中创建Looper对象
(2)子线程中创建Handler对象重写handleMessage处理消息
(3)子线程中开启loop循环
(4)主线程发送一条消息
emmm,貌似这些东西都需要字节写,如果多个线程都有这样需求那么每个线程都要创建一套相应机制。。。好麻烦、也浪费线程资源。那么有没有更好的替代呢?答案是有的,系统提供了HandlerThread
简介
HandlerThread 继承于 Thread,所以它本质就是个 Thread。
与普通 Thread 的区别在于,它不仅建立了一个线程,并且创建了消息队列,有自己的 Looper,可以让我们在自己的线程中分发和处理消息,并对外提供自己的 Looper 的 get 方法。
HandlerThread 自带 Looper 使它可以通过消息队列来重复使用当前线程,节省系统资源开销。这是它的优点也是缺点,每个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理。
2、源码
/**
* A {@link Thread} that has a {@link Looper}.
* The {@link Looper} can then be used to create {@link Handler}s.
* <p>
* Note that just like with a regular {@link Thread}, {@link #start()} must still be called.
*/
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;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
//线程 run方法被写死为特定逻辑
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper(); //1、创建Looper对象
notifyAll(); // 通知取 Looper 的线程,此时 Looper 已经创建好了
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();//2、开启loop轮询
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/**
* @return a shared {@link Handler} associated with this thread
* @hide
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
全部源码如上,十分easy!总结:
- HandlerThread 是一个自带 Looper 的线程,因此只能作为子线程使用
- HandlerThread 必须配合 Handler 使用,HandlerThread 线程中具体做什么事,需要在 Handler 的 callback 中进行,因为HandlerThread 的 run 方法被写死了。
- 子线程的 Handler 与 HandlerThread 关系建立是通过构造子线程的Handler 传入 HandlerThread 的 Looper 。所以在此之前,必须先调用 mHandlerThread.start 让 run 方法跑起来 Looper 才能创建。
3、实战
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val handlerThread = HandlerThread("HandlerThread")
handlerThread.start()
// 2、子线程处理消息
//注意:这里不能直接handleMessage,要用Handler.Callback+handlerThread.looper方式
// 指明Handler处理消息回调时运行的looper所在线程。
val childHandler = Handler(handlerThread.looper, object : Handler.Callback {
@SuppressLint("SetTextI18n")
override fun handleMessage(msg: Message): Boolean {
if (msg.what == 0x11) {
btn.text = "子线程更新UI"
}
return false
}
})
// 1、主线程发送消息
btn.setOnClickListener {
val message = Message.obtain()
message.what = 0x11
childHandler.sendMessage(message)
}
}
}
(1)按钮点击前
(2)按钮点击后
这里不仅验证了主线程发送消息子线程处理消息的case,还可以验证安卓中并非主线程才可以更新UI。