ActivityThread中做了哪些关于Handler的工作?(为什么主线程不需要单独创建Looper)
主要做了两件事:
- 1、在main方法中,创建了主线程的
Looper
和MessageQueue
,并且调用loop方法开启了主线程的消息循环。
public static void main(String[] args) { Looper.prepareMainLooper(); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
- 2、创建了一个Handler来进行四大组件的启动停止等事件处理
final H mH = new H(); class H extends Handler { public static final int BIND_APPLICATION = 110; public static final int EXIT_APPLICATION = 111; public static final int RECEIVER = 113; public static final int CREATE_SERVICE = 114; public static final int STOP_SERVICE = 116; public static final int BIND_SERVICE = 121;
IdleHandler是啥?有什么使用场景?
之前说过,当MessageQueue
没有消息的时候,就会阻塞在next方法中,其实在阻塞之前,MessageQueue
还会做一件事,就是检查是否存在IdleHandler
,如果有,就会去执行它的queueIdle
方法。
private IdleHandler[] mPendingIdleHandlers; Message next() { int pendingIdleHandlerCount = -1; for (;;) { synchronized (this) { //当消息执行完毕,就设置pendingIdleHandlerCount if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } //初始化mPendingIdleHandlers if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } //mIdleHandlers转为数组 mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // 遍历数组,处理每个IdleHandler for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } //如果queueIdle方法返回false,则处理完就删除这个IdleHandler if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; } }
当没有消息处理的时候,就会去处理这个mIdleHandlers
集合里面的每个IdleHandler
对象,并调用其queueIdle
方法。最后根据queueIdle
返回值判断是否用完删除当前的IdleHandler
。
然后看看IdleHandler
是怎么加进去的:
Looper.myQueue().addIdleHandler(new IdleHandler() { @Override public boolean queueIdle() { //做事情 return false; } }); public void addIdleHandler(@NonNull IdleHandler handler) { if (handler == null) { throw new NullPointerException("Can't add a null IdleHandler"); } synchronized (this) { mIdleHandlers.add(handler); } }
ok,综上所述,IdleHandler
就是当消息队列里面没有当前要处理的消息了,需要堵塞之前,可以做一些空闲任务的处理。
常见的使用场景有:启动优化
。
我们一般会把一些事件(比如界面view的绘制、赋值)放到onCreate
方法或者onResume
方法中。但是这两个方法其实都是在界面绘制之前调用的,也就是说一定程度上这两个方法的耗时会影响到启动时间。
所以我们可以把一些操作放到IdleHandler
中,也就是界面绘制完成之后才去调用,这样就能减少启动时间了。
但是,这里需要注意下可能会有坑。
如果使用不当,IdleHandler
会一直不执行,比如在View的onDraw方法
里面无限制的直接或者间接调用View的invalidate方法
。
其原因就在于onDraw方法中执行invalidate
,会添加一个同步屏障消息,在等到异步消息之前,会阻塞在next方法,而等到FrameDisplayEventReceiver
异步任务之后又会执行onDraw方法,从而无限循环。
具体可以看看这篇文章:https://mp.weixin.qq.com/s/dh_71i8J5ShpgxgWN5SPEw
HandlerThread是啥?有什么使用场景?
直接看源码:
public class HandlerThread extends Thread { @Override public void run() { Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); }
哦,原来如此。HandlerThread
就是一个封装了Looper的Thread类。
就是为了让我们在子线程里面更方便的使用Handler。
这里的加锁就是为了保证线程安全,获取当前线程的Looper对象,获取成功之后再通过notifyAll
方法唤醒其他线程,那哪里调用了wait
方法呢?
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; }
就是getLooper
方法,所以wait的意思就是等待Looper创建好,那边创建好之后再通知这边正确返回Looper。
IntentService是啥?有什么使用场景?
老规矩,直接看源码:
public abstract class IntentService extends Service { 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); } } @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); }
理一下这个源码:
- 首先,这是一个
Service
- 并且内部维护了一个
HandlerThread
,也就是有完整的Looper在运行。 - 还维护了一个子线程的
ServiceHandler
。 - 启动Service后,会通过Handler执行
onHandleIntent
方法。 - 完成任务后,会自动执行
stopSelf
停止当前Service。
所以,这就是一个可以在子线程进行耗时任务,并且在任务执行后自动停止的Service
。
BlockCanary使用过吗?说说原理
BlockCanary
是一个用来检测应用卡顿耗时的三方库。
上文说过,View的绘制也是通过Handler来执行的,所以如果能知道每次Handler处理消息的时间,就能知道每次绘制的耗时了?那Handler消息的处理时间怎么获取呢?
再去loop方法中找找细节:
public static void loop() { for (;;) { // 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); } } }
可以发现,loop方法内有一个Printer
类,在dispatchMessage
处理消息的前后分别打印了两次日志。
那我们把这个日志类Printer
替换成我们自己的Printer
,然后统计两次打印日志的时间不就相当于处理消息的时间了?
Looper.getMainLooper().setMessageLogging(mainLooperPrinter); public void setMessageLogging(@Nullable Printer printer) { mLogging = printer; }
这就是BlockCanary的原理。
具体介绍可以看看作者的说明:http://blog.zhaiyifan.cn/2016/01/16/BlockCanaryTransparentPerformanceMonitor/
说说Hanlder内存泄露问题。
这也是常常被问的一个问题,Handler
内存泄露的原因是什么?
"内部类持有了外部类的引用,也就是Hanlder持有了Activity的引用,从而导致无法被回收呗。"
其实这样回答是错误的,或者说没回答到点子上。
我们必须找到那个最终的引用者,不会被回收的引用者,其实就是主线程,这条完整引用链应该是这样:
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
具体分析可以看看我之前写的这篇文章:https://juejin.cn/post/6909362503898595342
利用Handler机制设计一个不崩溃的App?
主线程崩溃,其实都是发生在消息的处理内,包括生命周期、界面绘制。
所以如果我们能控制这个过程,并且在发生崩溃后重新开启消息循环,那么主线程就能继续运行。
Handler(Looper.getMainLooper()).post { while (true) { //主线程异常拦截 try { Looper.loop() } catch (e: Throwable) { } } }
还有一些特殊情况处理,比如onCreate内发生崩溃,具体可以看看文章
《能否让APP永不崩溃》https://juejin.cn/post/6904283635856179214
JVM中如何决定对象是否可以回收
JVM中通过可达性分析算法来决定对象是否可以回收。
具体做法就是把内存中所有对象之间的引用关系看做一条关系链,比如A持有B的引用,B持有C的引用。而在JVM中有一组对象作为GC Root,也就是根节点,然后从这些节点开始往下搜索,查看引用链,最后判断对象的引用链是否可达来决定对象是否可以被回收。
为了方便大家理解,我画了一张图来说明:
很明显,ABCD四个引用都是GCRoot
可达的,通俗点讲,就是跟GCRoot直接或间接有关系,有线连着的。而EF虽然直接连着线,但是他们和GCRoot是没关系的,也就是GCRoot
不可达的对象组。
所以当GC发生的时候,EF就会被回收。
GC发生的内存区域
在说GC发生的内存区域之前,我们先聊聊JVM中的内存分配。
在JVM中,主要有内存分成了五个数据区域:
程序计数器
:线程私有,主要用作记录当前线程执行的位置。虚拟机栈
:线程私有,描述Java方法执行的内存模型。本地方法栈
:线程私有,描述本地(native)方法执行的内存模型。堆
:存放对象实例。方法区
:存放类信息、常量、静态变量等
通过上面的介绍,我们了解到前三个都是线程私有,所以会随着线程的死亡而消失。
而后面两块内存区域,也就是堆和方法区是所有线程共有的,如果不处理可能内存就会一直增长,直到超出可用内存。所以需要借助GC机制对这些区域内的无用内存进行回收,特别是堆区的内存,因为堆区就是存储对象实例的。
GC发生的时机
那具体什么时候会被回收呢?主要有两种情况:
- 在堆内存中分配时,如果因为可用
剩余空间不足
导致对象内存分配失败,这时系统会触发一次 GC。 - 在应用层,开发者可以调用
System.gc()
来请求一次 GC。
GCRoot的类型
刚才说过了可达性分析算法,所以大家应该知道GCRoot的重要性了。
GCRoot
,说白了就是JVM认证的可以作为老大的人选,只有这些对象是可以作为引用链的头头,掌管并保护着有用的引用。
在Java中,有以下几种对象可以被作为GCRoot
,这些对象是不会被GC的:
- Java 虚拟机栈(局部变量表)中的引用的对象。
这里又涉及到一个问题了,什么是局部变量表。
刚才说过虚拟机栈
是用于支持方法调用或者执行的数据结构,具体是怎么操作的呢?
当某个方法被执行,就会在虚拟机栈中创建一个栈帧
,也就是一个方法就对应着一个栈帧,栈帧会管理方法调用和执行所有的数据结构。
而栈帧中又分为几块存储空间,进行存储方法对应的不同的数据结构,比如局部变量表
就是用于存储方法参数和方法内创建的局部变量。
所以这第一个GC Root
指得就是方法的参数或者方法中创建的参数。
public class GCTest { public static void test1(){ //局部变量作为GCRoot GCRoot root=new GCRoot(); System.gc(); } }
顺便说下栈帧中其他几个内存结构:
局部变量表
:存储方法参数和方法内创建的局部变量操作数栈
:后入先出栈。当方法执行过程中,就会通过操作数栈来进行参数传递,又或者进行加数动态连接
:支持方法调用过程中的动态连接。返回地址
:在方法退出之后,都需要返回到方法被调用的位置,程序才能继续执行,方法返回时可能需要在栈帧中保存一些信息,用来帮助恢复它的上层方法的执行状态,而这个返回地址区域就是用于存储返回地址信息的。一般方法正常退出时,是可以将调用者的PC计数器值作为返回地址。- 方法区中静态引用指向的对象。
这个很好理解,指得就是静态变量。
public class GCTest { private static GCRoot root2; public static void main(String[] args) { //静态变量作为GCRoot root2=new GCRoot(); System.gc(); } }
- 仍处于存活状态中的线程对象。
活着的线程,比如主线程,上一篇文章就说过Handler内存泄露的原因就是被主线程所引用,所以无法被回收。
Thread root3=new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } }); public void test2(){ //活着的线程作为GCRoot root3.start(); System.gc(); }
- Native 方法中 JNI 引用的对象。
在JNI中有如下三种引用类型可供使用:
- 局部引用
- 全局引用
- 弱全局引用
其中局部引用和全局引用都可以作为GC Root
,不会被GC回收。
编译打包的过程中有哪些task会执行
//aidl 转换aidl文件为java文件 > Task :app:compileDebugAidl //生成BuildConfig文件 > Task :app:generateDebugBuildConfig //获取gradle中配置的资源文件 > Task :app:generateDebugResValues // merge资源文件 > Task :app:mergeDebugResources // merge assets文件 > Task :app:mergeDebugAssets > Task :app:compressDebugAssets // merge所有的manifest文件 > Task :app:processDebugManifest //AAPT 生成R文件 > Task :app:processDebugResources //编译kotlin文件 > Task :app:compileDebugKotlin //javac 编译java文件 > Task :app:compileDebugJavaWithJavac //转换class文件为dex文件 > Task :app:dexBuilderDebug //打包成apk并签名 > Task :app:packageDebug
简单介绍v1、v2、v3、v4签名
之前大家比较熟知的签名工具是JDK提供的jarsigner,而apksigner是Google专门为Android提供的签名和签证工具。
其区别就在于jarsigner只能进行v1签名,而apksigner可以进行v2、v3、v4签名。
v1签名
v1签名方式主要是利用META-INFO
文件夹中的三个文件。
首先,将apk中除了META-INFO文件夹中的所有文件进行进行摘要写到 META-INFO/MANIFEST.MF;然后计算MANIFEST.MF文件的摘要写到CERT.SF;最后计算CERT.SF的摘要,使用私钥计算签名,将签名和开发者证书写到CERT.RSA。
所以META-INFO文件夹中这三个文件就能保证apk不会被修改。
但是缺点也很明显,META-INFO文件夹不会被签名,所以美团针对这种签名方式设计了一种多渠道打包方案:
利用pythone在META-INFO文件夹中创建一个文件,其名称就是渠道名,然后用java去读取文件名获取渠道。
v2签名
Android7.0
之后,推出了v2签名,为了解决v1签名速度慢以及签名不完整的问题。
apk本质上是一个压缩包,而压缩包文件格式一般分为三块:
文件数据区,中央目录结果,中央目录结束节。
而v2要做的就是,在文件中插入一个APK签名分块,位于中央目录部分之前,如下图:
这样处理之后,文件就完成无法修改了。
v3签名
Android 9
推出了v3签名方案,和v2签名方式基本相同,不同的是在v3签名分块中添加了有关受支持的sdk版本和新旧签名信息,可以用作签名替换升级。
v4签名
Android 11
推出了v4签名方案。
v4 签名基于根据 APK 的所有字节计算得出的 Merkle 哈希树。它完全遵循 fs-verity 哈希树的结构,将签名存储在单独的.apk.idsig 文件中。
参考
《Android开发艺术探索》
https://juejin.cn/post/6896751245722615815
https://juejin.cn/post/6891911483379482637
https://mp.weixin.qq.com/s/kQmH2GnwW8FK-yNmWcheTA
https://segmentfault.com/a/1190000021357383
https://blog.csdn.net/lmj623565791/article/details/72859156
https://developer.android.google.cn/guide/components/services#Lifecycle
http://gityuan.com/2017/03/10/job_scheduler_service/
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc?id=1856
https://www.zhihu.com/question/34652589
https://segmentfault.com/a/1190000003063859
https://juejin.cn/post/6844904150140977165
https://juejin.cn/post/6893791473121280013
https://www.jianshu.com/p/bfb13eb3a425
https://segmentfault.com/a/1190000020386580
https://www.jianshu.com/p/02db8b55aae9
https://kaiwu.lagou.com/course/courseInfo.htm?courseId=67#/detail/pc
https://www.runoob.com/design-pattern/design-pattern-tutorial.html