Android 技能图谱
2021-05-23 12:44:09 3 举报
AI智能生成
知识图谱
作者其他创作
大纲/内容
Android 技能
Git
跨平台
Flutter
Widget
StatelessWidget
生命周期
build
往往作为叶子结点,状态不会改变的
StatefulWidget
initState
didChangeDependencies
创建时候在initState 之后被调用
在依赖的InheritedWidget发生变化的时候会被调用
didUpdateWidget
调用setState
parent widget state changed
生成Widget树
deactivate
Widget从Widget树上移除
reassemble
执行HotLoad
dispose
页面关闭
自定义控件
组合控件
自定义Paint
自定义Layout
自定义Element
自定义RenderObject
Element
Render
动画
路由
组件间通信
Provider
通道
Channel
BasicMessageChannel
只能传递半结构化数据
MethodChannel
传递结构化数据
MethodCodec
JSONMethodCodec
StandardMethodCodec
OptionalMethodChannel
调用MethodChannel时,如果Platform没有实现,会抛出异常;而OptionalMethodChannel不会抛出异常,而是返回null;
EventChannel
传递数据流
Codec
MessageCodec
BinaryCodec
StringCodec
JSONMessageCodec
StandardMessageCodec
主题
国际化
pubspec.ymal
Analyze
buildRunner
网络请求
异常捕获
性能分析
首帧渲染时长
Native有onFirstFrame回调
动态化方案
插桩方案
内存泄漏检测方案
引擎复用方案
FlutterEngineGroup
单引擎方案
博客
腾讯TRouter https://blog.51cto.com/u_15060467/2678776
工程化 https://segmentfault.com/a/1190000037649954
字节方案 https://mp.weixin.qq.com/s/-vyU1JQzdGLUmLGHRImIvg
性能体验优化
增加SplashView
使用EngineCache
代码覆盖率测试
第三方框架
MTFlutter
Flutter Boost
Flap
Fair
Platform
Android
Engine
SplashView
FlutterActivity
FlutterFragment
FlutterView
Platform Task Runner
主要执行Flutter Engine代码和Native Plugin的任务
UI Task Runner
负责为Flutter Engine执行Root Isolate的代码;Root Isolate向Engine提交一次渲染帧时,Engine会等待下次vsync,当下次vsync到来时,由Root Isolate对Widgets进行布局操作,并生成页面的显示信息的描述,并将信息交给Engine去处理。
对widgets进行layout并生成layer tree
GPU Task Runner
渲染layerTree
IO Task Runner
读取文件、解析图片等信息
渲染流程
GPU 发出Vsync同步信号
UI Runner 进行build、layout、paint,生成layerTree交给 GPU Runner
GPU Runner 对layerTree进行compositor,交给Skia引擎渲染
RN
Picasso
工程相关
Jenkins
算法
树
前序遍历-深度优先
子主题
中序遍历
后续遍历
层序遍历-广度优先
思路:借助队列先进先出,遍历节点时,将子节点放入队列
二叉树
二叉树搜索树
验证二叉搜索树
最近公共节点
链表
链表反转
快慢指针
栈
计算1+2+3*4*5
排序算法
冒泡排序
插入排序
选择排序
归并排序
堆排序
加密算法
摘要算法
MD5
消息摘要算法(Message Digest Algorithm 5)
SHA
安全散列算法(Secure Hash Algorithm)
SHA-1
相对于MD5
安全性高
速度慢
160位散列值
SHA-256
256位散列值
SHA-384
384位散列值
SHA-512
512位散列值
对称加密
DES
56位,安全性低,性能中等
AES
取代DES,128、192、256位安全性高,性能高,资源消耗低
3DES
用两个密钥对明文进行三次加密
K1对明文加密K2对结果加密K1对结果加密
非对称加密
RSA
公钥
私钥
ECC
相比RSA
更小的密钥
安全等级更高
运算时间长,CPU消耗严重
相对于对称加密
安全性更高
运算时间长
Java基础
JVM
JVM Runtime Memory
Thread Share
Method AreaJDK1.8 移除了方法区MetaSpace取代,没有设定内存大小,可以无限增长,直至耗尽JVM内存
主要存放类信息、常量、静态变量、编译后的代码
Constant Pool Table
符号引用、字面量、String(JDK 1.7已经移到Heap中)、直接引用
Heap
引用类型的对象
分代
新生代 1/3
Eden 8/10
From Survivor 1/10
To Survivor 1/10
老年代 2/3
程序计数器(Program Counter Register)
Thread Private
Java Stack
Stack Frame
局部变量表
基本数据类型
对象引用(地址或句柄)
操作数栈
方法返回值
动态链接
Native Method Stack
GC
GC 标记算法
引用计数法(无法处理循环引用)
可达性算法
第一次标记(标记没有与GC Roots相连)
第二次标记(执行对象finalize,finalize和GC Roots没有重新建立连接,被标记)
确定对象死亡
GC Roots 常见类型
虚拟机栈中引用的对象(栈桢中的本地变量表)
方法区中静态属性引用的对象
方法区中常量池中引用类型
本地方法栈饮引用的对象
ClassLoader
GC 回收算法
标记清除法清除经过可达性算法标记后的对象回收后内存不连续,内存空间碎片化
标记整理法在标记清除法之上增加内存移动整理,回收后内存连续在内存回收的时候需要更大内存空间和时间
复制算法(新生代Copy对象)
泛型
泛型擦除
运行时获取泛型类型
ParameterizedType parameterizedType = (ParameterizedType) type; Type[] genericTypes = parameterizedType.getActualTypeArguments();
反射
getMethod、getField
获取到包括父类所有的public方法或字段
getDeclaredMethod、getDeclaredField
可以获取到某个Class的所有方法或字段
无法获取到父类的方法或字段
多线程
Thread
CPU执行的最小单位
线程状态
NEW
尚未启动的线程处于此状态
未调用start时
RUNNABLE
准备就绪
调用start之后,但未获取得CPU执行权
其他线程join结束
sleep结束
调用yield
RUNNING
线程获取到CPU执行权
Thread.State类中并没有这个状态
BLOCKED
获取锁时,进入synchronized关键字修饰的方法或代码块
IO阻塞
WAITING
调用object.wait()之后等待另一个线程调用object.notify()或object.notifyAll()
调用join
调用LockSupport.park()
TIME_WAITING
超时等待
调用object.wait(time)
调用join(time)
调用LockSupport.parkNanos(time)
调用LockSupport.parkUntil()
TERMINATED
终止状态,线程执行完毕
run函数执行完毕
main函数执行完毕
再次调用start(),抛出IllegalThreadStateException
常用函数
join
等待线程终止
join(time),等待time时间后线程终止
join()、join(0),表示无限等待
在ThreadA中调用ThreadB.join()
ThreadB处于WAITING状态
ThreadA会等待ThreadB执行完并TERMINATED
yield
交出CPU执行权
不会释放锁
只会让相同优先级的Thread获取CPU执行权
Thread进入RUNABLE就绪状态
sleep
Thread进入休眠状态
sleep(0)可以实现CPU重新分配执行权
setPriority
优先级高的获取CPU执行权概率更大
优先级具有继承性,默认继承parentThread优先级
setDaemon
设置是否为守护线程
当JVM所有线程都是守护线程,JVM退出
必须在Thread启动前调用
垃圾收集器线程就是守护线程
setDefaultUncaughtExceptionHandler
捕获Thread中Throw的异常
interrupt
为线程增加interrupt标志位
可以结束处于BLOCK状态的线程
无法结束处于RUNNING状态的线程
抛出InterruptedException
isInterrupted
单纯的判断线程是否被标志为interrupt,不会清除标志位
interrupted
判断线程是否标志为interrupt,并清除interrupt标志位
stop
暴力结束线程,终止run方法调用
抛出ThreadDeath错误,如果捕获必须重新throw,确保线程死亡
完全释放对象锁,可能会导致对象锁状态不一致
应该判断线程是否处于中断或自行增加flag,如需结束,应关闭IO,清除未完成的临时文件;
如何中断任务
设置flag,周期性判断flag
ThreadPoolExecutor
参数配置
corePoolSize
保留在线程池中的线程数
1. 正在执行的任务数低于corePoolSize,创建新的线程2. 正在执行的任务数高于corePoolSize,放入workQueue
maximumPoolSize
允许同时运行的最大线程数
当workQueue已满生效
运行的线程数小于最大线程数,可以创建新的线程
运行的线程数大于最大线程数,执行拒绝策略
keepAliveTime
超出核心线程数之外的空闲线程的存活时间
TimeUnit
keepAliveTime 时间单位
BlockingQueue
任务数高于corePoolSize时生效
阻塞队列已满
判断maxPoolSize
阻塞队列未满
任务入列
常用队列
有届队列(maxPoolSize和拒绝策略有效)
ArrayBlockingQueue
基于数组的阻塞队列
SynchronousQueue
不存储元素的同步阻塞队列
每个任务执行都需要等待上一个任务执行完毕
无界队列(maxPoolSize、拒绝策略失效)
LinkedBlockingQueue
基于单链表实现的阻塞队列
队尾插入,队头取出
可以指定容量,默认Integer.MAX_VALUE
不指定容量时,maxPoolSize和拒绝策略失效
LinkedBlockingDeque
基于双向链表实现的阻塞队列
队尾和队头都可以插入取出元素
LinkedTransferQueue
FIFO阻塞队列
可以通过transfer方法让生产者线程等待消费线程取走元素
PriorityBlockingQueue
按照优先级进行内部元素排序
元素必须实现Comparable接口
ThreadFactory
生产Thread,可以指定Thread Name、Group等
RejectedExecutionHandler
拒绝策略,线程池中线程数大于maxPoolSize时生效
策略类型
AbortPolicy
抛出RejectedExecutionException异常
实现:throw RejectedExecutionException
默认模式并推荐使用:由开发者自行处理异常
CallerRunsPolicy
在调用线程中执行任务
实现:runnable.run()
缺点:在Android中可能出现主线程阻塞问题
DiscardPolicy
直接丢弃任务
空实现
缺点:没有任何输出日志或者提示
DiscardOldestPolicy
丢弃线程池中的最旧的一个等待执行的任务,并执行新提交任务
实现:getQueue().poll(); excute(runnable);
缺点:队列出现问题时,线程池会出现Bug
常用方法
核心参数修改
prestartAllCoreThreads 初始化所有核心线程
prestartCoreThread 初始化一个新的核心线程
setKeepAliveTime 修改保活时间
setMaximumPoolSize 修改最大线程数
setCorePoolSize 修改核心线程数
allowCoreThreadTimeOut 允许核心线程销毁
setThreadFactory 修改线程工厂
setRejectedExecutionHandler 修改拒绝策略
钩子函数
beforeExecute、afterExecute 任务执行前后的钩子函数
onShutdown
提交任务
submit
返回Future,可以获取任务结果
execute
单纯提交任务
终止线程池
shutdown
阻止新任务的提交
已添加的任务将执行完
shutdownNow
线程池状态置为STOP,并不一定立即停止
对未执行的任务添加interrupt状态
awaitTermination
等待一定时间后,返回线程池是否终止
可以配合Shutdown使用
Executes
newSingleThreadExecutor
只有一个线程运行
无界队列
特点
可以保证按任务提交顺序执行
newFixedThreadPool
固定核心线程数
LinkedBlockingQueue无界队列
并发线程数可控
适合作为CPU密集型线程池
线程个数指定为cpu核数+1
newCachedThreadPool
SynchronousQueue 无界队列
保活时间60s
适合作为IO密集型线程池
核心线程数指定为cpu核数*2
newScheduledThreadPool
周期性执行任务
newSingleThreadScheduledExecutor
只有一个核心线程
常见问题
核心线程是如何保活的?
1. getTask方法从BlockingQueue中获取任务2. 如果允许核心线程超时销毁 或者 当前线程数大于核心线程数,调用Queue.poll(keepAliveTime)3. 否则调用Queue.take(),阻塞直至有队列添加任务
BlockQueue的阻塞实现是通过Lock,没有任务时Condition.await,有任务时Condition.signal
Concurrent
锁
Synchronize
非公平锁、可重入锁、悲关锁、独占锁、Java内置的锁
可以保证代码块和变量的原子性和可见性
双重检查锁单例为什么需要加volatile
保证原子性和可见性是指获取到锁,如果没有获取到锁就判断==null,是无法保证原子性和可见性的
字节码指令实现
monitor enter
monitor exit
锁状态
无锁状态
锁状态为01
偏向锁状态为0
轻量级锁00
1. 进入同步代码块2. 建立LockRecord空间3. 拷贝对象头MarkWord4. 拷贝成功,CAS更新MarkWord指针为LockRecord、CAS更新LockRecord owner指针为MarkWord5. 拷贝失败,检查栈桢是否指向MarkWord,如果是说明已经获取锁,直接执行代码块;否则存在锁竞争,置为重量级锁;5. 更新MarkWord锁标志位为轻量级锁00
重量级锁10
拷贝对象头失败
同时MarkWord没有指向当前栈桢,说明存在锁竞争,轻量级锁膨胀为重量级锁
偏向锁
偏向状态为1,锁状态为01
偏向锁获取
1. 当前偏向锁状态为1,锁状态为012. MarkWord中ThreadId为当前线程
偏向锁释放
1. 其他线程主动竞争锁2. 并且到达全局安全点(没有字节码在执行)3. 撤销偏向锁,置为无锁状态或轻量级
偏向锁的作用
通过判断MarkWord中ThreadId是否为当前线程,来减少轻量级锁的步骤
使用方式
同步代码块
普通同步代码块
锁对象为指定的对象
静态同步代码块
锁对象为Class
同步方法
普通同步方法
静态同步方法
Object
wait、wait(time)
释放锁,线程挂起,直至调用notify或notifyAll唤醒
释放CPU执行权
必须在synchronize代码块中执行,否则会抛出IllegalMonitorStateException
notify
唤醒某个线程
不能保证哪个线程被唤醒,取决于线程调度器
notifyAll
唤醒所有线程
LockSupport
基本原理
各个函数实际调用的是unsafe类的方法
unsafe类的实现是在C++
通过控制当前线程是否拥有许可证来阻塞或者恢复线程执行
许可证最多有一个
先unpark一次,再park,实际线程不会阻塞,许可证+1之后-1,相当于没变
park()
无限期阻塞当前线程
park(Object blocker)
指定阻塞者,可以用于调试
parkNanos(long nanos)
阻塞一定时间
指定阻塞者、阻塞一定时间
parkUntil(long deadline)
阻塞至某个时间
指定阻塞者、阻塞至某个时间
unpark(Thread thread)
唤醒线程
优势
无需获取锁、可以随时阻塞线程
可以唤醒指定线程
缺点
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
可能出现高优先级的Thread等待低优先级的Thread释放锁
独享锁的缺点:一个线程获取到锁,其他线程进入阻塞或等待
Lock
lock
获取锁,获取不到线程等待,直到获取到锁
出现异常不会释放锁,需要try-catch释放锁
tryLock
尝试获取锁,获取到返回true,不会出现等待
lockInterruptibly
处于等待锁的线程能响应中断
unlock
释放锁,通常需要放在finally中
newCondition
await 等同Object.wait
signal 等同于Object.notify
signalAll 等同于Object.notifyAll
AQS(AbstractQueuedSynchronizer)
好文章
基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架
利用了一个int来表示状态,acquire和release通过CAS控制状态
基本使用
实现 tryAcquire、tryRelease
尝试获取或释放独占锁
ReentrantLock的同步器、Mutex独占锁
实现 tryAcquireShared、tryReleaseShared
尝试获取或释放共享锁
CountDownLatch、CyclicBarrier、Semaphore的同步器
Fair和NonFair锁
优秀流程图
公平锁:如果等待队列不为空直接进入队列等待,FIFO
非公平锁:先尝试一次获取锁,失败后进入队列等待唤醒
例如:ReentrantLock、Semaphore
非公平锁吞吐量更高
ReentrantLock
可重入锁,Lock的一个常用实现
对比Synchronize
Synchronize使用简单,但不灵活
ReentrantLock可以实现公平锁
ReentrantLock手动加锁释放锁,在高并发情况下,效率更高
ReentrantLock加锁和释放锁次数必须一致,否则其他线程永远无法获取到锁
ReentrantLock出现异常时不会主动释放锁,unlock需要放在finnaly中
tryLock可以处理获取不到锁的情况
lockInterruptibly可以响应中断,可处理死锁问题
ReentrantReadWriteLock
支持公平和非公平的获取锁的方式
允许读并发
支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁
还允许从写入锁降级为读取锁,实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的
读取锁和写入锁都支持锁获取期间的中断
Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException
CAS
乐观锁,无阻塞多线程争抢资源的模型
使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值
用途
Lock的底层实现工具
配合volatile实现Atomic原子操作
优势:并发量不高的情况下,性能较Synchronize高
存在aba问题
JDK1.5通过增加版本号解决
可以使用AtomicStampedReference、AtomicMarkedReference
如果自旋循环长时间不成功,开销大
无法保证可见性,通常需要配合volatile关键字使用
只能保证单个变量的原子性,无法保证一段代码或者多个变量的原子性
Volatile
内存屏障
种类
Load Barrier
在指令前插入LoadBarrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据
Store Barrier
在指令后插入StoreBarrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见
作用
阻止屏障两侧的指令重排序
强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效
有序性
禁止指令重排序优化,多线程下指令重排可能会改变输出结果
可见性
变量修改后,从高速缓存立即同步到主内存
使用变量时,每次从住内存读取最新的值到高速缓存
使用条件
对变量的写操作不依赖于当前值
不能用作线程安全计数器,如i++
该变量没有包含在具有其他变量的不变式中
不能用在i < k,两个变量值中
使用场景
状态标志
线程结束任务的flag标志位
一次性安全发布
双重检查锁单例
开销较低的“读-写锁”策略
private volatile valueget 读无锁synchronize set 写加锁
volatile bean
一个Bean类所有的字段都是volatile
ThreadLocal
保存变量副本,做到线程安全
原理
1. 调用ThreadLocal.get2. 根据Thread.currentThread()获取到threadLocalMap3. 如果为空,初始化threadLocalMap,如果不为空返回获取到的值4. 调用ThreadLocal.put,如果threadLocalMap未初始化,初始化map,然后put
问题
可能导致的内存泄漏
弱引用ThreadLcoal可以回收,ThreadLocaMap和Thread生命周期一样长,出现key为null,value无法访问,导致value内存泄漏,Thread销毁才会回收
正确使用:变量使用完毕调用remove
InheritableThreadLocal
继承父线程的值
CountDownLatch
指定一个或者多个线程等待其他线程执行完毕
1. 调用await,当前线程放入AQS阻塞队列中2. count为原子操作,每次countDown,count-13. count==0,通过tryAcquireShared逐个唤醒阻塞队列中阻塞的线程
1. 创建CountDownLatch,指定count2. 在需要等待结果的线程调用await3. 在其他线程中调用countDown
并行计算,等待所有计算结束
保证线程执行顺序
CyclicBarrier
多个线程相互等待执行完成,计数器可以重置
基于ReentrantLock实现,计数器count为0的时候,唤醒所有阻塞线程,并重新选择count个线程执行任务
1. 创建CyclicBarrier,指定并发数量和回调2. 在执行任务线程中调用await
用于一组线程互相等待对方执行一些操作后再一起继续执行
100个任务,每次执行完5个任务再执行接下来的5个任务
Semaphore
限制执行的线程数量
基于AQS同步器实现,可以指定Fair、NonFair
1. 创建Semaphore,指定并发数量2. 调用tryAcquire,如果获取许可成功,执行完任务调用release释放许可
限定文件同时读的线程数
Reference
优秀博客
StrongReference
即使OOM也不会回收
WeakReference
所引用的强引用对象不可达时,GC就会回收
SoftReference
OOM之前,GC才会回收
Phantom Reference
幽灵引用
关联的对象被垃圾收集器回收时候得到一个系统通知\t
ReferenceQueue
引用回收后,会将WeakReference、SoftReference放入队列中
Clone
深拷贝
递归copy引用数据类型和基本数据类型
更改Copy后的数据不会影响原始数据
浅拷贝
基本数据类型、String、数组类型复制一份,引用类型只copy了引用
更改Copy后的数据,引用类型数据会跟随改变
集合
Set
HashSet
SortedSet
TreeSet
SynchronizedSortedSet
SynchronizedNavigableSet
Map
HashMap
基本数据结构
数组+链表+红黑树
版本差异
JDK1.7
JDK1.8
链表长度>=8转成红黑树,时间复杂度O(logn);红黑树大小<=6转成链表,时间复杂度O(n);防止频繁增删导致不断转换数据结构;
扩容过程
Hash计算
并发问题
无同步锁
fail-fast机制,多线程操作会抛出ConcurrentModificationException;每次修改modifyCount都会+1,迭代器初始化时赋值给exceptedModifyCount,在迭代过程中并与其比较,不相同则抛出异常;
并发resize时,1.7的头插法,链表可能形成环,还有数据丢失,当get时会触发死循环
SortedMap
TreeMap
SynchronizedSortedMap
SynchronizedNavigableMap
LinkedHashMap
HashTable
Synchronize方法加锁
ArraryMap
数据结构
int[] mHashs 存储key.hash
Object[] mBaseCache,容量由4扩容到8时,存储mHashs、mArray;容量由8变回4时,直接重用
Object[] mTwiceBaseCache,容量由8扩容到12时,存储mHashs、mArray容量由12变回8时,直接重用
扩容
每次扩容当前容量1/2(mSize+(mSize>>1)
对mHashs使用二分查找算法获取key.hash的index,key在mArray中index*2
clear 清空数组,空间也回收mHashes = EmptyArray.INT; mArray = EmptyArray.OBJECT;
erase 清空数组,空间保留
List
Vector
Stack
ArrayList
LinkedList
SparseArray
key只能是int类型
两个数组,一个存key,一个存value
每次扩容当前容量1/2
使用二分查找算法
Queue
PriorityQueue
ConcurrentHashMap
JDK1.6\\1.7
分割为Segment数组,每个Segment为大小1/16的HashMap
Segment继承自ReentrantLock,实现每段线程安全
Hash:使用hashCode() + 4次位运算 + 5次异或运算(9次扰动)
数据结构采用了和HashMap一样的结构,数组+链表+红黑树数据结构
Node作为锁对象,在进行具体操作时使用CAS+Synchronize来更新值
Node\b中key、value使用volatile修饰,读的时候配合CAS使用Unsafe.getObjectVolatile
Hash算法优化,通过hashCode()的高16位异或低16位实现的
通过增加tail指针,既避免了死循环问题(让数据直接插入到队尾),又避免了尾部遍历,但仍是非线程安全的,多线程时可能会造成数据丢失问题
CopyOnWriteArrayList、CopyOnWriteArraySet
LinkedBlockingDeque、LinkedBlockingQeque
ConcurrentLinkedDeque、ConcurrentLinkedQueue
ConcurrentSkipListSet、ConcurrentSkipListMap
Collections工具类
binarySearch()
checkedXXX()
copyXXX()
Bootstrap ClassLoader 引导类加载器
Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库;
加载JavaHome lib下的rt.jar、resources.jar、charsets.jar和class等
BootStrap ClassLoader不遵循委托机制,没有子加载器
可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录;
System.getProperty(\"sun.boot.class.path\")查看classpath
Extension ClassLoader 扩展类加载器
父加载器:BootClassLoader
负责加载Java的扩展类库,Java 虚拟机的实现会提供一个扩展库目录,该类加载器在此目录里面查找并加载 Java 类
默认加载JAVA_HOME/jre/lib/ext/目下的所有jar;
Application ClassLoader 系统类加载器
父加载器:ExtClassLoader
负责加载应用程序classpath目录下的所有jar和class文件;
一般来说,Java 应用的类都是由它来完成加载的
通过ClassLoader.getSystemClassLoader()来获取它;
通过System.getProperty(“java.class.path”)来查看 classpath
DexClassLoader
能够加载未安装的jar/apk/dex
PathClassLoader
只能加载系统中已经安装过的apk 主要的区别在于PathClassLoader的optimizedDirectory是null;能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的
双亲委派
加载流程
查找Class,未找到,交由parentClassLoader查找
parent为null,交由BootstrapClassLoader
parent和bootstrap都未找到,调用findClass,若仍未加载,throw ClassNotFoundExecption
避免同一个类被多次加载;
每个加载器只能加载自己范围内的类;
Throwable
Error
指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止;
OutOfMemoryError
StackOverflowError
ThreadDeath
Exception
希望程序捕获处理的异常
RunTimeException
非受检查异常,只有在运行时才可能抛出异常
NullPointerException
ArrayIdexOutOfBoundsException
其他Exception
受检查异常,在编译前必须由开发者主动捕获
IOException
序列化
Serializable
将某个对象保存到磁盘上或者通过网络传输
serialVersionUID
用于序列化或反序列化时比较是否为相同版本class
如果未显式指定,JVM会自动生成一个
如果只是修改了方法、静态变量、transient修饰的瞬态变量反序列化不受影响,则无需修改版本号
@transient
标识字段不进行序列化
自定义序列化
private void writeObject(java.io.ObjectOutputStream out) throws IOException序列化对象
private void readObjectNoData() throws ObjectStreamException;反序列化没有数据
private Object writeReplace() throws ObjectStreamException 在序列化时,会先调用此方法,再调用writeObject方法;此方法可将任意对象代替目标序列化对象
private Object readResolve() throws ObjectStreamException反序列化时替换反序列化出的对象,反序列化出来的对象被立即丢弃;readResolve常用来反序列单例类,保证单例类的唯一性。
序列化对象
new ObjectOutputStream(new FileOutputStream(\"person.txt\"))).writeObject(new Person());
(Person)(new ObjectInputStream(new FileInputStream(\"person.txt\"))).readObject())
注意事项
所有成员必须也是Serializable,否则序列化失败
readResolve与writeReplace的访问修饰符可以是private、protected、public,子类应该也复写;所以通常只建议final修饰的类复写这2个方法
Externalizable
强制自定义序列化
Parcelable
Android独有
只在内存中序列化、反序列化
需要递归序列化、反序列化引用类型成员变量
类
内部类
内部类持有外部类引用
可以访问private的字段和方法
1. 编译器自动为内部类添加一个外部类的类型相同的成员变量;2 编译器自动为内部类的构造方法添加一个外部类的类型参数;3. 在调用内部类的构造函数初始化内部类对象时, 会默认传入外部类的引用;
静态内部类
动态代理
注解
String
StringBuffer
StringBuilder
Handler
Handler机制
Looper
prepare
向ThreadLocal中添加loop;
初始化MessageQueue
loop
for循环调用MessageQueue#next取出消息
调用msg.target也就是Handler#dispatchMessage
setObserver
添加消息处理观察者,包括start、dispatch、exception
setSlowLogThresholdMs
设置消息处理过慢的阈值
MessageQueue
enqueueMessage
消息入列,根据upTimeMillis排序
uptimeMillis==0或者小于队头msg,直接插入到队头;根据当前blocked标志位设置为needwake
根据时间排序,插入到合适位置;这种情况下队列是不为空的,通常不需要唤醒;但当mBlocked && p.target == null && msg.isAsynchronous(),需要唤醒
调用nativeWake唤醒
next
Message
int arg1
int arg2
Handler target
Runable callback
int what
Object obj
Mesenger replyTo
Message next
Callback
优先调用Message#callback;再调用Handler#callback;Handler#handlerMessage;
IdleHandler
执行时机
队列空闲、下一个消息还未到执行时间
存储结构
mIdleHandlers:ArrayList,存放执行完不销毁的IdleHandler
mPendingIdleHandlers:IdleHandler[],存放执行完销毁的IdleHandler
实际上,在运行时mIdleHandlers.toArray(mPendingIdleHandlers);
返回值:true,执行完不销毁;false,执行完移除掉
ActivityThread#GcIdler:垃圾回收
ActivityThread#PurgeIdler:资源回收
Instrumentation#ActivityGoing
Instrumentation#Idler
HandlerThread
继承Thread
在run方法中初始化Looper
在quit中Looper.quit
四大组件
Activity
启动模式
taskAffinity
如果activityA配置了taskAffinity,那么activityA将在taskAffinity的任务栈启动否则将在启动activityA所在的taskAffinity任务栈启动
配置了taskAffinity的rootActivity决定了任务栈的名称
taskAffinity默认为applicationid
一个activity的taskAffinity设置一个空字符串,表明这个activity不属于任何task
standard
每次创建新的Activity
singleTop
栈顶复用,如果栈顶存在该Activity,不会启动新的Activity,但会调用onNewIntent
如果不存在,启动新的Activity
singleTask
如果taskAffinity任务栈中存在该Activity,清空该Activity上面的Activity,并回调onNewIntent
如果不存在,在taskAffinity任务栈启动新的Activity
启动时,增加FLAG_ACTIVITY_NEW_TASK标签
总结:设置了singleTask的Activity启动时不一定会启动新的任务栈,取决于taskAffinity是否已经存在
singleInstance
activityA为singleInstance,如果系统中不存在activityA,activityA将在一个单独的任务栈启动,如果taskAffinity任务栈存在,将新建task Id;否则调用其onNewIntent
activityA启动其他的Activity,其他的Activity将在另一个的任务栈,如果taskAffinity任务栈存在,将会在taskAffinity任务栈中启动;
被启动或者启动其他Activity,都会增加FLAG_ACTIVITY_NEW_TASK标签
总结:设置了singleInstance的activity,如果系统中没有启动过,启动时一定会启动一个新的任务栈,如果taskAffinity存在,将新建taskId;activityB被singleInstance的activity启动时不一定会创建新的任务栈,取决于taskAffinity任务栈是否存在
startActivity(Api 28)
Activity#startActivityForResult
Instrumentation#execStartActivity
ActivityManagerService#startActivityApi30 改名为ActivityTaskManagerService跨进程通信,通知系统进程,启动Activity
ActivityStarter#execute,这里面会调用startActivity,并且处理了ActivityLauncherMode;获取到ActivityStack处理activityPausedLocked,pause当前Activity
Service
启动方式
startService
服务启动之后会无限期运行,需要调用stopSelf或stopService停止
生命周期:onCreate、onStartCommand、onDestroy
onStartCommand
START_NOT_STICKY:除非有待传递的挂起 Intent,否则服务挂掉不再重启;适用避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务;
START_STICKY:将挂起的Intent全部执行一遍,如果没有挂起的Intent,就传递一个空的Intent;适用于不执行命令、但无限期运行并等待作业的媒体播放器;
START_REDELIVER_INTENT:先用传递给Service最后一个Intent调用onStartCommand;挂起的Intent会依次执行;此常量适用于主动执行应立即恢复的作业(例如下载文件)的服务;
bindService
可以绑定多个应用组件,当所有应用组件都调用unbindService解绑之后,service会自动停止;或者应用组件(例如Activity)销毁,也会自动解绑;
生命周期:onCreate、onBind、onUnbind、onDestroy
Android 5.0 (api 21)以后不允许使用隐式Intent启动Service,因为您无法确定哪些服务将响应 intent,且用户无法看到哪些服务已启动;
Android 8.0 (api 26)如果应用没有在前台运行,系统会对后台服务进行限制,可能会抛出异常;前台服务需要调用startForegroundService()启动,并在5s内调用startForegroundService()绑定前台服务;bindService不受影响;
自定义Binder(本地服务)
服务和调用者只能在相同应用中
编写Service和Binder
自定义LocalBinder继承Binder,并在onBind中返回该binder;增加自定义函数,可以返回Service实例或者其他的
调用
继承ServiceConnection,重写onServiceConnection,获取到的IBinder就是LocalBinder对象,可以直接调用LocalBinder相应函数;重写onServiceDisconnection,可以释放一些资源、重置标志位等;bindService时传入;
Messenger(远程服务)
编写Service和Messenger
定义Handler处理消息,在onBind中返回Messenger(Handler).binder
继承ServiceConnection,重写onServiceConnection,Messenger(service);重写onServiceDisconnection,可以释放一些资源、重置标志位等;调用messenger.send(Message)发送消息;
AIDL
创建.aidl文件,定义接口
继承接口.Stub创建Binder,并在onBind方法中返回
调用:创建ServiceConnection
可以传递的对象:基本数据类型、String、List、Map、Parcelable
IntentService(过时)
利用Handler依次处理向Service提交的任务
重写onHandleIntent既可
受Android 8.0限制
JobIntentService
<service android:name=\"com.jorgesys.jobscheduler.MyService\" android:permission=\"android.permission.BIND_JOB_SERVICE\" android:exported=\"true\"/> 不然报错
Android 8.0 (Api 26)以下还是使用startService处理;以上使用Jobscheduler处理任务
用来替代IntentService
JobService(过时)
使用相当繁琐,同时受Android 8.0限制
可以定义任务周期执行,以及条件限制,例如系统IDEL状态、网络条件
推荐使用JobInetntService或者WorkManager
BrocastReceiver
静态注册:无限期运行,并监听;
动态注册:需要unRegister,生命周期跟随注册者;
ContentProvider
ContentProvider:负责对自己应用内数据增删改查;通过contentResolver.notifyChange通知观察者数据变更;
ContentResolver:根据Uri执行增删改查;
ContentObserver:负责观察ContentProvider中的数据变化;通过contentResolver.registerContentObserver注册观察者;
Fragment
CREATED
onAttach
onCreate
onCreateView
onViewCreated
onActivityCreated
onViewStateRestored
STARTED
onStart
RESUMED
onResume
onPause
onStop
onSaveInstanceState
onDestroyView
DESTROYED
onDestroy
onDettach
View
Layout性能对比
View绘制流程
ViewRootImpl#requestLayoutViewRootImpl#performTraversals
performMeasure#measure#onMeasure
MeasureSpec:Parent传递给Child的布局需求
UNSPECIFIED:任意大小,意味着Parent对Child不做要求
EXACTLY:Parent已经测量出Child确切大小;例如Child明确指定大小,或者match_parent,表示和ParentView一样大
AT_MOST:Child为wrap_content,最大值不会超过ParentView的剩余空间
performLayout#layout#onLayout
performDraw#draw#onDraw
requestLayout
位置或者大小变化时调用
invalidate
样式变化时调用,例如颜色
触摸事件分发
dispatchTouchEvent
接收事件并分发1. ACTION_DOWN事件返回false,后续move、up事件都不会分发2. ViewGroup重写了dispatchTouchEvent
如果是ViewGroup
调用onInterceptTouchEvent判断是否要拦截掉事件
如果不拦截事件,根据点击的位置,找出child,调用child.dispatchTouchEvent
如果child.dispatchTouchEvent返回false,执行onTouchEvent
如果是View
调用onTouchEvent
返回值
true:不再向下dispatch,执行onTouchEvent
false:不再向下dispatch,也不调用onTouhEvent处理事件,向父View回溯
super:执行ViewGroup或者View的实现,向子View分发事件
onInterceptTouchEvent(ViewGroup)
true:则代表拦截该事件,停止传递给子view,会走自己的onTouchEvent事件
false、super:不拦截事件
事件被拦截后,子view会接收到一个cancel事件,来恢复之前的状态,结束当前事件流
requestDisallowInterceptTouchEvent(ViewGroup)
请求父布局不拦截
调用该方法即可实现事件的传递和接管,并且在整个事件流中,父view以及再往上的父view都要遵守该规则
onTouchListener
优先于onTouchEvent方法执行
onTouchEvent
View用来消费事件的ViewGroup没有重写onTouchEvent方法
ACTION_DOWN返回false,后续事件不会再接收
onClick
在onTouchEvent的UP事件中执行,会返回true直接消费事件
TouchDelegate
可以用来扩View的点击区域
会在View的onTouchEvent中回调
GestureDetector
可以用来处理一些常见手势
事件冲突解决
内部解决
通常在ChildViewGroup重写dispatchTouchEvent;并在ACTION_DOWN调用requestDisallowInterceptTouchEvent(true);在ACTION_MOVE调用requestDisallowInterceptTouchEvent(condition);ChildViewGroup需要返回super,继续向子View分发;ChildViewGroup如果返回true,ChildViewGroup消费事件,父布局不会再响应onTouchEvent;ChildViewGroup返回false,子View不会再接收到事件,点击等操作不能用;
外部解决
在ParentViewGroup中重写onInterceptTouchEvent,合适的时机拦截掉事件
RecyclerView
缓存复用
mAttachedScrap:存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找 ViewHolder;
mHiddenViews:做动画前会处于隐藏状态
mCachedViews:用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是2,通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则移除旧 ViewHolder
ViewCacheExtension:开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存
RecycledViewPool :ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把 ViewHolder 存入RecyclerViewPool 中;按照 Type 来查找 ViewHolder;每个 Type 默认最多缓存 5 个;可以多个 RecyclerView 共享 RecycledViewPool;
DiffUtils
LayoutManager
LinearLayoutManager
GridLayoutManager
StaggeredGridLayoutManager
ItemDecoration
DividerItemDecoration
ItemAnimator
SnapHelper
PagerSnapHelper
ConcatAdapter
属性动画
补间动画
帧动画
共享元素动画
布局动画
转场动画
Bitmap
内存分配
Android 3.0 (Api 11) -- Android 7.1 (Api 25) Bitmap存在Java堆中;和普通对象一样由Java虚拟机回收;
Android O 8.0 (Api 26) Bitmap 像素存在Native中,引用存在Java 堆中;如果调用了recycle,仅仅是标记该Bitmap为dead,等待垃圾回收;Bitmap回收实际上在创建时就指定了Bitmap#nativeGetNativeFinalizer函数来回收内存;
Bitmap压缩
采样率压缩
BitmapFactory.Options.inSampleSize只能是2的倍数
质量压缩
存储压缩,只会缩放文件大小,尺寸和在内存中的大小不会改变
矩阵缩放
可以直接用Bitmap#createScaledBitmap创建缩放的Bitmap
常用属性
inJustDecodeBounds:只获取Bitmap属性数据,不加在Bitmap像素
inScaled:默认为true,是否支持缩放,设置为true时,Bitmap将以 inTargetDensity 的值进行缩放;
inSampleSize:采样率值,必须为2的倍数,如果为2,意味着宽高各位原来的1/2,大小则为原图1/4;
Bitmap inBitmap:内存复用,Android 4.4以下只能复用相同大小内存,Android 4.4以上只要比复用的内存小即可
超大图展示
使用BitmapRegionDecoder对大图进行局部解析
IPC进程间通信
Binder
Messager
OkHttp
拦截器
1. ApplicationInterceptor
开发者通过addIntercetor添加的
每次发起请求只会调用一次
不关心中间的重定向和重试
2. RetryAndFollowUpInterceptor
失败时重定向重试
最多重定向20次(MAX_FOLLOW_UPS)
3. BridgeInterceptor
构造网络请求信息
主要添加了一些Header
4. CacheInterceptor
缓存拦截器
主要通过CacheStrategy类来计算缓存是否生效
5. ConnectInterceptor
建立连接,发起网络请求
负责从ConnectionPool中复用连接
构建Http请求,发起握手、SSL等
6. networkInterceptors
开发者通过addNetworkInterceptor添加的
7. CallServerInterceptor
利用ConnectInterceptor建立的HttpStream传输数据
EventListener
可以获取各个事件回调
ConnectionPool
连接池,默认并发5个keepalive连接,保活5分钟
Retrofit
动态代理创建Service
自定义注解方案
对Retrofit.create再包裹一层动态代理
在CallAdapter中解析annotation
CallAdapterFactory
根据returnType从各个CallAdapterFactory查找对应的CallAdapter
Converter.Factory
requestBodyConverter根据注解将注解信息转成requestBody
responseBodyConverter将responseBody转成T
stringConverter将@Query、@Path等这些注解的值转成String
GsonConverterFactory将Json格式请求体转成RequestBody;将Json格式ResponseBody转成对象;
简化请求
对请求和响应提供反序列化工具
支持不同HttpClient
图片加载
Glide
缓存流程
2. 获取内存缓存LruResourceCache(memoryCacheSize)中获取,并返回EngineResource;EngineResource增加了引用计数功能,如果被引用count+1,释放时count-1,count=0放回从ActiveResources中移动到LruResourceCache,count>0移回去
3. Resource Disk Cache
4. 原始数据 (Data Disk Cache)
5. 网络缓存
缓存控制
with(view)、with(fragment)、with(activity)都是优先关联fragment、activity、application生命周期利用addFragment来完成生命周期联动,当destroy对请求取消和释放资源
BitmapPool
大小为屏幕大小最高质量的一张图片
主要用于复用Bitmap对象,省去反复申请Bitmap内存,造成内存抖动
加圆角
利用Transform对Bitmap裁剪
将View做成圆角
自带统计监控功能
支持延迟到图片尺寸计算完成加载
支持飞行模式、并发线程数根据网络类型而变
“无”本地缓存,交给OkHttp来控制缓存
Fresco
很好的支持GIF图片的显示
需要使用自带的ImageView,灵活性不够
Glide VS Picasso
Glide:RGB-565Picasso:ARGB_8888
Glide可以加载Gif动图
Picasso缓存的是全尺寸的Glide根据View大小缓存不同尺寸的
Picasso自带统计监控功能,命中率等
异步任务
RxJava
全局异常捕获
自定义线程池
操作符
内存泄漏检测
LeakCanary
在Application中注册ActivityLifecycleCallback观察Activity销毁
为FragmentManager注册FragmentLifecycleCallbacks监听Fragment销毁
当监听Activity或Fragment Destroy时,会将Activity加入到KeyedWeakReference中;并为页面生成UUID,将UUID和KeyedWeakReference添加到watchedObjects中;
随后执行ensureGoneAsync,调用watchExecutor,watchExecutor向mainHandler发送了一个postDelayed
KeyedWeakReference中存储的引用对象一旦被回收,就会将其放入到RQ中;将ReferenceQueue中存储的KeyedWeakReference清空掉,对应的watchedObjects中也清除掉;watchedObjects剩下的就是没有被回收的,需要进行检测是否有内存泄漏;
调用回调,开启子线程,Runtime.getRuntime().gc()触发GC,等待100ms,System.runFinalization()
如果触发gc后,对象还没有回收,就会调用Debug.dumpHprofData进行dumpHeap;
dumpHeap结束后,发出通知,启动独立进程的HeapAnalyzerService进行引用分析;
利用shark库分析引用链;
主线程阻塞检测
BlockCanary
向Looper.getMainLooper().setMessageLogging(Printer)
同时创建出ThreadSampler和CpuSampler用来记录资源调用;并启动记录;
StackSampler:每300ms发出postDelay,通过mThread.getStackTrace记录主线程运行信息;最多记录100条;
CpuSampler:每300ms发出postDelay,通过读取cpu日志记录;最多记录10条;
两次日志打印超出设置的阈值,就会触发阻塞警告,停止日志记录;
事件总线
EventBus
subscribe
EventType
subscriptionsByEventType按priority保存EventClass与对应的method和activityClass
typesBySubscriber保存activityClass和涉及到的EventClass,用来判断当前class是否已经注册过
stickyEvents保存粘性事件,并在订阅的时候执行一次
ThreadMode
POSTING:在哪个线程发送,就在哪个线程处理
MAIN:如果在主线程发送,直接执行;否则通过Handler来添加主线程到MQ中
MAIN_ORDERED:直接加入主线程MQ中
BACKGROUND:在主线程中发送,加入事件队列,线程池处理;在子线程发送,直接在子线程中处理
ASYNC:直接加入到事件队列,通过线程池在子线程处理
priority
默认为0,优先级高的优先接收事件
解析Method事件信息时,按priority加入到事件List中;
sticky
默认非粘性事件;
注册时,如果sticky为true,调用一次stickyEvents中的事件
注册时通过反射查找接收函数并缓存到METHOD_CATCH中;
unSubscribe
post
向当前线程利用ThreadLocal保存的PostingThreadState中的eventQueue添加事件
如果当前线程没有在处理事件,while(true)处理消息事件队列postSingleEvent
从subscriptionsByEventType中取出Event对应的订阅方法,并执行
postSticky
向stickyEvents中添加event
执行post
LiveDataBus(LiveData 2.1.0)
LiveDataBus具有生命周期感知
官方支持架构组件,可以减小APK包的大小
实现及其简单
收到订阅前的事件问题
setValue或postValue LiveData#mVersion+1
observe
Observe包装类LifecycleBoundObserver中mLastVersion默认为-1,订阅时对比LiveData#mVersion,会发送一次事件
解决方案:在事件触发前,利用反射将mLastVersion置为mVersion
observeForever
解决方案:
RxBus
PublishSubject用于收发事件
SerializedSubject包装PublishSubject保证多线程下事件有序
bus.ofType(eventType).onBackpressureBuffer()订阅时过滤事件类型,背压有序处理事件发送和消费
数据库
Room
GreenDao
Realm
Jetpack
Lifecycle
LiveData
ViewModel
DataBinding
ViewBinding
ViewPage2
Paging
Compose
DataStore
Startup
Dagger2
兼容性适配
Android 5
Android 6
Android 7
Android 8
Android 9
Android 10
Android 11
ANR 0.1‰
常见原因
主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态
应用在主线程上非常缓慢地执行涉及 I/O 的操作,读写文件
应用在主线程上进行长时间的计算
主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回
主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块
类型
InputDispatching:5秒内无法响应屏幕触摸事件或键盘输入事件
logcat常见日志
等待锁时间过长:java.lang.Object.wait
主线程操作数据库:android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount
handler超时:android.os.MessageQueue.nativePollOnce,日志里可能看到原因,比如网络请求等待锁
进程间数据传输过大:android.os.BinderProxy.transactNative
主线程绘制超时:android.view.ThreadedRenderer.nSyncAndDrawFrame
BroadcastQueue :前台广播onReceive()函数10s,后台60s
AMS中BroadcastQueue.BroadcastHandler收到BROADCAST_TIMEOUT_MSG
Service :前台服务20s,后台服务在200s
AMS.MainHandler收到SERVICE_TIMEOUT_MSG
ContentProvider :ContentProvider的publish在10s内没进行完
AMS.MainHandler收到CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG
排查、优化
开启StrictMode
在开发者选项中 启用后台 ANR 对话框
Profiler排查方法耗时
接入BlockCanary
低版本/data/anr/traces.txt高版本多个/data/anr/anr_*
\"main\" prio=5 tid=1 Sleeping 主线程Sleeping
CPU usage from 75634ms to 0ms ago:25% 869/system_server: 19% user + 6.1% kernel / faults: 86246 minor...CPU usage from 601ms to 1132ms later with 99% awakeCPU负载过高
Reason: Input dispatching timed out输入事件等待时间过长
java.lang.Thread.State: BLOCKED (on object monitor)等待获取锁时间过长
OOM 约0.04‰
常见类型
Thread创建失败:java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
堆溢出 java.lang.OutOfMemoryError: Java heap space
栈溢出:java.lang.OutOfMemoryError: OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available
大对象创建失败:java.lang.OutOfMemoryError: dalvik.system.VMRuntime.newNonMovableArray (Native Method)
Native内存溢出:Caused by: java.lang.OutOfMemoryError at android.graphics.Bitmap.nativeCreate
埋点方案
自动化埋点通过GradlePlugin注入
PV、PD通过ActivityLifecycle
热修复方案
基于multidex的热更新框架
Tinker
将patch.dex和base.dex合并成新的dex,将merge.dex插入到pathClassLoader的dexElements最前面
缺陷,这部分合并是在vm heap中,容易oom
Sophix
缺陷
需要反射更改DexElements改变Dex的加载顺序
patch需要在下次启动时才能生效
在android N [speed-profile]编译模式下可能会有问题
native hook方案
dexposed
利用xposed劫持自己应用进程中的Java Method调用,统一转发到Native Dispatch方法上,从而转发到path包中的方法上;
dalvik支持较好,但是不支持art,所以5.0以上支持情况不好
最大挑战在于稳定性与兼容性,而且native异常排查难度更高
由于无法增加变量与类等限制,无法做到功能发布级别
即时生效
不需要任何编译器的插桩或者代码改写,对正常运行不引入任何性能开销。这是AspectJ之类的框架没法比拟的优势
对所改写方法的性能开销也极低(微秒级),基本可以忽略不计
从工程的角度来看,热补丁仅仅是牛刀小试,它真正的威力在于『线上调试』
基于Xposed原理实现的AOP不仅可以hook自己的代码,还可以hook同进程的Android SDK代码,这也就可以让我们有能力在App中填上Google自己挖的坑。
Andfix
Hotfix/Nuwa
需要针对dalvik虚拟机和art虚拟机做适配
需要考虑指令集的兼容问题
需要native代码支持,兼容性上会有一定的影响
robust
FPS
Memory
CPU
Crash捕获
业界性能指标
测试
单元测试
覆盖率测试
Lint、CheckStyle
Gradle Plugin
继承Plugin
通过registerTransform来处理class
通过Project可以查找、监听或者创建task
通过Project.extensions创建配置信息
在resource文件夹中配置gradle-plugin实现类
插桩框架
ASM
javassist
APT
增加AutoService注解
配置要处理的注解
javapoet生成Java代码
合规检测
Kotlin
data class
默认重写了equals、hashCode、toString、copy函数只针对声明在构造函数中的字段
componentN 按字段声明顺序生成解构函数
将字段声明在类中,可以从重写的函数中排除
sealed class
密封类本身是抽象的,不能被实例化
继承自密封类的类可以使用when函数
inline class
内联类必须含有唯一的一个属性在主构造函数中初始化
在运行时,将使用这个唯一属性来表示内联类的实例,内联类并没有被实例化
限制
不能有init代码块
不能含有延迟初始化/委托属性
可以继承接口,不能被继承或继承其他类
inner class
等同Java内部类
typealias
可以为类起别名 typealias NodeSet=Set<Network.Node>
可以定义闭包
内联
关键字
inline
修饰标识内联函数
修饰的函数会被复制到调用处
noline
禁用内联,修饰内联函数的lambda参数
在内联函数中需要将入参lambda作为参数传递或作为返回值时
crossInline
交叉内联,修饰内联函数的lambda参数
在内联函数中需要在嵌套函数或者其他lambda中调用入参lambda
reified
修饰符来限定类型泛型参数
在内联方法中可以将方型当作Class使用
内联属性
对属性或get、set方法内联
注意
大函数内联可能导致字节码增加
目前在例如foreach这样的内联函数中不能调用break或continue
内联函数的lambda中return只能返回调用函数,无法对内联函数执行return
委托
委托类
被委托的类必须和委托类实现相同接口
可以复写其中的方法
委托属性
标准委托 by lazy
安全模式
SYNCHRONIZED线程安全
PUBLICATION(一次发布)多线程调用,只有第一个返回值作为Lazy实例返回值
NONE未指定,不保证线程安全
可以继承Lazy接口,自定义实现懒加载
Delegate
ReadOnlyProperty
只支持getValue
ReadWriteProperty
增加支持setValue
ObservableProperty
具备beforeChange、afterChange钩子函数
委托给另一个属性since1.4
var delegatedToTopLevel: Int by ::topLevelInt
可以委托给顶层属性、同一个类或其他类的成员或扩展属性
将属性储存在映射中
上面代码使用name时可能出现空指针
局部委托属性
fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo)}
没啥用
提供委托
provideDelegate,不如叫delegateProvider
复写provideDelegate操作符
Property每个只能委托一个变量,provideDelegate作用就是为每个变量都提供Property
Delegates工具类
observable,实现了afterChange
vetoable,实现了beforeChange
notNull
必须先经过赋值,否则获取时throw IllegalStateException
扩展
扩展函数
扩展属性
实际就是一个get函数
每次都会生成新的值
协程
创建
启动
取消
Context
Dispatcher线程调度
Dispatchers.Unconfined
挂起点之前运行在当前上下文挂起点之后运行在子线程
不懂的慎用
Dispatchers.Default
useCoroutinesScheduler=systemProp(\"kotlinx.coroutines.scheduler\")
true: ExperimentalCoroutineDispatcher
false: CommonPool,密集计算型
看着都是密集计算型,个数为CPU核数
Dispatchers.IO
受kotlinx.coroutines.io.parallelism配置控制kotlinx.coroutines.scheduler.max.pool.size 配置最大线程数
默认为64个和CPU核数中的最大值
Dispatchers.Main
在Android上就是Handler
自定义
线程池转换Executors.newSingleThreadExecutor().asCoroutineDispatcher()
CoroutineStart启动模式
DEFAULT
默认配置,立即调度,协程真正开始执行前可以取消
LAZY
需要手动调用start或join,如果没有调用start或join就cancel会抛出异常
ATOMIC
立即调度,协程真正开始执行前不能取消
实验性Api
UNDISPATCHED
立即调度,在第一个挂起点前不能取消,并且第一个挂起点前运行在当前Dispatcher下
ContinuationInterceptor
异常
CoroutineExceptionHandler
异常传播
作用域
capacity:Int
0:RENDEZVOUS:RendezvousChannel阻塞策略,缓冲区为0,不调用receive或者send比receive快,send就会一直挂起
Integer.MAX_VALUE:UNLIMITED:LinkedListChannel不限制,缓存队列无限存储
-1:CONFLATED:ConflatedChannel丢弃策略,只保留最后一个
-2:BUFFERED:ArrayChannel(64)只有在缓冲区满的时候才会挂起
ArrayChannel(capacity)
scope.produce
启动一个生产者;直接处理生产者逻辑;利用返回ReceiveChannel处理消费者逻辑;
scope.actor
启动一个消费者;直接处理消费者逻辑;利用返回sendChannel处理生产者逻辑;
Flow
代码风格和质量
工具
Detekt
KtLint
Suppress
文档
Dokka
Dart
Future
多个Future依次创建,按创建Future对象顺序执行,和then函数调用顺序无关
Stream
单播流
只能listener或await for 一次,否则抛出异常
广播流
asBroadcastStream转成广播流
StreamController
辅助类,可以进行事件发送和监听
StreamTransformer
对stream进行变换
Isolate
创建步骤
初始化isolate数据结构
初始化堆内存(Heap)
进入新创建的isolate,使用跟isolate一对一的线程运行isolate
配置Port
配置消息处理机制(Message Handler)
配置Debugger,如果有必要的话
将isolate注册到全局监控器(Monitor)
event queue
microtask queue
网络
TSL/SSL
加密流程
Client:Client Hello;支持的加密套件、协议版本;随机数random_c;
Server:Server Hello;确定的加密套件和协议版本;随机数random_s;数字证书(RSA公钥);Hello Done;
Client:利用RSA公钥加密随机数pre_master,同时利用三个随机数计算出对称加密密钥;发送利用对称加密密钥加密的handshake_msg;编码改变通知;发送handshake_finish握手结束通知;
Server:利用RSA私钥解密pre_master,计算对称加密密钥;发送对称加密密钥加密的handshake_msg;发送handshake_finish握手结束通知;
Client、Server:Application msg
TCP
三次握手
1. Client发送SYN,请求建立连接2. Server发送ACK、SYN,应答并建立连接;建立半双工,此时Client可以给Server发送数据3. Client发送ACK,表示收到Server ACK、SYN;建立全双工,此时Server可以给Client发送数据
四次挥手
TCP是全双工,Client、Server都可以发送数据
1. Clien发送FIN,表示Client不再发送数据;如果Client收不到Server ACK,Client会不断重试发送FIN;2. Server发送ACK,表示Server收到Client FIN;此时Server可能还在发送数据,所以不能直接将ACK和FIN合并3. Server发送FIN,表示Server不再发送数据;如果Server收不到Client ACK,Server会不断重试发送FIN;4. Client发送ACK,表示Client 收到Server FIN
UDP
Sockt
HTTP
v0.9
只支持GET请求,仅能请求HTML资源
v1.0
新增POST和HEAD
支持cache
短链接,用完关闭,需要自己增加connection:keep-alive请求头保持长链接
新增Content-Type,支持其他资源text/xml image/jpeg audio/mp3
v1.1
新增PUT、PATCH、OPTIONS、DELETE
增加Content-Length,用于链接复用表示一个请求结束;Header增加Host,支持虚拟主机;支持chunked块传输
链接复用,省去TCP多次建立链接问题;默认支持长链接,用完不关闭;不需要增加keep-alive请求头;最多维持6-8个长链接;
客户端可以在同一个链接中依次发送多个请求;
队头阻塞问题:服务端是依次处理,处理完一个请求,客户端才能发送下一个请求;
Header体积大问题
Server不能主动Push
每次传输还需要建立新的链接
v2.0(SPYD)
头信息和数据体都是二进制,称为头信息帧和数据帧
支持多路复用,复用TCP连接,且不用有序,每个数据流中的数据包必须表示数据流Id,用于区分是属于哪个数据流
引入头信息压缩机制,即客户端和服务端都维护一张Header索引
服务端可以主动Push
抓包
Https 为什么能被抓包?
客户端没有验证Server在SSL认证过程中的公钥证书真伪性,直接选择了信任所有证书;
防止抓包
不要信任所有Https请求
应下载或者内置Server公钥证书,通过对比本地和SSL下发的公钥证书有效性来判断请求是否来自真实的服务端
0 条评论
回复 删除
下一页