第一部分、Java基础
一、内存分配
方法区、堆、虚拟机栈、本地方法栈、程序计数器
其中方法区、堆为公共内存区域,虚拟机栈、本地方法栈、程序计数器为线程私有
1 程序计数器PC
记录正在执行的虚拟机字节码指令的地址,线程切换时,处理器根据PC还原到需执行的位置
JVM基于栈,DVM基于寄存器,栈指令紧凑,寄存器指令长但指令数少(比如少了压/出栈指令)
2 虚拟机栈
虚拟机栈是线程私有的,是由栈帧组成的栈,栈顶帧为活动栈, 方法调用其他方法时,会将对应栈帧压倒栈顶,执行完后弹出,直至所有栈帧弹出,当前runnable执行结束
栈帧是用于给虚拟机进行方法调用和方法执行的数据结构,每个方法调用产生一个栈帧, 每个栈帧包含局部变量表、操作数栈、动态链接、返回地址等,一个栈帧对应一个方法,对应多条指令(操作数)
动态链接是指向运行时常量池的方法引用,支持方法调用过程中将class文件中的符号引用转化为直接引用
class文件中的符号引用,在类解析阶段,或第一次使用时转化为直接引用,称为静态解析;在运行时转化为直接引用,称为动态链接
返回地址保存了方法正常退出时的PC计数值,方法异常退出时,返回地址是异常处理器表确定的,不保存在栈帧中
本地方法栈与虚拟机栈类似,只不过针对native方法
4 堆
方法的实例都分配在此区域,是垃圾回收GC管理的主要区域,一般分为新生代和老年代,新生代又分为eden、survivor0、survivor1
5 方法区
存储已被JVM加载的类信息(版本、字段、方法、接口)、常量、静态变量、代码
方法区是规范层的定义,永久区是HotSpot在JDK1.7及以下的实现,1.8后改为元空间实现
二、GC回收
可达性分析
JVM把内存中对象之间的引用关系看做一张图(离散图论),由多颗有向树组成,树的根节点称为GC Root
从GC Root开始向下搜索,搜索所经过的路径称为引用链,通过引用链是否可达来判断对象是否可以回收
GC Root包括以下对象:
- 虚拟机栈中本地变量表所引用的对象
- 本地方法栈中本地变量表所引用的对象
- 方法区中静态引用指向的对象(包括常量)
- 仍处于存活状态的线程对象
回收时机:因内存不足导致的内存分配失败、主动调用System.gc()
内存回收算法
标记清除 Mark and Sweep
- 标记:从GC Root开始全部遍历,被引用的标记为灰色(存活),否则标记成黑色(垃圾)
- 清除:遍历完成后,将垃圾全部清除 实现简单,不需要进行对象移动;需要stop the world,会产生内存碎片
复制算法 Copying
- 内存分为两块,一次用一块
- 在用的一块进行标记
- 标记完存活的全部拷贝到另一块
- 切换第二块为使用内存,并清除之前块的所有对象 无内存碎片;内存会缩小为一半,存活率高时复制频繁
标记-压缩 Mark-Compact
- 标记:同标记清除
- 压缩:将存活对象按顺序压缩到内存另一端 无内存碎片,不需要内存减半;仍然有对象移动,效率一般
分代回收机制
JVM一半分为新生代、老年代,HotSpot多定义了一个永久代
新生代默认按8:1:1分为eden、survivor0、survivor1
一般新生代因为存活率低采用复制算法,老年代采用MS或MC
GC称谓
- young GC/minor GC: 回收新生代
- old GC: 回收老年代
- full GC: 收集整个堆
Hotspot回收器
- Serial(JDK1.3之前),单线程,用于新生代,复制算法,一旦启动,stop the world,多用于client
- Serial Old,用于老年代,标记清除算法,其余同上
- ParNew,Serial的多线程版本,同样也要stop all
- CMS(Concurrent Mark Sweep,JDK1.5起),初始标记、并发标记、重新标记、并发清除,其中初标和重标仍然需要stop,其余可与用户程序 并发工作。CMS无法处理浮动垃圾,即并发运行的用户程序在标记过程中产生的垃圾。 CMS需预留一些空间进行整理,如果预留空间不足,则会抛出Concurrent Mode Failure, 此时JVM启动备案,使用Serial Old进行full GC。
- G1(Garbage First),提出基于Region的概念,将新生代、老年代划分为更小的单元Region,优先回收价值最大的Region, 从整体上看是基于标记-压缩,从Region局部看基于复制算法22
三、class类文件
class文件包含两种数据结构:无符号数和表
class文件结构:
魔数 版本号 常量池 访问标志 类/父类/接口 字段描述集合 方法描述集合 属性描述集合
魔数
u4,ca fe ba be
版本号
u4,前两字节表示子版本号,后两字节表示主版本号, 如 00 01 00 34,表示52.1,即jdk 1.8.1
常量池
常量池是表集合,如类的名称、父类的名称、类中的方法名、参数名称、参数类型等 先是一个u2表示常量表个数, 每一项是单独的表,表的第一个u1大小都是tag,用于区分表类型,类型诸如 CONSTANT_utf8_info(字符串表)、CONSTANT_Integer_info(整型常量表)、CONSTANT_class_info(类/接口引用表)等
根据类型,可以查出表的结构,解析后面的数据,每个表可以表示出一个常量,如CONSTANT_utf8_info:
table CONSTANT_utf8_info {
u1 tag;
u2 length;
u1[] bytes;
}
根据上图,tag=1时表示CONSTANT_utf8_info字符串表, 读取后续u2的地址数据,可以获取到字符串的长度length, 然后读取后续的length长度的地址,即该字符串的值。 因为预留了1位表示结束(ends with an instruction that is 1 byte long), 于是String在编译期的最大长度为u2-2=65535-1=65534。 为什么不是65536-1?因为u2的最大值就是65535,最小值是0。
但是运行期String拼接可以比这个更长,但是String的诸多构造函数中,都受限于int的取值(数组的length是非负int), 如果int溢出变成负数就会报错,所以运行期的上线就是int的最大值,也就是2^31-1(4G)
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final int count;
public int length() {...}
}
另外,这里也介绍下class常量表:
table CONSTANT_Class_info {
u1 tag = 7;
u2 name_index;
}
class常量表只有一个u2的name_index,这个值是个整型,指向当前class文件中该类的全限定名, 一定指向一个CONSTANT_utf8_info,比如”java/lang/Object”
访问标志
u2,是不同访问标志的或值(因为互不重叠,也就是累加和),值对应如下: 如0021表示最普通的单public标志(0x0001+0x0020,ACC_SUPER现在都是true)
类索引、父类索引与接口索引计数器
- 类索引u2,指向常量池中该类全限定名的位置
- 父类索引u2,指向常量池中该类父类全限定名的位置(java只支持单继承,所以只有一个父类)
- 接口索引计数器u2,该类实现了多少个接口,之后跟随指定数量个接口索引
字段描述集合
先是u2计数,之后跟随指定数个字段表,字段表结构如下:
CONSTANT_Fieldref_info{
u2 access_flags 字段的访问标志
u2 name_index 字段的名称索引(也就是变量名)
u2 descriptor_index 字段的描述索引(也就是变量的类型)
u2 attributes_count 属性计数器,下面再介绍
attribute_info
}
先是字段计数器,之后按字段访问标志、变量名索引、变量类型索引罗列
方法描述集合
一样先是u2计数,之后跟随指定数个方法表,结构如下:
CONSTANT_Methodref_info{
u2 access_flags; 方法的访问标志
u2 name_index; 指向方法名的索引
u2 descriptor_index; 指向方法类型的索引
u2 attributes_count; 方法属性计数器
attribute_info attributes;
}
其中descriptor_index包含了参数类型和返回类型, 如(Ljava/lang/String)V表示参数为String的无返回值方法
属性描述集合
可以放方法的Code属性,就是字节码了,还有异常表等。
属性名称 使用位置 说明
ConstantValue 字段表 final 关键字定义的常量值
Code 方法表 Java 代码编译的字节码指令
LineNumberTable Code 属性 Java 源码的行号与字节码指令的对应关系
LocalVariableTable Code 属性 方法的局部变量描述
Exceptions 方法表 方法抛出的异常
SourceFile 类文件 源文件名称
InnerClasses 类文件 内部类列表
Synthetic 类、方法表、属性表 标识方法或字段为编译器自动生成的
参https://www.cnblogs.com/binarylei/p/10508441.html
四、字节码技术
Transform API,是gradle中在编译生成class文件之后和生成dex文件之前的hack,
可通过Gradle插件来注册自定义的Transform,注册后的Transform会被Gradle包装成一个Gradle Task, 这个TransForm Task会在java compile Task执行完后运行
Transform提供了关键的transform方法:
public void transform(@NonNull
TransformInvocation transformInvocation)
可以从transformInvocation.inputs中拿到class的文件目录从而遍历class文件,然后采用字节码工具对字节码进行修改
AspectJ、ASM、Javassist框架是常用的字节码工具
AspectJ AOP框架提供五种注解:Before、After、AfterRunning、AfterThrowing、Around, 其中Around可以执行拦截,调用ProceedingJoinPoint.proceed()时才执行原方法,通过代理+runnable匿名内部类实现
Android上可使用aspectjx接入AspectJ,使用@Aspect注解的类,会被自动解析,类中可使用类似如下代码进行插桩
@Before("execution(* android.app.Activity.on**(..))")
public void onActivityMethodBefore(JoinPoint joinPoint) throws Throwable {
String key = joinPoint.getSignature().toString();
Log.d(TAG, "onActivityMethodBefore: " + key);
}
ASM使用Classworking,常用的API为ClassReader、ClassWriter
Javassist使用反射,比ASM慢,但接口比ASM丰富
参:https://blog.yorek.xyz/android/paid/master/bytecode/
五、ClassLoader
JVM的类加载器:为类文件生成一个java.lang.Class实例对象
- 启动类加载器BootstrapClassLoader – 加载%JAVA_HOME%/jre/classes目录下的类,由native代码实现,不是ClassLoader子类 – 如rt.jar(RunTime),java.util.、java.nio.、java.lang.、java.text.、java.math.*都在其中
- 扩展类加载器ExtClassLoader(jdk 1.9后改名为PlatformCLassLoader) – 加载%JAVA_HOME%/jre/lib/ext目录下的class,及java.ext.dirs系统变量指定的路径中类库,父类加载器为null – 如zipfs.jar处理zip先关、localedata.jar日期显示国际化,包括各地区的日期文字
- 系统加载器APPClassLoader(或称应用类加载器) – 默认加载器,加载”java.class.path”目录下的类,即来自java命令中的classpath,以及java.class.path系统属性
加载过程: 1.检测该类是否被加载,已加载直接返回Class对象 2.如果有父类,先交给父类加载,如果父类为null,或者本身就是BootstrapCL,就先由BootstrapCL尝试加载 3.如果2没有加载成功,调用自身findClass方法加载,具体实现是defineClass,关键调用defineClass1是native方法 4.如果加载成功,返回Class对象;如果加载失败,抛出ClassNotFountException
Android中的ClassLoader
- Android将class文件转化为dex文件
- BootClassLoader是包访问权限,外部不能直接访问,是BaseDexClassLoader的实际parent
- BaseDexClassLoader负责加载dex文件,子类为PathClassLoader和DexClassLoader
- PathClassLoader只能加载已安装的apk的dex文件
- DexClassLoader可以加载未安装的dex文件,是热修复和热更新的基础
六、Class加载过程
装载 - 链接(验证、准备、解析) - 初始化
装载 1.生成二进制字节流:通过全限定名(包名+类名)查找.class文件,生成二进制流,查找来源:class文件、jar包、网络字节流 2.解析:把二进制流解析为JVM内部的数据结构,并存储在方法区 3.成功标志:在方法区生成java.lang.Class类型对象
链接 1.验证
- 文件格式验证:检查字节流是否符合class文件格式规范,校验版本当前虚拟机是否支持、校验常量池中tag类型是否合法
- 元数据校验:语义校验,是否有父类(除了Object外都有父类)、父类是否非final、如果非抽象是否已实现所有方法、重载语法是否正确
- 字节码校验:校验数据流、控制流,保证指令不会跳转到方法体以外的指令上、保证类型转化有效
- 符号引用校验:符号引用通过全限定名是否可找到对应的类、符号引用指定的字段/方法是否存在、字段/方法访问性是否合法 2.准备
- 为类的静态变量分配内存,final static赋初始值,普通static赋”0值”,普通static的赋值在初始化阶段 3.解析
- 把常量池中的符号引用转化为直接引用,即JVM会把常量池中的类、接口名、字段名、方法名转换为具体的内存地址
初始化
1.初始化即执行类构造器
- 以上均为主动引用,被动引用如
- final static的常量,如果编译器可以确定值,会被替换成常量,调用时不触发类初始化
- 如在父类定义static value,在其他位置调用子类Child.value访问时,只会触发父类初始化
对象创建顺序
- 父类静态变量、静态块、
- 子类静态变量、静态块
- 父类普通成员变量、普通代码块
- 父类构造函数
- 子类普通成员变量、普通代码块
- 子类构造函数
七、Java内存模型JMM
Java内存模型的目标是定义程序中各个变量的访问规则,即虚拟机将变量存储到内存和从内存中取出变量的底层细节。
这里说的变量包括实例字段、静态字段、构成数组对象的元素,不包括局部变量与方法参数。
java线程 工作内存
java线程 工作内存 save/load操作 主内存
java线程 工作内存
主内存与工作内存间的交互定义了以下8种操作:
- lock
- unlock
- read 将变量值从主内存读到工作内存
- load 将read到的值房主工作内存的变量中
- use 将工作内存中的值传递给执行引擎
- assign
- store 将工作内存中变量的值传值主内存
- write 将store带来的值放入主内存的变量中
- 缓存一致性问题:其他线程访问变量时,可能没有读到最新值
- 运行期指令重排:对线程中无依赖的指令进行重排序,以达到精简指令的效果
Java内存模型是一套共享内存的读写规范,屏蔽底层各种硬件与操作系统的访问差异,使各平台下Java程序对内存的访问效果一致
happens-before先行发生原则:如果操作A happens before操作B,那么A的执行结果对B可见 以下情况自动符合happens-before
- 程序次序原则:同一线程中,如果代码的字节码顺序在前的操作先执行
- 锁定规则:一个锁如果处于被锁定状态,必须unlock后才能进行lock操作
- 变量规则:volatile变量的写,对其他线程的读可见
- 线程启动规则:Thread的start发生在该线程的所有操作之前
- 线程中断规则:interrupt操作对所有catch InterruptException可见
- 线程终结规则:Thread的isAlive()返回值一定准确
- 对象终结规则:一个对象的初始化完成与finalize()前
八、同步
synchronized使用:
- 修饰实例方法:锁当前对象
- 修饰静态方法:锁当前类的Class对象
- 修饰代码块:锁括号中的参数对象
sync是可重入的,比如子类调用父类,访问同一个对象时不会死锁
可以用Object的wait/notify实现不可重入锁
作用于代码块时,编译成字节码对应monitorenter与monitorexit指令
作用于方法时,方法的flags会打上ACC_SYNCHRONIZED标志,在JVM访问时会增加上述两条指令
Reentrantlock:
- 可以指定公平锁(默认不公平,sync也是不公平)
- 支持读写锁
Java对象在内存中布局分为:对象头、实例数据、对齐填充, 对象头中包含了对象的hashCode、分代年龄、锁标志位、是否偏向锁等
Monitor是对象有中的一个对象,是重量级锁同步工具,记录了如下信息:
- _owner:指向持有ObjectMonitor对象的线程(当前获得锁的线程)
- WaitSet:处于wait状态的等待线程队列(获得锁的线程主动调wait()时进入)
- _EntryList:处于等待锁block状态的线程队列
- _count:记录线程读取锁的次数
锁的状态总共有四种:无锁、偏向锁、轻量级锁和重量级锁
- 锁自旋:线程先等待一段时间,不立即挂起,等待时执行一段无意义的循环,需占用CPU
- 偏向锁:当线程进入同步块时,锁的对象头记录下当前的ThreadId,下次线程获取锁时, 判断ThreadId是否一致,一致则不需再次获取锁直接执行,否则膨胀成重量级锁
参:https://www.cnblogs.com/paddix/p/5405678.html
AQS(Abstract Queued Synchronizer)抽象队列同步器
ReentrantLock内部通过一个Sync对象实现同步,Sync是一个抽象内部静态类, extends AbstractQueuedSynchronizer,有NonfairSync和FairSync两种实现
九、线程池
可执行者
public interface Executor {
void execute(Runnable command);
}
ExecutorService定义了提交机制与线程池关闭方法
public interface ExecutorService extends Executor {
}
ThreadPoolExecutor是ExecutorService的默认实现
ScheduledThreadPoolExecutor提供重复执行机制
// Executors工厂类
// 单线程
newSingleThreadExecutor()
// 可缓存池,回收复用,无可用时新建线程
newCachedThreadPool()
// 固定大小
newFixedThreadPool(int nThreads)
// 固定大小,重复执行池
newScheduledThreadPool(int corePoolSize)
// 执行完后,依次递增period延时再执行
ScheduledExecutorService.scheduleAtFixedRate(run, initialDelay, period, unit)
// 执行完后,使用固定delay延时执行
ScheduledExecutorService.scheduleWithFixedDelay(run, initialDelay, delay, unit)
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize:核心线程数,最多可同时执行的线程数
maximumPoolSize:最大线程数,所有运行和等待线程的最大数,如果没有设置RejectedExecutionHandler,超过这个数时会抛出RejectedExecutionException
keepAliveTime:达到核心线程数数,切小于等于最大线程数时提交的Runnable,处于等待状态,等待的时长
unit:keepAliveTime的单位
workQueue:Runnable等待队列
threadFactory:线程工场,默认DefaultThreadFactory,默认指定与当前线程通过group,指定一个线程名,设置为非守护线程,优先级为NORM_PRIORITY
handler:超出maximumPoolSize时提交Runnable的处理器:void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
RejectedExecutionException有4个子类(策略):
AbortPolicy,默认策略,抛出RejectedExecutionException
DiscardPolicy,空实现,直接放弃,啥也不干
DiscardOldestPolicy,去掉队列里最老的任务,把新任务插入
CallerRunsPolicy,直接在当前线程调run()方法(退回给提交线程执行)
ThreadPoolExecutor
public void execute(Runnable command) {...}
public <T> Future<T> submit(Callable<T> task) {...}
submit有返回值Future,Future的get()方法可以阻塞等待执行结果
第二部分、Android
十、DVM Feature
dalvik虚拟机是基于寄存器的虚拟机,JVM是基于栈式虚拟机。寄存器式指令少、单条指令长,解释器执行更快速,栈式便于移植
DVM将内存分为Active Heap和Zygote Heap两块,因为所有进程都是从Zygote复制出来的,写时拷贝,避免频繁复制
manifest文件中可以指定android:largeHeap=”true”申请使用大堆,大堆上限大小可通过ActivityManager.getLargeMemoryClass()读出
DVM采用JIT来将字节码转换成机器码,运行时解释;ART采用了AOT预编译技术,安装的时候就完成了字节码转换为机器码,占用空间更大
ART GC策略:
- ART 有多个不同的 GC 方案,涉及运行不同的垃圾回收器。从 Android 8 (Oreo) 开始,默认方案是并发复制 (CC)。另一个 GC 方案是并发标记清除 (CMS)。
- 在Android 10 及更高版本中,CC 回收器在分代模式下运行。
- CMS分为粘性(sticky)CMS和部分(partial)CMS,粘性CMS只回收上次GC后新分配的对象,CMS是不移动对象的GC,有内存碎片问题,通常在app退到后台后,Android会对内存进行压缩
参:https://source.android.google.cn/devices/tech/dalvik/gc-debug?hl=zh-cn
十、Activity启动
Activity.startActivity->Activity.startActivityForResult Instrumentation.execStartActivity(Service和Application的startActivity先判断下有无FLAG_ACTIVITY_NEW_TASK,然后也调用该方法) ATMS.startActivity,ActivityStack.resumeTopActivityInnerLocked,->ActivityTaskSupervisor.startSpecificActivity 判断Activity所在进程是否已经启动 – 如果已经启动,realStartActivityLocked – 如果还未启动,ATMS.startProcessAsyn->AMS-startProcess->Zygote_Process.start(…)->LocalSocket.connect(ZygoteSocketAddress) 反射调用ActivityThread.main,new出ActivityThread实例 ActivityThread继承自ClientTransactionHandler,生命周期方法改为Transaction调用
并调用attach方法
attach方法中,取出AMS实例,调用AMS.attachApplication(applicationThread, startSeq)又将自身需要被调用的管理方法提供给AMS管理 如scheduleTransaction、scheduleBindService等,ApplicationThread是ActivityThread的内部类,是一个Binder对象 AMS通过ApplicationThread调用scheduleTransaction调度Activity的生命周期方法,scheduleTransaction内部向mH sendMessage发送消息 mH是ActivityThread的内部类H extends Handler实例,其中定义了BIND_SERVICE、EXECUTE_TRANSACTION等消息 EXECUTE_TRANSACTION会调到mTransactionExecutor.execute(transaction) TransactionExecutor的execute会调到executeLifecycleState,其中封装ActivityLifecycleItem最终调到performLifecycleSequence 其中会调到mTransactionHandler.handleLaunchActivity、handleStartActivity等方法 而这个mTransactionHandler就是通过构造函数传入的ActivityThread自身,所以最终还是调回了ActivityThread.handleLaunchActivity等方法 handleLaunchActivity调用 activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent)创建实例 然后检查并makeApplication,调用activity.attach(appContext…)
attach里会调PhoneWindow的创建 ## 十一、Activity、Window、View关系
ActivityA - Instrument - AMS - ApplicationThread - ActivityB
Activity.startActivity
Activity.startActivityForResult
Instrumentation.execStartActivity
ActivityTaskManager.getService().startActivity
ActivityTaskManagerService.startActivity
ActivityTaskManagerService.startActivityAsUser
ActivityStarter.executeRequest(Request...)(以前叫startActivityMayWait)
ActivityStarter.startActivityUnchecked(ActivityRecord...)
ActivityStack.startActivityInner(ActivityRecord...) - startActivityLocked
ActivityRecord.showStartingWindow - addStartingWindow
ActivityManagerService.startProcessLocked
ActivityThread.main - attach - attachApplicationLocked - bindApplication
ActivityStack.realStartActivityLocked
ActivityThread.scheduleLaunchActivity - handleLaunchActivity - handleResumeActivity - Idler
ActivityStack.completeResumeLocked - activityIdleInternal - startPausingLocked
十一、Activity、Window、View关系
ActivityThread 中的 performLaunchActivity 方法,通过反射创建 Activity 对象,并执行其 attach 方法 Window 就是在这个方法中被创建 mWindow = new PhoneWindow(…) 接下来调用mWindow.setWindowManager,持有一个WindowManagerImpl的引用 Activity 将 setContentView 的操作交给了 PhoneWindow 在 PhoneWindow 中默认有一个 DecorView
在 ActivityThread 的 handleResumeActivity 中,会调用 WindowManager 的 addView 方法将 DecorView 添加到 WMS(WindowManagerService) WindowManagerImpl.addView(decor, layoutParams) WindowManagerImpl所有调用会调到WindowManagerGlobal,WindowManagerGlobal.addView里会new ViewRootImpl() 然后调用ViewRootImpl.setView,里面做了两件主要的事 1、里调用WindowSession.addToDisplay,WindowSession是Binder对象,会调到WMS的addView方法 2、new InputEventReceiver,设置输入管道,触摸事件先到达ViewPostImeInputState,其onProcess会调到 DecorView.dispatchPointerEvent, 这个方法会调到Window.getCallBack的dispatchTouchEvent方法,而这个CallBack在Activity的attach中,被mWindow.setCallback(this)设置层Activity自身 所以触摸事件的起点是Activity的dispatchTouchEvent,如果没复写,会调getWindow().superDispatchTouchEvent 此处Window是PhoneWindow,调到Decor的superDispatchTouchEvent,Decor调的就是ViewGroup的super.dispatchTouchEvent()进入事件机制
触摸事件由InputManagerService监听 点击屏幕->InputManagerService的Read线程捕获事件,预处理后发送给Dispatcher线程 ->Dispatcher根据触摸事件的位置及窗口的属性来确定将事件发送到哪个窗口,找到目标窗口 通过Socket将事件发送到目标窗口->APP端被唤醒->找到目标窗口处理事件
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
// mWindow是在attach中创建的
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, ...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// ...
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE), ...);
// ...
}
// Window是抽象的,PhoneWindow是Window的唯一实现类
// Window的setWindowManager中,使用系统的WindowManager创建了一新的WindowManagerImpl
public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
// ...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
// Window中setContentView是abstract的,PhoneWindow的实现如下:
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// ..
mLayoutInflater.inflate(layoutResID, mContentParent);
// ...
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
// ...
}
// ...
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
// ...
}
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes());
}
public class DecorView extends FrameLayout implements WindowCallbacks ...
protected ViewGroup generateLayout(DecorView decor) {
// ...
ViewGroup contentParent = ( `ViewGroup)findViewById(ID_ANDROID_CONTENT);
// ...
return contentParent;
}
总之,PhoneWindow中有一个DecorView mDecor,以及一个ViewGroup mContentParent,mContentParent = mDecor.fiid(android.R.id.content)
自定义的layout,由mLayoutInflater.inflate(layoutResID, mContentParent)添加至mContentParent下
真正把View当做窗口添加到WMS是有WindowManager的实现WindowManagerImpl完成的
WindowManagerImpl会调到WindowManagerGlobal的addView,
再通过ViewRootImpl的setView方法,先由requestLayout完成measure-layout-draw操作,
再调用mWindowSession.addToDisplay调到WMS中,之后的操作就交由WMS完成
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
}
public final class WindowManagerGlobal {
private static IWindowSession sWindowSession = getWindowManagerService().openSession(...);
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
}
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException
throw e;
}
}
public class WindowManagerService extends IWindowManager.Stub {
private static WindowManagerService sInstance;
private WindowManagerService(...) {}
public IWindowSession openSession(...) {
// 第一个参数把WMS传到Session的成员变量mService里
Session session = new Session(this, callback, client, inputContext);
return session;
}
}
public final class ViewRootImpl implements ViewParent... {
mWindowSession = WindowManagerGlobal.getWindowSession();
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
requestLayout();
mWindowSession.addToDisplay(...);
// 以下构造输入责任链,viewPostImeStage是责任链最后一个节点
InputStage viewPostImeStage = new ViewPostImeInputStage(...);
}
}
}
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final WindowManagerService mService;
public int addToDisplay(IWindow window, ...) {
mService.addWindow(this, window, ...);
}
}
当触屏事件发生后,经过驱动层通过Socket跨进程通知Android Framework层WMS,最终消息以msg传到ViewRootImpl
ViewRootImpl在handleMessage中调用doProcessInputEvents,由deliverInputEvent传给setView中构造的input责任链
ViewPostImeInputStage的onProcess中,执行了handled = mView.dispatchPointerEvent(event),将事件分发给了mView
mView一般就是传过来的DecorView,其复写方法如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
而其中Window.Callback,是Activity在attach方法中调用了mWindow.setCallBack(this),设置为了自身
所以点击事件会优先传递到Activity上,其对应方法如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
可见,如果不复写Activity的该方法,会默认先走PhoneWindow的superDispatchTouchEvent,
实际调到DecorView的同名方法,DecorView调用parent(也就是ViewGroup)的dispatchTouchEvent,至此回到ViewGroup的事件传递过程
十二、touch事件分发
上节可知,点击事件先传递到Activity的dispatchTouchEvent,如果未处理,走入DecorView的传递
ViewGroup分发:
首先,持有ViewGroup实例的对象可以直接禁用其处理点击事件
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
ViewGroup的传递如下:
public void dispatchTouchEvent() {
// 0.收到down事件时清空TouchTarget链表,mFirstTouchTarget置空
if (actionMasked == MotionEvent.ACTION_DOWN) {
resetTouchState();
}
// 1.检查当前ViewGroup是否要拦截事件
// 如果不是down事件,且还没有子View申明过要处理touch事件,
// 先判断是不是被禁用了,禁用则不拦截,否则再调用自身onInterceptTouchEvent看是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
// 2.将down事件分发给子View
// 按层级从上往下遍历子View,如果发现某个View要处理点击事件,
// 将其设为TouchTarget链表的头结点(mFirstTouchTarget),并结束遍历
if (!canceled && !intercepted) {
// 这里注意,是down事件,或者多点触控中的point_down,或者鼠标move才走这个分支,如果是普通move/up等,直接去3
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE)) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
// 需同时满足可接受事件(可见或动画中)、触摸点在View范围中(INVISIBLE时如在动画中,可以点击)
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// 该dispatch方法会调用child.dispatchTouchEvent(event)继续传递
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
...
// 3.根据mFirstTarget,再次分发事件
// 如果mFirstTouchTarget为空,说明子View没有拦截事件,这里继续调dispatch,因为传入null会调父类的dispatch
// 如果mFirstTouchTarget非空,如果已经在2中处理过了,跳过,如果还未处理,传递给链表中所有节点处理
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
// ...
}
}
}
...
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, View child, ...) {
// ...
if (child == null) {
// 传入的child为空,调父类(就是View)的方法,实际会调到自身的onTouchEvent中
handled = super.dispatchTouchEvent(event);
} else {
// 传入的child非空,传递给子类处理
handled = child.dispatchTouchEvent(event);
}
// ...
}
View事件处理
上面我们看到,在子View都不处理点击时,最终会调用到super.dispatchTouchEvent
一般自定义的View如果需要处理touch,不推荐直接复写该方法,而是推荐处理onTouchEvent
View中方法如下:
public boolean dispatchTouchEvent(MotionEvent event) {
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
可以看到,会优先调用mOnTouchListener的onTouch方法,如果onTouch返回false,会继续调自身的onTouchEvent
View默认onTouchEvent如下,其中处理了我们常见的点击、长按等操作:
public boolean onTouchEvent(MotionEvent event) {
// 这个代理可以用来实现扩大点击区域
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable) {
switch (action) {
case MotionEvent.ACTION_UP:
// ...
performClickInternal();
// ...
case MotionEvent.ACTION_DOWN:
// ...
checkForLongClick();
// ...
}
}
}
至此,整个点击事件传递就陈述完了,简单图示如下:
十三、View的绘制
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
参数中的int measureSpec高2位表示specMode,低30位表示具体数值
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
specMode值:
MeasureSpec.UNSPECIFIED:未指定,如ListView的item
MeasureSpec.EXACTLY:固定大小,match_parent或固定值
MeasureSpec.AT_MOST:wrap_content
setMeasuredDimension传入的值,在判断是视图否有阴影后,就会算出measureWidth和measureHeight
onMeasure是计算View的宽高,由measure方法调用,measure方法
可用View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);计算measureSpec值
十四、Handler
Handler按官方说法主要有两个作用:1、发送一个延时消息,在将来指定时刻执行这个任务;2、在其他线程排序执行任务 在没有引用三方库的情况下,可以简单使用子线程完成网络请求,通过向主线程的Handler发送消息完成回到主线程的回调 但这里要注意如果还有未处理的消息会导致Handler强引用的外部对象泄露, 如果在Activity中申明,建议将Handler设置为静态成员,通过弱引用去访问Activity方法,并在onDestroy中removeAllCallBacks
一个线程可以有多个Handler,但一个线程只能有一个Looper(因为ThreadLocal就是这么用的,一个线程一个实例) 调用Lopper.prepare()时,会先判断sThreadLocal.get()是否为空,为空则抛出异常,否则调用sThreadLocal.set(new Looper(quitAllowed)); 每个Looper有一个MessageQueue成员,从loop()开始,死循环调用msg = queue.next()和msg.target.dispatchMessage(msg) msg.target是Handler对象,dispatchMessage中Handler先判断下msg.callback是否非空,callback是runnable对象,如果非空则执行,结束 否则判断Handler自己的mCallback是否非空, mCallback非空则执行mCallback.handleMessage(msg),如果返回true则直接结束,否则继续执行Handler自身的handleMessage方法 mCallback为空则直接执行Handler自身的handleMessage方法,handleMessage方法可以不复写(是非abstract的空实现)
Handler.sendMessage()最终调到enqueueMessage(queue, msg, uptimeMillis), uptimeMillis是uptimeMillis()+delayMillis,会赋值给msg.when,是app上层不能直接干预的属性 enqueueMessage中,如果msg.when小于消息头(成员变量)时间戳,或者消息头为null,把新来的msg插到消息头前面,否则按时间遍历到对应位置插入msg (MessageQueue的mPtr变量保存NativeMessageQueue对象地址)
MessageQueue的next()方法中调用了nativePollOnce(ptr, nextPollTimeoutMillis), 该方法会依次调到NativeMessageQueue::pollOnce、Looper::pollOnce()、Looper::pollInner(),空闲时停在epoll_wait(),等待事件发生或者超时
select 阻塞,直到描述符fd准备就绪(有数据可读、可写、异常)或者超时;在返回后,通过遍历文件描述符来获取已经就绪的socket 文件描述符个数受限(1024),遍历性能低 poll 没有最大数量限制,也需要遍历文件描述符来获取已经就绪的socket epoll 没有描述符数量限制,用中断通信,每个fd定义回调函数,只有就绪的fd才会执行回调函数,不需要遍历
SyncBarrier 每次通过invalidate来刷新UI时,最终都会调用到ViewRootImpl中的scheduleTraversals方法,会向主线程的Looper中post一个SyncBarrier, 其目的是为了在刷新UI时,主线程的同步消息都被跳过,此时渲染UI的异步消息就可以得到优先处理
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
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));
}
// 这个方法是给ActivityThread的main方法调的,设置sMainLooper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static void loop() {
final Looper me = myLooper();
for (;;) {
Message msg = queue.next(); // might block
// ...
// 这里的target就是handler
msg.target.dispatchMessage(msg);
// ...
}
}
}
public class Handler {
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// ...
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; // 这里把target设置为了handler自身
return queue.enqueueMessage(msg, uptimeMillis);
}
// handler.post会将runnable封装成一个msg添加到msg queue中,由此转换到handler所在线程执行.run()
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
// mCallback是构造函数传进来的
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
// 空方法,由子类实现
public void handleMessage(Message msg) {
}
}
public final class Message implements Parcelable {
long when; // currentTimeMillis() + delay
Handler target; // 调用sendMessage的handler
Message next; // 单向链表
// 设置为异步消息,普通消息默认是同步消息
// 在MessageQueue调用postSyncBarrier后,只处理异步消息
// 再次调用removeSyncBarrier后恢复处理普通消息,因此异步消息用于处理一些高优消息
// 比如,ViewRootImpl中scheduleTraversals会调用postSyncBarrier,
// 之后由Vsync发送的绘制消息会被优先执行,之后unscheduleTraversals会调removeSyncBarrier解除
public void setAsynchronous(boolean async) {...}
}
public final class MessageQueue {
boolean enqueueMessage(Message msg, long when) {
// 主要是按时间when把msg插到Message链表中
// 而取消息操作next()是Looper.loop()中调用的
}
Message next() {
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
if (now < msg.when) {
// 下一条消息还没到点,带上超时值重新pollOnce
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
return msg;
}
// 因为线程闲下来了没消息处理,就看下idle里有没有事务要处理,一次最多4个,这个可以用于启动优化
for (int i = 0; i < pendingIdleHandlerCount; i++) {
IdleHandler idler = mPendingIdleHandlers[i];
boolean keep = idler.queueIdle();
if (!keep) {
mIdleHandlers.remove(idler);
}
}
}
}
public void addIdleHandler(@NonNull IdleHandler handler) {
mIdleHandlers.add(handler);
}
}
十五、OkHttp
RequestBody formBody = new FormBody.Builder().add("key", "value").build();
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(interceptor).proxy(proxy).cache(new Cache(...)).build();
Request request = new Request.Builder().url(url)
.post(formBody).header("User-Agent", "Chrome").build();
client.newCall(request).enqueue(new CallBack(){
public void onResponse(Call call, Response, response);
public void onFailed(Call call, IOException);
});
public Call newCall(Request request) {
return RealCall.newRealCall(...);
}
public void enqueue(Callback) {
client.dispatcher().enqueue(new AsyncCall(callback));
}
public void enqueue() {
executorService().execute(call);
}
public class AsyncCall extents NamedRunnable() {
public final void run() {... execute(); ...}
protected void execute() {
try {
Response response = getResponseWithInterceptorChain();
callBack.onResponse();
} catch (IOException e) {
callBack.onFailure();
}
}
}
public Response getResponseWithInterceptorChain() {
List<Interceptor> interceptors = new ArrayList();
interceptors.addAll(client.interceptors); // client外传入的
interceptors.add(retryAndFollowUpInterceptor); //
interceptors.add(new BridgeInterceptor()); // 设置Head默认值,如Cookie
interceptors.add(new CacheInterceptor()); //
interceptors.add(new ConnectInterceptor()); // 建立TCP
interceptors.add(new CallServerInterceptor()); // 从Server拿到远端回包
Interceptor.Chain chain = new RealInterceptorChain(interceptors);
return chain.proceed(originalRequest);
}
十六、RecyclerView四级缓存
public final class Recycler {
// 屏幕内缓存,数据刷新时,不需要重新加载子ItemView,直接复用;
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
// 刚刚移出屏幕的缓存,最大容量为2,通过position来保存
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
// 自定义缓存
private ViewCacheExtension mViewCacheExtension;
// 缓存池,保存第二级缓存中保存不了的ItemView。通过itemType来保存,每种itemType可以保存5个ItemView
// 可以多个RecyclerView共享
RecycledViewPool mRecyclerPool;
static final int DEFAULT_CACHE_SIZE = 2;
...
}
自定义缓存RecyclerView对应方法如下:
public void setViewCacheExtension(@Nullable ViewCacheExtension extension) {
mRecycler.setViewCacheExtension(extension);
}
public abstract static class ViewCacheExtension {
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}
LayoutManager的实现中,layoutChunk调layoutState.next(Recycler),此时recycler会调用getViewForPosition
进一步调到tryGetViewHolderForPositionByDeadline,
// 0) If there is a changed scrap, try to find from there
// 1) Find by position from scrap/hidden list/cache
// 2) Find from scrap/cache via stable ids, if exists
会尝试从传入的extension中取出holder复用
十七、apk安装过程与包体积优化
PackageManagerService(PMS)
打包: Resources资源文件:在res目录下的xml、drawable图片等都会编译成二进制文件,生成resources.arsc和R.java R.java中ID是4字节的无符号整数,(16进制表示)。最高1字节表示PackageID,次高的1个字节表示TypeID,最低的2字节表示EntryID (assets和raw目录下的资源不会做任何处理直接放入apk)
resources.arsc相当于一个资源索引表,也可以理解为一个 map 映射表。其中 map 的 key 就是 R.java 中的资源 ID,而 value 就是其对应的资源所在路径
源代码首先会通过javac编译为.class字节码文件(aidl会先编译成java再处理) 连同依赖的三方库中的.class文件一同被dx工具优化为.dex文件
APK Builder将经过编译之后的resource和.dex文件、AndroidManifest.xml清单文件和动态库.so文件一起打包到apk中
jarsigner签名,生成META_INF文件夹
一个完整的 apk 解压缩之后的内容如下: AndroidManifest.xml /assets classes.dex classes2.dex /META_INF /res resources.arsc
安装: 安装页面为PackageInstallerActivity.java 拷贝安装包; 装载代码。
创建了类型为 INIT_COPY 的 Message。 创建 InstallParams,并传入安装包的相关数据,设置为Message的obj参数 发送Message,由 PMS 的内部类 PackageHandler 接收并处理 connectToService连接DefaultContainerService连接成功后向PMS发送MCS_BOUND PMS收到后startCopy 创建存储安装包的目标路径,实际上是 /data/app/ 应用包名目录; 调用服务的 copyPackage 方法将安装包 apk 拷贝到目标路径中;(由DefaultContainerService完成) 将 apk 中的动态库 .so 文件也拷贝到目标路径中 最终安装包在 data/app 目录下以 base.apk 的方式保存
当安装包拷贝操作结束之后,继续调用 handleReturnCode 然后调用installPackageTraceLI 调用 PackageParser 的 parsePackage 方法解析 apk 文件,主要是解析 AndroidManifest.xml 文件,将结果记录在 PackageParser.Package 中。我们在清单文件中声明的 Activity、Service 等组件就是在这一步中被记录到系统 Framework 中,后续才可以通过 startActivity 或者 startService 启动相应的活动或者服务。 对 apk 中的签名信息进行验证操作。collectCertificates 做签名验证,collectManifestDigest 主要是做包的项目清单摘要的收集,主要适合用来比较两个包的是否一样。如果我们设备上已经安装了一个 debug 版本的 apk,再次使用一个 release 版本的 apk 进行覆盖安装时,会在这一步验证失败,最终导致安装失败。 执行 dex 优化,实际为 dex2oat 操作,用来将 apk 中的 dex 文件转换为 oat 文件。 调用 installNewPackageLI 方法执行新 apk 的安装操作: 创建/data/data/应用包名 如果安装成功,更新系统设置中的应用信息,比如应用的权限信息 如果安装失败,则将安装包以及各种缓存文件删除
包体积优化: Android Studio 中点击 Analyze -> Inspect Code,除了代码提示,还会提示那些xml资源声明但从未使用 shrinkResources minifyEnabled=true:在编译阶段删除未被使用到资源文件
十八、android系统启动
通电后,从BootRom启动BootLoader,加载到RAM内存 BootLoader启动偶,加载第一个内核进程idle(pid=0),idle负责启动内核进程init(pid=1)和kthreadd(pid=2) -init进程是Linux中第一个用户空间的进程,是所有用户进程的祖先进程(通过kernel_execve执行用户空间编译连接进入用户态) -kthreadd是内核空间除idle外其他内核进程的祖先进程 init进程会陆续启动系统进程,其中就包括Zygote Zygote的main方法中,初始化AndroidRunTime, 然后调用到AndroidRunTime.start(“…ZygoteInit”), start方法中
- startVM启动Android虚拟机
- 调用startReg注册jni方法
- 然后通过jni反射调用env->CallStaticVoidMethod(ZygoteInit.class, “main”)到ZygoteInit.main方法,在此处从native进入java层 ZygoteInit.main中
- 调用preload加载公共资源,常用java类如preloadSharedLibraries(如系统目录”android”,这样之后fork的进程就不用重新加载了)
- new ZygoteServer()创建Socket(用于fork)
- forkSystemServer,启动system_server(系统服务大合集)
- zygoteServer.runSelectLoop()进入死循环,等待AMS消息创建进程
system_server int pid = fork(),之后可以用if (pid == 0)区分原进程与子进程,== 0为子进程,!= 0还在父进程,值为子进程的pid ZygoteInit.main中if (pid == 0)进入system_server子进程代码块,调用handleSystemServerProcess 然后进入ZygoteInit.zygoteInit->RuntimeInit.applicationInit->反射调用SystemServer.class的main方法 main方法中
- 通过ActivityThread调用ContextImpl.createSystemUiContext
- new SystemServiceManager(mSystemContext)
- startBootstrapServices(t) – 最先启动的是ActivityTaskManagerService – AMS在之后初始化并调用onStart方法:ActivityManagerService.Lifecycle.startService(mSystemServiceManager, atm); – ServiceManager.addService(ACTIVITY_SERVICE, this, …)将AMS添加到ServiceManager中 – AMS构造函数中调用了ActivityTaskManagerService.initialize方法,其中实例化了用于管理Activity栈的ActivityTaskSupervisor
- startCoreServices(t); – 如BatteryService、BugreportManagerService
- startOtherServices(t); – 会启动WMS、PowerMS等服务,在最后调用mActivityManagerService.systemReady->TaskManager.resumeTopActivities – ->LocalService.resumeTopActivities->ActivityStack.resumeTopActivityInnerLocked() – 如果nextFocusedStack为null->RootWindowContainer.resumeHomeActivity – ->构造HomeIntent(ACTION_MAIN = “android.intent.action.MAIN”, CATEGORY_HOME = “android.intent.category.HOME”) – startHomeOnDisplay,启动Launcher
ActivityManager通过getService访问ActivityManagerService,原由ActivityManagerNative桥接ActivityManagerProxy访问Service方式已废弃
app跳转部分
ActivityA –> ActivityManagerService(简称 AMS) ActivityManagerService –> ApplicationThread ApplicationThread –> ActivityB
十九、内存泄漏与LeakCanary原理
在Activity需要被回收时,使用WeakReference指向该Activity,并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue。 然后给WeakReference做一个标记Key并在一个强引用Set中添加相应的Key记录 最后主动触发GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set 中的记录也删除 Activity需要被回收是指onActivityDestroyed、onFragmentDestroyed、onFragmentViewDestroyed RefWatcher的watch方法发起,先通过UUID生成随机key并添加到keys集合中 然后调用ensureGoneAsync进行检测,ensureGone中先将Queue中数据从Set中移除,然后判断集合中是否还有这个Activity的key 如果有,未避免是系统还未回收,调用gcTrigger.runGc()执行gc,再重复一次Set检测 如果还有,就说明发生了泄露,调用HeapAnalyzerService.runAnalysis生成前台服务收集堆信息 收集的实现是android.os.Debug.dumpHprofData,获得hprof文件,之后用haha(2.0前)/shark(2.0后)分析hprof文件 在分析过程中找出对象类型的实例,并通过上面的Key判断出是需要找到的实例, 最后用广度优先搜索,从GC Root开始遍历找到该对象(或多个对象)最短路径 ensureGoneAsync是通过idelHandler添加执行的
5.0起,System.gc()需要到下次System.runFinalization()时统一执行 Runtime.getRuntime().gc()会马上执行
Native内存泄漏检测:malloc hooks两种方式:
- 通过so的PLT(Procedure Linkage Table)进行hook so文件进行动态链接的时候,依赖于PLT表和GOT(Global Offset Table)表进行外部函数调用 PLT相当于一段跳板代码,修改这段代码的目标函数符号地址,可以实现malloc函数的hook
- 通过dlsym进行hook dlsym是动态加载so文件的关键,通过dlsym将参数改为RTLD_NEXT可以将特定符号重定向到后面加载的共享库中的符号上,从而实现malloc的hook
LeakTracer是基于dlsym进行的hook,hook的函数有:new/delete/malloc/realloc/calloc/free
二十、异常捕获原理
java - setDefaultUncaughtExceptionHandler设置全局的Handler, 注意先getDefaultUncaughtExceptionHandler把原存下执行完收集逻辑后增加调用原Handler逻辑
native - 基于信号量 CoffeeCatch通过sigaction捕获到信号量SIGSEGV, SIGBUS等,注意最好处理完收集逻辑后还原原来的sigaction 要使用alarm保证信号处理程序不会陷入死锁或者死循环的状态。 考虑到信号处理程序中的诸多限制,一般会clone一个新的进程,在其中完成解析堆栈等任务 或者建立子线程并一直等待,等到捕捉到crash信号时,唤醒这条线程dump出crash堆栈,并把crash堆栈回调给java 使用libunwind收集堆栈 crash线程就是捕获到信号的线程,在信号处理函数中获得当前线程的名字,然后把crash线程的名字传给java层,在java里dump出这个线程的堆栈,就是crash所对应的java层堆栈 https://blog.csdn.net/u012216131/article/details/116643868
ANR的来源是哪里,最终都会走到ProcessRecord中的appNotResponding 几种比较极端的情况,不会产生一个ANR,这些情况包括:进程正在处于正在关闭的状态,正在crash的状态,被kill的状态,或者相同进程已经处在ANR的流程中 前台ANR会弹无响应的Dialog,后台ANR会直接杀死进程 当应用发生ANR之后,系统会收集许多进程,来dump堆栈,从而生成ANR Trace文件,收集的第一个,也是一定会被收集到的进程,就是发生ANR的进程, 接着系统开始向这些应用进程发送SIGQUIT信号,应用进程收到SIGQUIT后开始dump堆栈。
二十一、UI卡顿优化、surface绘制原理
SysTrace
ViewRootImpl通过openSession()获取WindowManagerService的WindowSession,完成ViewRootImpl到WinodwManagerService的单向通信。 而反向通信,是通过一个W类(extends IWindow. Stub)的实例完成的,在ViewRootImpl#setView方法中,完成绑定
二十二、LiveData
abstract class LiveData
- 只能在主线程
- owner.getLifecycle().getCurrentState() == DESTROYED时直接return
- 将owner, observer保存到新建的LifecycleBoundObserver实例
- 从成员变量mObservers检查该observer是否已经绑定过其他owner,如果绑定过抛出异常;否则调用owner.getLifecycle().addObserver(wrapper)进行绑定
- addObserver中,将Observer添加到mObserverMap,Observer.mState赋初始值,
- 这会触发dispatchEvent->onStateChanged->dispatchingValue->onChanged给新观察者赋初始值(粘性事件)
setValue-dispatchingValue-considerNotify
- 非shouldBeActive直接return(mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED))
- observer.mLastVersion < mVersion时,调用observer.mObserver.onChanged((T) mData)(因为只能在主线程setValue,没有线程安全问题)
在LifecycleBoundObserver的onStateChanged中会调activeStateChanged(shouldBeActive()),如果是active的会触发dispatchingValue设置值 FragmentActivity父类ComponentActivity的onCreate中调用ReportFragment.injectIfNeededIn(this)新建了一个ReportFragment 这个ReportFragment是空的,没有复写onCreateView方法(父类会直接return null) 在这个ReportFragment中registerActivityLifecycleCallbacks 在Callbacks的onActivityCreated调用dispatch(Lifecycle.Event.ON_CREATE)、onStart调用dispatch(Lifecycle.Event.ON_START)… dispatch触发ComponentActivity.getLifecycleRegistry().handleLifecycleEvent->Sync->dispatchEvent->dispatchingValue->onStateChanged触发LiveData回调
xlog、mmap、nativePollOnce
日志设计理念: 安全性:不能把日志明文打到日志文件里 流畅性:不能影响程序的性能。最基本的保证是使用了日志不会导致程序卡顿。 完整性:不能因为程序被系统杀掉,或者发生了 crash,crash 捕捉模块没有捕捉到导致部分时间点没有日志, 要保证程序整个生命周期内都有日志。 容错性:不能因为部分数据损坏就影响了整个日志文件,应该最小化数据损坏对日志文件的影响
理解Context
上下文Context是抽象类,直接子类是ContextImpl和ContextWrapper Context中定义了与四大组件交互的关键方法,如abstract startActivity,这些方法由ContextWrapper提供默认实现 子类根据具体的场景进行复写,如Activity复写了startActivity Context中有一些方法不是abstract的,如startActivityForResult有具体实现,是直接跑出异常,让子类去复写,只有Activity复写了该方法 ContextWrapper通过装饰模式持有一个ContextImpl实例,通过构造方法或者attachBaseContext传入 ContextWrapper的直接子类是ContextThemeWrapper、Application和Service,ContextThemeWrapper的子类是Activity
Activity的ContextImpl在ActivityThread的performLaunchActivity中createBaseContextForActivity调用 ContextImpl.createActivityContext(… r(ActivityClientRecord).packageInfo, r.activityInfo, r.token…静态方法构造, 并在performLaunchActivity中newActivity并调用activity.attach(appContext…完成绑定
Service的ContextImpl在ActivityThread的handleCreateService中由 ContextImpl.createAppContext(this, packageInfo)静态方法创建, 并与instantiateService构造的Service实例进行service.attach(context… service.onCreate()调用进行绑定
Application的ContextImpl在ActivityThread的 handleReceiver、handleCreateService、handleBindApplication、attach、performLaunchActivity 都会检查并调用LoadedApk类实例的packageInfo的makeApplication, 其中调用ContextImpl.createAppContext(mActivityThread静态方法 并在mActivityThread.mInstrumentation.newApplication中instantiateApplication并调用app.attach(context)绑定
CustomViewModel vm = new ViewModelProvider(MainActivity.this).get(CustomViewModel.class); ViewModel可实现Fragment间共享 数据一直保存在内存中,Activity重建时不丢失(ViewModelStore)
Binder
Binder是Android系统提供的一种IPC机制 性能:数据拷贝次数:Binder数据拷贝只需要一次(Client->内核,Server与内核建立binder_mmap,无需拷贝) 而管道、消息队列、Socket都需要2次(A->内核,内核->B,因为内核地址是共享的) 但共享内存方式一次内存拷贝都不需要; 从性能角度看,Binder性能仅次于共享内存,共享内存同步较复杂,需要通过信号量等方式自己维护 稳定性:基于C/S架构,Server端与Client端相对独立,各自维护自己业务,稳定性较好; 安全:Android为每个安装好的应用程序分配了自己的UID,传统Linux IPC没有完善的权限机制, Binder提供了更完善的权限管理(如clearCallingIdentity、restoreCallingIdentity调用) 可开发性:Binder符合面向对象的思想,将进程间通信转化为通过对某个Binder对象的引用调用该对象的方法
Android OS中的Zygote进程的IPC采用的是Socket机制 (fork不允许存在多线程,Binder通讯是多线程,所以不能用Binder;fork只会复制当前线程到子进程,可能会出现锁丢失而死锁) Android中的Kill Process采用的signal(信号)机制等等。 而Binder更多则用在system_server进程与上层App层的IPC交互
oneway调用是异步调用,但无论是否异步,Client端都需要等Server端的BR_TRANSACTION_COMPLETE消息才能继续执行(Server端确认收到调用), 如果非异步需要等待BR_REPLY(Server端逻辑执行完成)
在fork生成新进程后,主线层在ZygoteInitNative会调用startThreadPool生成Binder线程池
http://gityuan.com/2016/09/04/binder-start-service/ https://zhuanlan.zhihu.com/p/35519585 https://blog.csdn.net/freekiteyu/article/details/70082302
RePlugin
插件调用宿主:因为ClassLoader作为成员变量传入插件ClassLoader,插件可以用RePlugin.getHostClassLoader().loadClass(“完整类名”)访问到宿主中的类 宿主调插件:Activity传Intent、BroadcastReceiver、Service、ContentProvider、插件反射调用宿主EventBus
插件宿主RePluginClassLoader继承自PathClassLoader 在初始化时从application.getBaseContext()取出”mPackageInfo”的”mClassLoader”字段设为RePluginClassLoader实例(唯一hook) RePluginClassLoader重载了loadClass方法,会尝试从PluginDexClassLoader中加载(PMF.loadClass),未找到再走双亲委派路径
PluginDexClassLoader是插件的ClassLoader,继承自DexClassLoader,是在loadDex方法实例化的,每一个插件都会有一个PluginDexClassLoader PluginDexClassLoader当loadClass方法找不到类时,从宿主ClassLoader中反射获取loadClass方法
RePlugin初始化:RePluginApplication.attachBaseContext():创建初始进程等
Activity启动流程 因为Activity类被TransForm修改为了PluginActivity,startActivity方法转移到PluginActivity内部 最终调到PluginLibProxy的同名方法,通过PluginContainer查询对应坑位的Activity类对应的坑位(类名或IntentFilter), 通过ActivityState记录关联关系(这里如果插件没加载过会先执行插件加载) 然后按分配坑位,之后走AMS的startActivity,最后走到ActivityThread的performLaunchActivity 这里会通过类加载器加载Activity类并实例化,因为ClassLoader已经被替换了,loadClass时会先判断是否为坑位类, 如果是则通过PluginContainer的ActivityState找到类,找不到再通过parent PathClassLoader找 之后就是正常的生命周期方法
插件加载原理 释放so 加载dex:Loader.loadDex函数会获取Dex中的组件的信息,包括Manifest中的组件属性, 比如进程属性,TaskAffinity属性,注册静态广播等,以及初始化PluginDexClassLoader 加载插件的Entry类:用于初始化插件的运行环境,将对sPluginContext、sHostContext、sHostClassLoader等字段赋值 之后插件可以通过这些类反射调用宿主方法,如插件调用Replugin.startActivity会反射调回宿主的Replugin.startActivity
资源加载 各插件独立,资源不会冲突,不需修改aapt,优先找插件,找不到再找宿主
宿主replugin-host-gradle:生成带 RePlugin 插件坑位的 AndroidManifest.xml(允许自定义数量) replugin-plugin-gradle:动态将插件中的Activity的继承相关代码修改为PluginActivity父类
AndroidX问题:没有替换x中的Activity类,在LoaderActivityInjector的loaderActivityRules声明中加上 ‘androidx.appcompat.app.AppCompatActivity’: ‘com.qihoo360.replugin.loader.a.PluginAppCompatActivity’…即可
https://ljd1996.github.io/2020/03/19/RePlugin%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/
路由组件
注解处理器APT @Route(path = …)指定Activity/Service路径 @Interceptor()路由拦截器 @Autowired参数自动填充
弹幕
SurfaceView:双缓冲、在WMS中有对应的WindowState,在SurfaceFlinger中有单独Layer 不在View hierachy中,不能进行平移,缩放等变换,在一个独立的线程中进行绘制,不会影响主线程 TextureView:View hierachy中的一个普通View, 支持和其它普通View一样进行移动,旋转,缩放,动画等变化,必须在硬件加速的窗口中使用,5.0以后有单独的渲染线程,有1-3帧延迟
tep1 创建弹幕对象 BaseDanmaku step2 从Bitmap缓存中(DrawingCache)取出一个Bitmap(宽高大于当前弹幕)用于存放此条弹幕,没有找到合适的缓存时,创建新的Bitmap(一条弹幕对应一个Bitmap) step3 调用Bitmap.eraseColor(0)清除内容,利用软绘制的Canvas在Bitmap上绘制弹幕内容 step4 Bitmap准备好后通知View刷新(invalidate刷新) step5 等待View 的 onDraw 回调,用回调的Canvas(利用DisplayListCanvas进行硬件绘制),调用drawBitmap绘制弹幕的Bitmap step6 返回step4 直到弹幕使用完毕,绘制流程结束
跨端方案
React使用jsx来写布局,然后会转换成虚拟dom树 遍历虚拟dom 创建Native组件 UIManager的createView,映射到java层的UIManagerModule,最终调到ViewManager.createView生成Native View 根据父子关系来把子组件添加到父组件里面
渲染流程:处理数据、绘制Dom数、解析Js
Json解析->数据源 Dom解析->Dom树 Css解析->Css样式规则 JSCore-Js上下文
weex这4步都在js中,优化:前三步由native完成,Android使用v8引擎
VideoNative尽可能的将这些解析过程放在Native侧去做,包括数据的解析和绑定、Dom 的展开、Css 的解析和匹配, Js 在 VideoNative 中只负责事件监听和业务逻辑,而这一部分是不可避免必须依赖 Js,这就使得 VideoNative 对 Js 的依赖减少到最小 利用 Native 的多线程能力进行 Dom 树构建、表达式计算、Css 匹配规则的计算、Yoga 布局 Dom 树和样式的解析都是耗时操作,我们可以将 Dom 树和样式的解析的中间结果序列化到本地,下次打开页面再反序列化到内存,直接省略树的展开、计算属性等步骤 Yoga 是由 C 实现的布局引擎
JSAPI
在jsapi对象初始化时,反射Method[],遍历方法,如果有对应注解(如@JsApiMethod),并生成对应函数签名 这里注意下签名不包括jsapi函数自身返回值,js的执行结果通过JsCallback执行js回调 映射关系String-S、数字类型-N、JSONObject-O、JsCallback-F,并将签名缓存下来
JS通过invoke(老版本是jspromt)回调,回调关键参数:”method”、”types”、”args”,还原签名,匹配Method调用invoke得到返回值
如果有JsCallback-F,需在js方法中执行callback的js调用,回调格式如javascript:try {INJECT_NAME.callback(CALLBACK_INDEX_INT, RETURN_VALUE);}catch(e){}
其他
Bitmap.getByteCount()可返回实际占用字节数 缩放比例 scale = 当前设备屏幕密度density / 图片所在 drawable 目录对应屏幕密度density 屏幕像素密度densityDpi = 屏幕密度density * 160 density = PPI / 160 Bitmap 实际大小 = 宽 * scale * 高 * scale * Config 对应存储像素数 mdpi : 该像素密度对应 density = 1 , densityDpi = 160 hdpi : 该像素密度对应 density = 1.5 , densityDpi = 240 xhdpi : 该像素密度对应 density = 2 , densityDpi = 320 xxhdpi : 该像素密度对应 density = 3 , densityDpi = 480 通过asset目录加载,不会缩放
requestLayout invalidate方法,如果View的位置并没有发生改变,则View不会触发重新布局的操作