文章转载自第一代码原创文章:https://www.diyidaima.com/android/126.html
1、一个线程有几个Handler?
你在MainActivity new一个Handler,在LoginActivity也可以new 一个Handler,甚至在Fragment也可以new一个Handler,而这些线程均是主线程,所以一个线程可以有多个Handler;
2、一个线程有几个Looper,如何保证?
一个线程只有一个Looper。那如何保证一个线程只有一个Looper呢?首先通过下面的Looper源码,我们看到Looper是通过调用静态方法prepare()进行初始化的,而prepare()又调用了prepare(true)方法。
其中prepare(true)的true表示创建的子线程的Looper是一定能被停止的。
在该方法里面,首先调用了通过sThreadLocal调用了get()。sThreadLocal是ThreadLocal的实例,内部维护了一个和HashMap类似的key-value键值对,只是略微有所不同,其内部是通过一个静态类Values的table数组来保存键值对的,如table[0]存储的是ThreadLocal实例本身,而table[1]存储的是Looper实例。所以sThreadLocal.get()的目的是判断当前线程是否已经创建了Looper了。如果没有创建,则通过Looper构造方法创建了一个新的实例,里面初始化了MessageQueue(消息队列),然后使用sThreadLocal.set(…)进行关联。
那Looper是如何和线程Thread关联上的呢?查看下方ThreadLocal的部分源码,上方sThreadLocal.set(…)方法内部通过Thread.currentThread()获取到当前的线程currentThread,也就是给当前线程加入了指向Looper的变量,然后通过values(currentThread)获取到Thread的变量localValues,其类型为ThreadLocal.Values,然后再赋值给values,如果为null,则进行初始化,最后将ThreadLocal和Looper put进values里。这样当前线程就和Looper关联到了一起。
那如何保证唯一性呢? 我们再翻看源码,在Looper源码里,只有一个地方调用了sThreadLocal.set(…),这也保证了Looper被初始化的唯一性。
3.Handler内存泄露原因?为什么其他的内部类没有说有过这个问题?
首先是因为内部类持有外部类的引用,导致即使Activity等执行了onDestory()也不会释放,因为jvm识别到该activity被引用了,所以不会对其进行回收,导致内存泄露。那为什么其他的内部类没有说有过这个问题呢?主要是Handler可以发送延时类消息,消息不到时间不会被销毁。同时MessageQueue持有Message,Message持有了Handler,而Handler持有了Activity,只有消息被销毁了,才会销毁Handler,Handler才会销毁Activity。所以Handler内部类持有外部类的引用才会发生内存泄露。那如何解决Handler的内存泄露问题呢?
4.为什么主线程可以new Handler()呢?如果想要在子线程中new Handler()需要做些什么准备?
在ActivityThread(主线程)里有一个main()函数,它是每一个Android应用最早执行的函数。如下源码:
public static void main(String[] args) {
.....
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到,ActivityThread(主线程)的main函数已经执行了Looper.prepareMainLooper()和Looper.loop()。我们再来看看Looper.prepareMainLooper()的源码:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
我们看到,其内部使用prepare(false)初始化了一个Looper。所以说主线程一启动,在main()函数中,由系统已经帮我们完成执行prepare() 和 loop()了,所以可以直接new Handler(),不需要再进行Looper初始化了。并且主线程中的所有代码,全都运行在这两个函数(prepareMainLooper()/prepare() 和 loop())之间。所有的线程都必须要prepare()和loop(),如果子线程中想要进行Handler操作,就必须在子线程中执行prepare() 和 loop()。
5.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?
当MessageQueue没有Message时,loop()会阻塞状态,会一直卡在Message msg = queue.next()里。那此时我们需要调用Looper的quitSafely()或quit()函数,其内部均会调用MessageQueue的quit()把消息队列中的全部消息给移除掉,这样就释放了内存。而在quit()函数的最后一行,会执行一个nativeWake()函数,唤醒queue.next()里的nativePollOnce(ptr, nextPollTimeoutMillis),唤醒后,才能往下执行,发现 Message msg = mMessages是空的,就会执行dispose()和return null,此时Looper就跳出了死循环,释放线程。综上所述,子线程中维护的Looper,消息队列无消息的时,可以调用Looper的quitSafely()或quit()函数,使得Looper结束死循环,并且起到释放内存和线程的作用。
6.既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程中),那它内部是如何确保线程安全的?
在往MessageQueue添加数据时,使用syncchronized线程锁,在往MessageQueue取数据时,也使用syncchronized线程锁,保证消息不会混乱。
7.我们使用Message时应该如何创建它?
Message使用了享元设计模式,在销毁时并没有真正的销毁,只是将内部变量置为初始值或者说清零,在创建的时候,使用Message.obtain()进行创建,其内部会从队列头部取出Message对象复用。
8.使用Handler的postDelay后消息队列会有什么变化?
使用Handler的postDelayed(@NonNull Runnable r, long delayMillis)发送延时消息,该postDelayed内部会调用sendMessageDelayed(@NonNull Message msg, long delayMillis)方法,sendMessageDelayed又调用MessageQueue的enqueueMessage方法。如果MessageQueue为空,此时调用enqueueMessage会通过内部的nativeWake方法唤醒消息队列,消息队列被唤醒后,next方法会被执行,从而触发计算该Message执行需要等待的时间,计算完之后重新等待,直到时间到了才会触发相应操作。
9. Looper死循环为什么不会导致应用卡死?
我们先看主线程ActivityThread的main方法的源码:
public static void main(String[] args) {
Looper.prepareMainLooper();//创建Looper和MessageQueue对象,用于处理主线程的消息
ActivityThread thread = new ActivityThread();
thread.attach(false);//建立Binder通道 (创建新线程)
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
//如果能执行下面方法,说明应用崩溃或者是退出了...
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我们在Launch桌面点击app的图标时,会先向AMS请求启动根Activity,AMS又会向zygote请求启动应用程序进程socket,zygote会创建并启动应用程序进程,应用程序进程准备就绪后返回给AMS,AMS再启动根Activity。最终进入到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,我们的Activity的生命周期都是通过Handler机制处理的,包括 onCreate、onResume等方法。下面是loop方法循环:
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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
通过创建一个for死循环来处理消息 msg.target.dispatchMessage(msg);
真正卡死主线程操作的是在回调方法onCreate、onStart、onResume等操作时间过长,会导致掉帧甚至ANR,Looper.loop()本身不会导致应用卡死。主线程的死循环一直运行不会特别消耗CPU资源在主线程的MessageQueue没有消息时,便阻塞在loop的queue.next()中的nativePollOnce()方法里,此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生。所以说主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。