再准备
ctrl+m打开浏览器markdown预览
#算法 ##排序
###归并排序 先分治排序,再两两归并排序,也适用于大数据量排序
###快速排序
对 3 4 1 2 5
排序
- 先从e往前e–找,直到找到一个小于a[b]的数,交换
- 再从b往后b++找,直到找到一个大于a[e]的数,交换
- 直到b==e 返回索引b。
3 1 4 2 5 6
b e
3 4 1 2 5 6 发现 2<3 交换
b e
2 4 1 3 5 6 换b往后找
b e
2 4 1 3 5 6 发现 4>3 交换
b e
2 3 1 4 5 6 换e往前找
b e
2 3 1 4 5 6 发现 1<3 交换
b e
2 1 3 4 5 6 换b往后找
b e
2 1 3 4 5 6 b==e 结束,3的位置肯定是对的, 2 1 与 4 5 6 递归下
e
b
void main(){
quickSort(a,0,a.length);
}
void quickSort(int a[],int b,int e){
int m = quickSortPart(a,0,a.length);
quickSort(a,b,m-1);
quickSort(a,m+1,e);
}
// a[b] 到了正确的位置上
int quickSortPart(int a[],int b,int e){
boolean back = true;
while(b < e){
if(a[b]>a[e]){
swap(a[b],a[e]);
// 产生了一次交换,改变应该前进的节点
if(back){
e--;
}else{
b++;
}
back=!back;
}else{
if(back){
e--;
}else{
b++;
}
}
}
return b;
}
###堆排序 不稳定
- 首先用数组表示一个二叉树,
i
节点的左子树为2i+1
,右子树为2i+2
- 先把堆建好,
- 排序,每次取出堆头放到数组末尾
static void adjustHeap(int a[], int i, int length) {
//往子节点遍历,
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) {
if (k + 1 < length && a[k + 1] > a[k]) {
k++;
}
// 就是 根节点 左节点 右节点 中最大的当作 根节点
if (a[k] > a[i]) {
swap(a, k, i);
// 如果发生了切换,把子树也调整下
// 比如 1 3 2, 3变成根节点了, 3 1 2, 那以1为根节点再调整下。
i = k;
} else {
break;
}
}
}
public static void swap(int[] arr, int a, int b) {
int temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
###KMP
- KMP 算法的关键思路:避免了不必要的回溯
- 真前后缀概念 ``` i 0 1 2 3 4 5 6 7 模式串 A B C D A B D ‘\0’ next[ i ] -1 0 0 0 0 1 2 0
//next[j]代表 [0, j - 1] 区段中最长相同真前后缀的长度 int[] getNext(String p){ int j=-1; int i=0; int[] next = new int[p.length]; next[i]=-1; while(i<p.length){ if(j==-1 || p[i]==p[j]){ i++; j++; next[i] = j; }else{ j = next[i] } } }
int kmp(String str,String p){ int[] next = getNext(p); int i=0; int j=0; while(i<str.length && j<p.length){ if(str[i]==p[j]){ i++; j++; }else{ j = next[j]; } } if(j==p.length){ // 匹配成功 return i-j; } return -1; }
###### 判断两个单链表是否相交同时找出第一个相交点
1. 判断是否有环
1. 无环
* 相交的话尾节点肯定相同
2. 有环
* 一步 两步法
###### [1,1,0,0,2,2,1]排序[0,0,1,1,1,2,2]
[三个指针](https://blog.csdn.net/nutcc/article/details/11224643)
###### 只能买卖两次股票的情况下,如何最大化收益 ,没卖前不能买 [1, 2, 1, 3, 0, 4]
用f(i, j) 表示在第i (from 0) 天到第j天,只买卖一次时的最大收益;那么,要计算买卖两次的收益,只需要找到一天k,使得f(0, k) + f(k + 1, n - 1)最大即可
#基础知识
### http https http1.0 http1.1 http2.0
#####http1.0
每次建立连接都要经历三次握手和滑动窗口慢启动
#####http1.1 半双工
1. 更多缓存控制策略供选择 Expires:绝对时间 Cache-Control:3600(绝对时间)
* 缓存再验证命中(If-Match)
* 服务器再验证 (If-Modified-Since,If-None-Match)
2. 请求头新增`range`,支持断点续传
3. `Host`头处理,一台物理主机多虚拟主机共享一个`ip`问题
4. `Keep Alive`,复用Tcp链接
5. 新增错误状态码
#####http2.0 全双工
1. 二进制格式
2. request 多路复用
3. header 压缩,每次都重复发header头
4. 服务端推送
##### 一次网络请求过程
1. DNS解析ip
* 优化 dns 预解析
2. 有ip了,tcp三次握手
* https 还有 TLS 握手
3. http半双工,客户端请求,服务端响应
* 减少重复请求
* 合理使用缓存,根据网络类型优化(移动网络图片可以差点)
* 链接复用
4. 客户端得到内容,渲染
### TCP
当一个连接被建立或被终止时,交换的报文段只包含TCP头部,而没有数据。
* 三次握手
* 目的
* 双方都能明确自己和对方的收、发能力是正常的,避免不必要的资源浪费
* 过程
1. C 向 S 发 SYN。C进入SYN_SENT。 S得出结论,C的发送能力 S的接受能力正常
2. S 向 C 发 SYN,ACK。S进入SYN_RCVD。 C得出结论,S的收发能力,C的收发能力正常
3. C 向 S 回 ACK。 C进入ESTABLISHED,S收到后也进入ESTABLISHED。 S得出结论,C的收发能力,S的收发能力正常
* 所以至少三次交互,C S 都能确认双方的收发能力正常。比如第三步,光收到数据,S能确定自己的收能力和C的发能力正常,但结合第二步,还能确定自己的发能力和C的收能力正常。 如果是https,三次握手完了还会有个 TLS(安全传输层协议) 的hankshake
* 四次挥手

* 目的
* 也是保证双方的收发能力都能正常关闭,避免资源浪费
* 那为什么是四次呢,因为不同于握手,ACK 和 FIN 无法同时发,要分两次。
* 过程
* C发送 FIN 终止数据发送,C进入 FIN_WAIT1 状态
* S收到FIN,进入CLOSE_WAIT状态。S发送 ACK ,C收到ACK 进入 FIN_WAIT2 状态
* S发送完数据,S发送FIN ACK,S进入 LAST_ACK 状态
* C收到FIN,进入TIME_WAIT状态,C发送ACK,过一会进入CLOSED状态(过一会保证这个ACK能发送成功)。S收到ACK,进入CLOSED状态
##### WebSocket
* WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层。
* 允许服务端主动向客户端推送数据
##### https
中间者攻击
##### 下载速度慢优化

跟踪哪一步慢了
1. 是否缓存时间为0,导致每次都走源站?
2. 本地网络如何?
3. cnd性能如何
4. 源站负荷如何,是否国内业务使用国外源站 是否跨机房 bgp多线机房
### Unicode Utf-8 Utf-16
Unicode包含世界上所有语言的字符集。Utf-8 Utf-16 都属于 Unicode 的实现方式,
* Utf-8 是变长的,用1-4个字节表示一个字符。向下兼容ASCII
* Utf-16 2个字节或4个字节
定长的编码方便定位
字符数一直在增加
>UTF-16 曾经是可以当定长编码用的,这也是当初会选择他们的主要原因。但是计划比不上变化快,Unicode收录的字符很快就超过了65536个。所以如果还想用定长编码似乎只能采取UTF-32这种编码方式了。可是这种方式最大的问题是即使是英文字母也要四个字节来存储,空间浪费太大了。所以UTF-8这种变长编码方式开始流行起来了,英文字母只需要一个字节,汉字三个字节。更古怪更稀有的字符可以用四个,五个或更多字节表示,因为使用频率低,所以空间浪费不大。当然定长编码的好处是可以快速定位字符,对于string.charAt(index)方法有着较好的支持。UTF-8的话,就需要从头开始一个字符一个字符的解析才行,会慢一点。但是与查询定位相比,顺序输出的情况更多,所以平常也不会感受到效率会比较慢。未来的趋势是UTF-8,文件编码是UTF-8,数据库编码是UTF-8,网络流编码是UTF-8,这样真的能减少很多麻烦,现在想要解决编码问题,统一UTF-8化是最佳解决方案。
### 事务 ACID特性
* 原子性(atomicity)
* 一致性(consistency)
* 隔离性(isolation)
* 持久性(durability)
### MVC MVP MVVM
1. MVC
* Activity 就是C
2. MVP
* 核心理念是通过一个抽象的 IView 与真正的View进行解耦
1. MVVM
* Model View ViewModel
1. 数据驱动
* 数据变化 ui自动变化
* 观察者机制
2. 低耦合度
* 数据独立于ui
* 团队协作,一个写view,一个写viewmodel。 代码复用,单元测试
3. 更新ui
* 由框架确保在主线程
* 基本与MVP一致,ViewModel相当于P,唯一的区别就是双向绑定,View的变动自动反应在ViewModel上,反之亦然,这样开发者就不用处理事件和view更新了。(有点像flutter的响应式编程,数据驱动UI)
[MVVM Light kit](https://www.jianshu.com/p/43ea7a531700)
### png jpg webp的区别
1. png
* 无损压缩
* 颜色越少 压缩率越高
* 霍夫曼树呗
* 透明通道
* 有 8bit 24bit 32bit
2. jpg
* 有损压缩
* 24 bit真彩色
3. webp
* 有损无损都支持

### java基础
##### StringBuffer 为什么是线程安全的
` public synchronized StringBuffer append(String str)`
##### Serializable 与 Parcelable
* Parcelable的速度比高十倍以上,只序列化属性,用于内存传递,高效
* Serializable 接口是一种标识接口(marker interface),这意味着无需实现方法,Java便会对这个对象进行高效的序列化操作。
这种方法的缺点是使用了反射,序列化的过程较慢。这种机制会在序列化的时候创建许多的临时对象,容易触发垃圾回收。
* Serializable的系统实现是采用ObjectInputStream和ObjectOutputStream实现
* Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了
##### 静态代理和动态代理
* 静态代理
* 手写
* 动态代理 InvocationHandler
* 代理的是接口
* 运行时通过反射动态生成
##### ThreadLocal
通过ThreadLocalMap实现每个线程独享资源(map key是ThreadLocal对象的弱引用),`ThreadLocalMap`使用开发地址法解决hash冲突,不同于`HashMap`的`链地址法`。
会有key为null,value有值导致的内存泄漏,set完要remove。
##### java 容器有哪些
1. LinkedBlockingQueue
2. CopyOnWriteArrayList
* 添加时复制,加锁,添加完了,新引用替换旧引用
* 不影响读
3. ConcurrentSkipListMap
* 跳表
##### ConcurrentHashMap
* 分段锁
* 不锁全表,只锁一部分
* jdk1.7的实现,1.8有所变化。ConcurrentHashMap 有 N 个 Segment,每个Segment维护一段区域的 hashcode,Segment本身继承ReentrantLock
* ReentrantLock
* 可重入,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)
* 可中断,可限时,等了一会还拿不到锁,可以选择放弃
* 公平锁,先来肯定先得锁
##### TreeMap
* 实现原理
* 红黑树 保证有序
* put get O(logn)的复杂度
##### LinkedHashMap
* 实现原理
* 在HashMap的基础上又维护了一个双向链表来保证插入顺序
##### HashMap
* 当键值对的个数大于 size`*`loadFactor 阀值时产生扩容
* 2倍扩容
##### 设计模式
工厂、单例、建造者、适配器、装饰器、代理、组合、策略、模板方法、观察者、责任链、
####线程
##### 如何控制某个方法允许并发访问线程的个数?
使用信号量 Semaphore ,信号量个数变为负数时,再次请求的时候就会阻塞
##### sleep wait yield 区别
* wait会让线程释放掉这个同步代码块的管程身份和锁
* yield 只是让当前线程尝试让出cpu的使用权而已
##### 为什么 wait 一定要synchronized中
防止 lost wakeup
##### String
1. 为什么是final
* 安全
* hashcode不可变
* 如果可以字符串常量池没法实现
2. 字符串常量池
* 堆中
* 节约很多内存
##### 注解
1. 作用域 类 方法 属性
2. 生命周期 源码期 编译期 运行期
3. AbstractProcessor 注解处理器,写我们不愿意写的模版代码
4. AutoService是为了注册注解处理器
5. JavaPoet生成java类
##### 内部类引用外部函数的参数,为什么要final定义
* 原因
* 内部类对象的生命周期与局部变量的生命周期不一致
* Java为了避免数据不同步的问题,做出了匿名内部类只可以访问final的局部变量的限制。
* 定义了final以后
* 编译器会把匿名内部类对象要访问的所有final类型局部变量,都拷贝一份作为该匿名类的成员变量(通过构造参数的形式)。
* 1.8后取消了这个检查,编译器自动加了
* Effectively final 功能 后面不能再赋值了
void A(){ int age = 10; handler.post{ // java8能通过 System.out.println(age); } // java8就会在 print 那句里报错,提示需要变为final 或 effectively final age = 11; }
##### 乐观锁
* `synchronized`是悲观锁
* CAS(compare and swap) 比较与交换 自旋锁 乐观锁
* AtomicInteger.getAndIncrement()
* CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作(操作系统层保障原子性)。
> 设定 AtomicInteger 的 value 原始值为 A,从 Java 内存模型得知,线程 1 和线程 2 各自持有一份 value 的副本,值都是 A。
线程 1 通过getIntVolatile(var1, var2)拿到 value 值 A,这时线程 1 被挂起。
线程 2 也通过getIntVolatile(var1, var2)方法获取到 value 值 A,并执行compareAndSwapInt方法比较内存值也为 A,成功修改内存值为 B。
这时线程 1 恢复执行compareAndSwapInt方法比较,发现自己手里的值 A 和内存的值 B 不一致,说明该值已经被其它线程提前修改过了。
线程 1 重新执行getIntVolatile(var1, var2)再次获取 value 值,因为变量 value 被 volatile 修饰,所以其它线程对它的修改,线程 A 总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。 ABA 问题
##### JNI机制
### kotlin
##### 协程
* 概念
* 没有一个官方统一的概念,网上概念很多,什么轻量级的线程,什么用户态,什么协作式的,什么处理多任务的组件
其实协程就是一个线程框架,官方提供的一套线程api,对执行和调度进行封装,结合编译器对语法的支持,能很方便的写出并发操作
* 方便在哪呢?
用同步的编码方式编写异步操作
消除了回调,更不容易出bug了
* 比如我们有个需求,并行请求A和B,然后合并结果,渲染ui。
* 以前怎么写,A回来了看B有没有回来,B回来了看A有没有回来,或者不并行了
* 协程来写 可能有问题
// launch内的这块代码就叫做 协程 // withContext(Dispatchers.IO) 这个关键字能让在IO执行完后自动切回到当前的上下文,避免线程切换时的代码嵌套
lauch(Dispatchers.Main){ // 相当于handler.post val a:Deferred = async{getA()} // 使用async返回的是Future对象 val b:Deferred = async{getB()} val ab = suspendingMerge(a.await(),b.await()) show(ab) } // 搞定,太爽了
// 协程内建函数
suspend fun getA():A{
return suspendCancellableCoroutine { continuation ->
// 构造call
call.enqueue(object : Callback
override fun onFailure(call: Call<T>, t: Throwable) {
continuation.resumeWithException(t)
}
})
} } ```
suspend 内的withContext(Dispatcher.IO)这里挂起 挂起就是切线程 非阻塞式挂起 看着代码是阻塞的实际是非阻塞的
- 原理
- kotlin编译器会对suspend标记的方法做代码转换 suspend是一种声明 声明这个是一个耗时任务 限制只能在协程里呗调用
- 增加Continuation类型入参,返回值为Object
- 生成了Continuation匿名类
- 对suspend方法的调用变为了switch形式的状态机,
- 是launch内的代码变为状态机,不是suspend的代码
// 反编译 suspend fun doA() { delay(100) } suspend fun doB() { delay(100) } fun test() { async { print("0") doA() print("1") doB() print("2") } }
- 是launch内的代码变为状态机,不是suspend的代码
- kotlin编译器会对suspend标记的方法做代码转换 suspend是一种声明 声明这个是一个耗时任务 限制只能在协程里呗调用
out协变 in协变
目的 在编译阶段解决可能出现的类型转换异常
- 逆变和协变是保证运行时安全而出现的机制
- 在协变中不能在函数参数中使用泛型 只能用 不能写
- 在逆变中不能在函数返回值中使用泛型 不能用 只能写
List<out A> 等价于 List<? extends A>
* 注意 跟 T extends A
这种类型声明时的上届不是一个东西!!!!!
List<in A> 等价于 List<? super A>
//声明时协变
// 使用时协变
fun copy1(from:ArrayList<Any>,to:ArrayList<Any>){
// 不报错
from[0] = "aaaa"
}
fun main1(){
var la = ArrayList<String>()
var lb = ArrayList<Any>()
// 报错 type mismatch,要求List<Any> 发现 List<String>
// java 类似的也报错 就是 List<Object> l = new ArrayList<String>() 也会报错的
copy1(la,lb)
}
fun copy2(from:ArrayList<out Any>,to:ArrayList<Any>){
// 报错 提示声明了 out 禁止被赋值
from[0] = "aaaa"
}
fun main2(){
var la = ArrayList<String>()
var lb = ArrayList<Any>()
// 不报错
copy2(la,lb)
}
// 使用时逆变
fun addTextView1(list:List<TextView>){
var tv = TextView(...)
list.add(tv)
}
fun main1(){
var l = List<View>()
// 报错 但是我只是想用这样list去接受一下tv,为啥不行啊
addTextView(l)
}
fun addTextView2(list:List<in TextView>){
var tv = TextView(...)
list.add(tv)
}
fun main2(){
var l = List<View>()
// 不报错
addTextView(l)
}
###Flutter
- framework
- 一系列Widget组件
- engine
- Skia 2D引擎库,android自带,会嵌到ios sdk里,所以ios sdk大
- embedder
Dart
..
级联- 单线程模型 微任务队列 microtask queue 与 事件队列 event queue
- 使用
Isolate.spawn
新建对象,使用sendPort
相互通信
Widget Element RenderObject 之间的关系
- Widget只是配置,RenderObject管理布局,绘制
- Element持有Widget和RenderObject引用,才是巨大控件树上的实体
mixin extends implement
- flutter是单继承
- mixin 实现类似多继承的效果
- mixins只能继承Object 不能有构造函数
- mixin X on A可以指定只有A类或者其子类才能混入X
Isolate
- 可以想成是一个沙盒,所有的dart代码都在isolate中执行
Future将异步返回一个值,而Stream将异步返回多次值
- Stream有多订阅(single)与单订阅(broadcast)
- Stream.asBroadcastStream()可以将单订阅转为多订阅
await for
读取Stream的多次值
State对象声明周期
- initState
- context还不可用
- didChangeDependencies
- context可用
- build
- dispose
Key
- GlobalKey
- LocalKey
- UniqueKey
- ObjectKey
热重载
- 扫描改动
- 增量编译
- 推送更新 代码合并
- widget重建
JVM
java 有 java语言规范 和 java 虚拟机规范,所以有大批语言能在 java 虚拟机上运行,比如 kotlin,groovy。 java 虚拟机只认 class 文件,字节码。
弱引用 软引用
- 软引用,在GC时,只有内存不足时才回收。所以图片框架的内存缓存用的是软引用。
- 弱引用,GC时就回收,无论内存足不足
GC
新生代,老生代,永久代。 每代GC策略不一样,内存达到阈值,GC整理空间
怎么判断垃圾
- 引用计数法
- 可达性分析法
垃圾回收算法
- 标记-清除
- 碎片多
- 标记-整理
- 复制法
- 将可用内存分为两块,A用完了,把A的整理复制给B,然后重新将可用内存分为两块。
- 分代收集
- 目前jvm普遍采用的算法
- 新生代使用 复制法
- 老生代使用 标记整理
#####jvm内存模型
- 线程共享
- 方法区
- 类信息,常量,静态变量
- 属于持久代
- 堆
- 字符串常量池
- 方法区
- 线程私有
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 为native方法服务
#####那些对象可以作为GC Roots的对象有:
- 虚拟机栈 本地方法栈
- 方法区中的类静态属性引用的对象;
- 方法区中常量引用的对象;
#####类加载过程 5个步骤
- 加载
- 获取类的字节流
- 无论你用什么方法获取,可以从jar包,也可以从网络,也可以动态代理生成这个类
- 静态存储结构存入方法区
- 生成该类的
java.lang.Class
对象 - 类加载时机
- 任何用到该类的就会加载,包裹Class.forName
- 获取类的字节流
- 验证
- 确保该class符合虚拟机标准
- 准备
- static初始化,存到方法区
- 比如
static int a = 99
此时a还是0
- 解析
- 将常量池的符号引用替换为直接引用
- 符号引用与直接应用,比如A类引用了B类,在未加载B类前,A类不知道B类的内存地址,所以只能用符号引用表示
- 分派就是多态的体现
- 将常量池的符号引用替换为直接引用
- 初始化
- 将
a=99
- 将
class A{
static A singleInstance = new A();
static int count1;
static int count2=0;
public A{
count1++;
count2++;
}
}
void main(){
getInsance();
print(count1);
print(count2);
// 输出的结果是
// 1
// 0
}
ClassLoader们
- 启动类加载器 BootstrapClassloader
- 负责加载
/lib 下核心库,入rt.jar
- 负责加载
- 扩展类加载器 ExtensionClassloader
- 负责加载
/lib/ext 下的类库
- 负责加载
- 应用程序加载器 AppClassloader
- 负责加载 Classpath
android 里面
- BootClassLoader 单例
- BaseDexClassLoader 下两个子类
- PathClassloader
- DexClassloader
双亲委派机制
如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去加载,每一层次的类加载器都是如此,因此所有的加载请求最终都应传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载
- 好处
- 防止核心类被篡改
断点调试原理
修改断点处的程序指令,加入INT3中断指令。 当前用户态程序发生中断,内核向当前程序发送SIGTRAP信号,当前进程挂起。 调试器进程 与 程序进程 通过 JDWP(Java Debug Wire Protocol,定义了调试器(debugger)和被调试的 Java 虚拟机(target vm)之间的通信协议)进行通信。
Dalvik android 4.4之前
- 内存 dex2opt odex
- Java Object Heap
- activityManager.getLargeMemoryClass() 最大可申请的内存
- Bitmap Memory
- 2.3之前在native,2.3以后在java heap,8.0以后又放到了native
- Native Heap
- Java Object Heap
- 加载 .dex 很多 .class 压缩而成
Art dex2aot
- 预编译技术(aot ahead of time),第一次安装时将字节码转换为机器码
- 优点当然是更快
- 缺点是占用存储变大,安装时间变长
初始化顺序
public class A {
public A(){
System.out.println("我是构造器");
}
{
System.out.println("我是普通块");
}
static{
System.out.println("我是静态块");
}
public static void main(String[] args){ new A(); }
我是静态块
我是普通块
我是构造器
顺序肯定是父子
- static{}块在JVM中会生成一个叫
的方法 - 普通块和构造方法合在一起会生成一个叫
的方法
#Android
基础
本地广播 全局广播的区别
- LocalBroadcastReceiver仅在自己的应用内发送接收广播,也就是只有自己的应用能收到,数据更加安全广播只在这个程序里,而且效率更高
JobScheduler
- 对于满足网络、电量、时间等一定预定条件而触发的任务,那么jobScheduler便是绝佳选择。JobScheduler主要用于在未来某个时间下满足一定条件时触发执行某项任务的情况,那么可以创建一个JobService的子类,重写其onStartJob()方法来实现这个功能。
ContentProvider是如何实现数据共享的
- 使用Uri标识,提供统一的增删改查接口
- 通过Binder进程间通信机制来控制,同时,它又以匿名共享内存作为数据传输媒介,从而提供了一种高效的数据共享方式。
- 将要传输的共享数据抽象成一个游标窗口 Cursor Window,内部包含一块匿名共享内存。通过binder将共享内存MemoryFile的fd传出去。
WatchDog
Watchdog是一个运行在system_server进程的名为”watchdog”的线程,用来检测系统服务是否发生死锁的
Watchdog运作过程,当阻塞时间超过1分钟则触发一次watchdog,会杀死system_server,触发上层重启
实现 Watchdog.Monitor
就能被加入 WatchDog监控,
监控原理就是向被监控的线程的Handler的消息队列中post一个任务
apk安装 发生了什么
- PMS拷贝Apk到非系统应用安装路径 /data/app
- PMS解析Apk的AndroidManifest信息,得到 PackageParser 对象,存到PMS管理中
- 创建应用程序目录 /data/data/包名,提取dex到/data/dalvik-cache中
- ART(android runtime)模式下,dex2oat
- 安装时预编译成机器码
- Dalvik模式 dex 2 odex
- 每次执行时都会将程序的语言由高级语言编译为机器语言
- ART(android runtime)模式下,dex2oat
- 签名校验,进程杀死,发送广播等
android开机启动流程
- bootloader引导
- 启动kernel
- 启动init进程
- 祖先进程
- 启动Android
- init fork 出 zygote 进程
-
zygote fock 出 system_server进程
- system_server 中启动(fork)AMS WindowManager PackageManagerService SurfaceFlinger等服务进程(注意 这些是进程),并将这些服务添加到ServiceManager
- PMS会扫描 /data/system/packages.xml 找到所有安装的apk
- AMS开启后会发出
ACTION_BOOT_COMPLETED
- 启动桌面Launcher
图片库比较
- glide
- 优点
- 库较小
- 易于使用
- 优点
- fresco
- 优点
- bitmap缓存 未解码的图片缓存 文件缓存 native
- 支持先加载小的占位
- 缺点
- 库略大 3M
- 优点
日志打印
mmap + native
- mmap实现文件磁盘地址和进程虚拟地址的映射
- 操作内存相当于操作文件
- 内存不足,进程退出时,操作系统会自动回写文件
- 可以说下直播系统快速捞日志实现
native崩溃收集
- google breakpad
- 监听Signal信号
省电优化
- 看耗电分析
- 使用battery historian查看电量监控数据
- gps cpu占用时间 上下行数据量
- 零散网络请求整合一起 心跳对齐
- 移动网络比wifi耗电高
- Doze(瞌睡)休眠模式
OOM
ActivityManager.getMemoryClass()
可以用来查询当前应用的Heap Size阈值
OOM是剩余的可分配的堆内存不足时触发,本质上是强引用的对象们占用的内存过大,长生命周期引用短生命周期。
- 减小对象的内存占用
- ArrayMap SparseArray(/spɑːs/)(key是int,避免自动装箱) 替代 HashMap
- SparseArray 两个数组,int[] keys,有序,根据二分查找确认key的位置
- ArrayMap 两个数组
int[n] mHashs
(存key的hash值,二分查找),Object[2*n] mValues
- Bitmap的内存占用,采样率+像素格式
- ArrayMap SparseArray(/spɑːs/)(key是int,避免自动装箱) 替代 HashMap
- 对象的复用
- Bitmap 复用 inBitmap属性
- 避免在频繁调用的方法里new对象,例如 onDraw onBind,避免内存抖动,频繁GC
- StringBuilder 代替 +
- 避免内存泄漏
- 匿名内部类导致的泄漏
- Handler导致的泄漏
- Cursor File未及时关闭导致的泄漏
- 擅于使用弱饮用,软引用
- LeakCanary原理
- 使用 WeakReference 来判断对象有没有被回收
- Native内存泄漏
- valgrind
- LeakTracer
- hook 内存分配和释放函数 new/malloc free/delloc
- 每次分配时,记录好内存大小以及调用堆栈
- 内存使用策略优化
onLowMemory
onTrimMemory
利用好- PB优化,混淆优化
- 编码上要控制好每个对象的生命周期
- 利用好AS的memory monitor
- 特别是 Allocation Tracker,可以跳到源码
#####工程中的经验
- 先阐述 OOM 难以解决的原因
- 使用
com.squareup.haha:haha:2.0.3
分析 hprof 内存快照,提高效率 - 线上的OOM 充分挖掘其特征,尽量匹配这些特征尝试复现场景
- 微信的
Matrix
hprof 进行裁剪,自动化分析 - Native泄漏
asan
valgrind
- 自己兜底总比被系统干掉好
- 方向是 逐步提升自动化程度从而提升发现问题的效率,更应该强调预防
ANR(AppNotResponse)
分析 /data/anr/trace.txt
,监控这个文件,监听anr广播,上传分析
分析 /data/system/dropbox
,DropBoxManager CPU使用情况和进程trace文件信息
#####分类
- InputDispatching Timeout 按键或触摸事件无响应
- Service Broadcast ContentProvider Timeout
- 但不会弹窗
表象是主线程的耗时操作引起,但引起耗时的原因很多
- CPU 满负荷,I/O阻塞
- GC 导致的挂起
#####原理 看GitYuan
- system_server埋炸弹
- 目标完成任务后请求拆炸弹
- system_server保留现场,引爆炸弹
#####input超时机制
InputReader
监听/dev/input
读取输入事件- 将事件交给
InputDispatcher
InputDispatcher
分发给窗口,有 in out wait 三个队列记录事件的分发进度- 从 in->out 会检测上一次的 pending 是否超时 (所以第N次输入超时,需要N+1次输入来触发)
- 若未超时,通过Socket通知app进程干活,并 out->wait
有哪些路径会引发ANR? 答应是从埋下定时炸弹到拆炸弹之间的任何一个或多个路径执行慢都会导致ANR(以service为例), 可以是service的生命周期的回调方法(比如onStartCommand)执行慢, 可以是主线程的消息队列存在其他耗时消息让service回调方法迟迟得不到执行, 可以是SP操作执行慢, 可以是system_server进程的binder线程繁忙而导致没有及时收到拆炸弹的指令。 另外ActivityManager线程也可能阻塞,出现的现象就是前台服务执行时间有可能超过10s,但并不会出现ANR。
发生ANR时从trace来看主线程却处于空闲状态或者停留在非耗时代码的原因有哪些?可以是抓取trace过于耗时而错过现场,可以是主线程消息队列堆积大量消息而最后抓取快照一刻只是瞬时状态
如何监测获取anr
- google FileObserver
- 低权限无法监听,获取不到anr文件的问题
- watchdog 自己埋雷监听
- adb shell bugreport
- 通过ActivityManagerService.getProcessesInErrorState()方法获取进程的ANR信息,此方法是通过逆向Bugly时发现的
手Q死锁监控系统
- Android应用发生ANR时,系统会发出SIGQUIT信号给发生ANR进程
- 系统信号捕捉线程信息输出
data/anr/traces.txt
,记录问题产生的虚拟机,线程堆栈相关信息 Thread.dumpStack(); Thread.currentThread().getStackTrace();
没有锁信息
- 系统信号捕捉线程信息输出
- 当监控线程发现卡死时(监控方式跟anr类似也是埋雷),主动向系统发送SIGQuit信号
- native方法
- 每个进程都有个一个 Signal Catcher 线程 循环待命
- 将持有锁 与 等待锁 信息上报给后台
- 判断是否存在锁列表循环(A是否等待锁,找到等待锁的线程B,B是否等待锁,找到等待锁的线程C,C若存在等待锁,等待锁线程是A或B,则死锁)
- 重点案例
- HashMap 链表环
启动速度优化
- 先了解Application的启动过程,
Activity onCreate
过程- 但这些也不可控
- 还有个暖启动
- 评估启动时间
Application onCreate
->onWindowFocusChanged
首帧Display
关键字的logadb shell am start -W com.zq.live/.MainActivity
还可以测量竞品adb shell screenrecord
录屏分析
- 找出耗时函数
- TraceView
startMethodTracing("启动耗时")
stopMethodTracing()
- 开销大,会把所有的线程的堆栈调用情况都记录下来。可能会使不耗时的方法也表现耗时
- Systrace
Trace.beginSection("启动耗时")
Trace.endSection()
- 开销小,只记录区间,
- TraceView
- 解决方案
- 根据业务对第三方库的初始化时机进行分类
- 对于必须启动时初始化的任务,梳理初始化任务的有向无环依赖图(类似gradlew的并行编译),
CountDownLatch
解决同步 A B 都执行完,执行 C 的问题。 - 利用好
idleHandle
- 主页面viewstub 懒加载
- 加载一下第三方库
- 其他经验
- 透明动画
- 拦截back替换成home
- IntentService后台初始化库
- 继承自Service在内部创建HandlerThread
- 统计每个版本的启动耗时,查看版本diff
com.github.zeshaoaaa:LaunchStarter:0.0.1
- 5.0以上默认使用ART,在安装时已将Class.dex转换为oat文件了
- dex-opt过程
- inline
- 减少了压栈出栈
- 编译器可对替换后的上下文进一步优化
- 减少了缺页中断
- quick指令优化
- inline
- dex-opt过程
- 支付宝经验
- 重排apk,将apk中启动要用到的资源排在一起,增大分页cache命中的概率
- GC抑制 也是钩子hook
- 假设为 应用启动后扫描内存中的
libdvm.so
,根据”指令指纹”定位到修改位置,使用到了二进制注入框架 crmulliner/adbi
- 假设为 应用启动后扫描内存中的
touch 事件分发机制
三个关键方法
- dispatchTouchEvent
- 事件分发
- onInterceptTouchEvent
- 事件拦截
- 只有ViewGroup有这个方法,其他两个 View Activity都有
- onTouchEvent
- 事件响应
事件分发图
以ViewGroup为例
public boolean dispatchTouchEvent(MotionEvent ev) {
... // 仅贴出关键代码
// 重点分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 判断值1:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
// 判断值2: 自己是否拦截
// 即如果 自己不拦截 或者 不允许我拦截 则进入里面
ev.setAction(MotionEvent.ACTION_DOWN);
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
// 重点分析2
// 通过for循环,遍历了当前ViewGroup下的所有子View
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
// 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
// 若是,则进入条件判断内部
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 条件判断的内部调用了该View的dispatchTouchEvent()
// 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
// 调用子View的dispatchTouchEvent后是有返回值的
// 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
// 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即把ViewGroup的点击事件拦截掉
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
// 重点分析3
// 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
if (target == null) {
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
// 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
// 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
// 此处需与上面区别:子View的dispatchTouchEvent()
}
...
View 的刷新与绘制流程
#####刷新
- CPU计算数据
- measure layout draw这些
- GPU进一步处理和缓存buffer
- 屏幕Display 每16.6ms来buffer里面读 显示
- 底层是以固定的频率16.6ms发出 VSync 信号的
- 跟 View 刷新有关的操作最终也都会层层走到 ViewRootImpl 中的 scheduleTraversals() 方法里。DecorView的parent也是
ViewRootImpl implement ViewParent
,ViewRootImpl并不是个View - View 的测量、布局、绘制三大流程都是交由 ViewRootImpl 发起,而且还都是在 performTraversals() 方法中发起的,所以这个方法的逻辑很复杂,因为每次都需要根据相应状态判断是否需要三个流程都走,有时可能只需要执行 performDraw() 绘制流程,有时可能只执行 performMeasure() 测量和 performLayout() 布局流程(一般测量和布局流程是一起执行的)。不管哪个流程都会遍历一次 View 树,所以其实界面的绘制是需要遍历很多次的,如果页面层次太过复杂,每一帧需要刷新的 View 又很多时,耗时就会长一点。
所以如果app都用fragment实现的,这个 performTraversals 会有性能问题
FrameDisplayEventReceiver继承自DisplayEventReceiver接收底层的VSync信号开始处理UI过程。VSync信号由SurfaceFlinger实现并定时发送。FrameDisplayEventReceiver收到信号后,调用onVsync方法组织消息发送到主线程处理。这个消息主要内容就是run方法里面的doFrame了,这里mTimestampNanos是信号到来的时间参数。
- 同步屏障消息 Choreographer异步消息标记 确保遍历绘制view tree第一时间执行,页面不掉帧
- 即使再小的 View,如果发起了重绘的请求,那么也需要先层层(不停找parent view)走到 ViewRootImpl 里去
single activity application
优点
- 它消耗更少的资源,能更快地响应页面间切换和交互
缺点
- 但是它也有些短处,在层次深的页面进行现场保存和还原会消耗更多的资源和时间。所以它适合在页面层级结构不深的应用或场合中应用
- ViewRootImpl
- schema跳转
绘制
Mesure阶段
- 预测量 布局窗口 最终测量 onMeasure 会被回调多次的 requestLayout 会从 onMeasure 开始触发
- MesureSepc = 2位的 SpecMode + 30位 SpecSize 组成
- View(包括普通View和ViewGroup)的SpecMode由本View的LayoutParams结合父View的
MeasureSpec
生成- EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize);
- AT_MOST: 子View的大小不得超过SpecSize;
- UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部
- 顶级DecorView的MeasureSpec是由窗口尺寸和自身的LayoutParams
- 然后往下一层层往下让子view测量
- MeasureSpec与上次不一样 或者 forceLayout=true 才会走这个阶段
- 对于ViewGroup及其子类来说,要先完成子View的测量,再进行自身的测量(考虑进padding等)
计算 SpecMode 的代码
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// spec为父View的MeasureSpec
// padding为父View在相应方向的已用尺寸加上父View的padding和子View的margin
// childDimension为子View的LayoutParams的值
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// 现在size的值为父View相应方向上的可用大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// 表示子View的LayoutParams指定了具体大小值(xx dp)
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子View想和父View一样大
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子View想自己决定其尺寸,但不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// 子View指定了具体大小
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 子View想跟父View一样大,但是父View的大小未固定下来
// 所以指定约束子View不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子View想要自己决定尺寸,但不能比父View大
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
. . .
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Layout阶段
用mesure阶段计算的width height (期望值),给自己以及child 定位,left top right bottom
Draw阶段
画背景 画自身 画子view 画滚动条
为什么不能在子线程更新ui
- ViewViewRootImpl是连接WindowManager和DecorView的纽带
- 当Activity对象被创建完毕后,在Activity的performResume阶段,会将DecorView添加到Window中,同时创建new ViewRootImpl对象并和DecorView建立联系
- ViewRootImpl requestLayout 操作会检查线程
卡顿优化
- 衡量卡顿
- 每秒丢帧数
- 分析刷新和绘制原理,从vSync信号开始
- Profile Gpu Rendering
- 同一个柱状图 可以看出 cpu opengl gpu 占的时间
- 水平线代表16.6ms水平线
- 优化步骤
- 过度绘制
- 布局复杂 层级过深
过度绘制
- 概念 在一帧(16.7ms)的时间内,某个像素被绘制多次
- 解决方法
- 减少层级
- 去掉不必要的背景
- canvas.clipRect()来帮助系统识别那些可见的区域
- 用扑克牌案例解释
- ViewStub
获取view高度的时机
- onWindowFocusChanged
- view.post({})
- addOnGlobalLayoutListener
屏幕适配
- px = density
*
dp - density = dpi/160
- dpi = 屏幕的斜边长(px)/屏幕尺寸(inch英尺,比如5寸屏幕)
则 px = sqrt(1920*
1920+1080*
1080)/5 / 160 *
dp
头条方案
- 修改density
handler 底层
- msg.setAsynchronous 屏障消息
Application Activity 启动过程整理
#####进程启动过程
- Process.start 通过 socket 告知 ZygoteInit 进程,想要创建一个进程,ZygoteInit 一直在循坏监听端口请求
- Zygote fork 一个新的进程
- ProcessState startThreadPool 启动一个线程池 ,IPCThreadState joinThreadPool,死循环 talkWithDriver executeCommand 与 binder 驱动不断交互,等待请求
- 创建 ActivityThread ,并创建主线程的 Looper。
#####桌面点击app图标启动主activity过程(当然进程启动过程是其一部分)
- Laucher 调到 Instrumentation 调到 ActivityManagerProxy(运行在Laucher进程) 通过 binder 调到 ActivityManagerService 尝试启动一个activity
- AMS 处理一下 ActivityStack ,通过 ApplicationThreadProxy (运行在System进程) 调用到 ApplicationThread,通知 Laucher 执行 onPause 方法
- 执行完pause后 又通过binder 通知到 AMS。AMS 准备启动目标 Activity,又发现目标Activity 的进程信息 ProcessRecord 是 null,通过 Process.start 创建一个新进程
- 新进程创建好,new ActivityThread ,准备主线程Looper,创建应用进程的binder server ApplicationThread,并通过 binder 传给 AMS,AMS 绑定 ProcessRecord 与 ApplicationThreadProxy
- AMS 记录好 堆栈信息后,通知应用进程 new MainActivity。
- 应用进程
app = mActivity.mInstrumentation.newApplication
创建Application,执行 applicaton.onCreate。ActivityThread 执行 mInstrumentation.newActivity
在哪new activity ,new Activity ,Instrumentation.callActivityOnCreate
binder
4M mmap映射内存
- 死亡通知是为了让Bp端(客户端进程)进能知晓Bn端(服务端进程)的生死情况,当Bn端进程死亡后能通知到Bp端。
- linkToDeath
-
ServiceManager 句柄为0,可以将其看作是个网络访问时域名解析服务器,本质上像是做一层适配
Binder Driver提供的功能:将各进程中的地址和内核地址(binder驱动中的空间)映射起来。
ServiceManager成为BInder守护进程之路.png
Server和Client获得ServiceManager接口之路.png
IPC BInder机制中Client获得Server远程接口过程分析.png
######自定义系统服务
- 边写 stub 与 proxy 也就是服务端与代理端
- ServiceManager.addService
插件化 动态化容器 热修复
欺骗”Android 系统的方式来让宿主正常的加载和运行插件(补丁)中的内容
- tinker和腾讯的qq空间热修复技术 重启生效 正是利用了DexClassLoader的加载机制,将需要替换的类添加到dexElements的前面,这样系统会使用先找到的修复过的类。
- ISPREVERIFIED 问题
- 如果A类再static private 构造函数 overide方法中只引用B类,且A B在同一dex下,A类就会被打上 ISPREVERIFIED 标签
- 如果打上该标签,B类被修复,跑到其他dex去了,A类就会报错
- 如果A类还引入其他dex的C类,则不会被打上该标签
- ISPREVERIFIED 问题
- 美团robust 即时生效 类似 Instant Run 原理,在编译打包阶段对每个函数都插入一段控制逻辑代码(ChangeQuickRedirect)
-
阿里Andfix 修改java方法在native层的函数指针,指向修复后的方法以达到修复目的。
- replugin方案 多classloader 每个插件都会生成一个DexClassLoader,当加载该插件中的类时需要通过对应DexClassLoader加载。这样不同插件的类是隔离的,当不同插件引用了同一个类库的不同版本时,不会出问题。
- Small方案 单classloader 。将插件的DexClassLoader中的pathList合并到主工程的DexClassLoader中。这样做的好处时,可以在不同的插件以及主工程间直接互相调用类和方法,并且可以将不同插件的公共模块抽出来放在一个common插件中直接供其他插件使用。 适合要加载的插件与主工程共同同一个基础库的情况。
- 阿里 atlas
- 类加载机制
- 资源加载
- 编译期隔离
- 合并式
- 动态更新
- 编译期 将所有的menifest打包进去
- diff merge做更新
- 类加载机制
- 美团插件化 Hydra
######资源加载
- 合并式
- 相互访问,但会引入冲突
- hook Resource
- 通过反射调用AssetManager
中到的addAssetPath方法就可以将特定路径的资源加载到系统内存中使用
- getLayout getDrawable getString 间接都是调用AssetManager的方法,然后再向系统读取资源
- 步骤
- 创建一个新的AssetManager对象,将插件和宿主的资源通过
addAssetPath
都塞进去 - 通过新的AssetManager对象创建Resources对象
- 通过反射替换 ContextImpl 中的 mResources 变量,LoadApk变量里的mResources对象
- 但modlue间会存在id冲突
- 如何解决冲突 分号段
- 那么号段之前是怎么分配的 Android源码目录/tools/aapt app module 0x7f打头
- 解决方案
- 一个是 修改 android sdk 内 aapt 源码,定制aapt
- VirtualApk 在资源编译任务完成后,重排id
- 收集插件资源 收集宿主资源 重设 R.java Resources.arsc 的id
- 创建一个新的AssetManager对象,将插件和宿主的资源通过
- 独立式
- 不冲突,但相互访问不方便
AssetManager
- 最终是调用了AssetManager类的addAssetPath方法传入各种资源目录来对其进行加载
######四大组件
- Activity
需要解决以下两个问题:
- 插件中的Activity没有在AndroidManifest中注册,如何绕过检测。
- 占坑
- 如何构造Activity实例,同步生命周期
- 类似组合的方式
- 插件中的Activity没有在AndroidManifest中注册,如何绕过检测。
- 广播
- 静态转动态
- Service 与 ContentProvider
- 使用一个总的,然后做分发
Replugin
- 通过反射替换 mPackageInfo.mClassLoader为ReluginClassLoader。尽量少的hook
- 使用编译插件插入一堆activity坑位,匹配合适的坑位
- 静态广播转动态
组件化 模块化
- 为什么组件化 好处
- 解耦 复用 易维护 灵活拔插 方便产品迭代功能
- 单独编译 并行开发 提高效率
- 容器化,不用每个模块再实现基础能力,让业务方只需要关注自己的业务开发
- 提供的组件方法尽量简单 好用,
- 也是插件化的基础
- 组件化路由框架
- Arouter 看注解那节
- apt(Annotation processing tool) 注解处理器绑定 path 与 组件 javapoet生成新的Java文件
- 存着类,需要时构造对象,懒加载
- Arouter 看注解那节
- AOP(Aspect-Oritented Programming)面向切面编程
- APT(Annotation Processing Tool)
- 代表框架 Dagger2, ButterKnife, EventBus3 、DBFlow、AndroidAnnotatio
- squareup的javapoet,用建造者的模式构建出任何你想要的源代码
- AspectJ
- 编译期间直接修改源代码生成class
- Javassist ASM ASM快 javassit易用
- 后处理阶段transform(编程成class 还未打包成dex时)直接修改class了
- 做一些代码规范检查 做一些内联优化 做一些耗时统计
- APT(Annotation Processing Tool)
- 结构自底向上
- commonsdk
- 网络 图片 日志 统计 权限 基本ui 工具类
- commoncore
- 登录账号 定位 升级
- commonservice
- module A module B
- commonsdk
- Google 动态化框架 App bundle
- 仅限于通过 Google Play 发布的应用,(Google进一步巩固自身生态)
- 6大设计原则
- 单一职责
- 开放关闭
- 里氏替换
- 依赖倒置
- 接口隔离
- 最少知识
点击build发生了啥
- 计算依赖 Preparation of dependecies。检测所有依赖的library是否ready。
- 打包资源 merging resources and processing manifest。
- 使用aapt(android asset package tool) 生成 R文件和Resources.arsc文件
- 编译,处理注解,源码编译成class字节码
- 后处理,Postprocessing。“transform”前缀的task在这个阶段被处理,包括 javassist
- 打包签名发布。生成aar 或 apk
- zipalign 对apk进行对齐处理,运行时节省内存
静态代码检查工具
- 缺陷模式匹配
- 模型检查
- 基于状态机
- 类型推断
- 数据流分析
进程保活
- 进程优先级 数值越大优先级越低 系统相关的进程 oom_adj 都是负的,可以
cat /proc/12345(pid)/oom_adj
查看优先级- 前台进程 (Foreground process)
- 正在交互的activitu
- bind方式启动的service
- service startForground,并绑定了 Notification
- 可见进程 (Visible process)
- service startForground
- 服务进程 (Service process)
- startService
- 后台进程 (Background process)
- stop的Activity
- 空进程 (Empty process)
- 为了缓存保留的
- 前台进程 (Foreground process)
- 先说系统为什么杀
- 内存不足 杀
- 锁屏省电 杀
- 内存清理软件 杀
- 会把oom_adj大于0的(0是前台进程)全杀了
- 再看怎么保
- service.startForeground 能从 服务进程 变为 可见进程
- 监听锁屏 启动1像素
- 手机白名单(定制rom 引导用户)
- onStartCommond
return START_STICKY
自己重启service - 双进程守护
apk包大小优化
- 做好版本管理
- 不编译废弃功能
- 查看哪块大了
- 压缩webp
- 动态加载so
- 混淆class resource.arsc清单
- AndroidResGuard 是在 apk 生成后干这个事的
- R.drawable.logo –> 整型 –> 在resource.asrc中 对应资源路径
- 读取原映射
- 写入
- AndroidResGuard 是在 apk 生成后干这个事的
- 7zip极致压缩
- 压缩算法不一样
- 压缩速度慢
- AabResGuard
- 针对android app bundle
- 资源清单文件为 resources.pb
0x7ffff0112f - drawable/cmx path:res/drawable-xxhdpi/cmx.png
- 去除无用字符串
- facebook的redex
- 联想到支付宝的apk重排
JetPack
官方终于开始管理这个混乱的Android开发环境,推出 Architecture Component,包括了大家一直期待的O(R)M库 Room,和生命周期管理工具ViewModel/LiveData
LiveData是一个持有Activity、Fragment生命周期的数据容器。当数据源发生变化的时候,通知它的观察者更新UI界面。同时它只会通知处于Active状态的观察者更新界面,如果某个观察者的状态处于Paused或Destroyed时那么它将不会收到通知。所以不用担心内存泄漏问题
Room : 官方ORM库 对代码的数据逻辑和UI界面深层解耦,实现数据驱动型的ui 代码量缩减40% bug率降低
架构师需要协调不同的团队,建立工具和框架等开发规范,监督由需求到上线整个流程,使项目尽量效率高、开发容易、维护方便、升级简单等。
ViewModel将视图和逻辑进行了分离。Activity或者Fragment只负责UI显示部分。具体的网络请求或者数据库操作则有ViewModel负责。这样避免了视图的臃肿和代码的耦合。
项目
直播
美团准备
美团已经成为基础设施
头条准备
- Scene
- 轻量级的导航框架 和 页面拆分组件库
音视频
- OpenSL ES
- 概念
- 一种针对嵌入式系统特别优化过的硬件音频加速API,无授权费并且可以跨平台使用。
- 高性能 标准化 低延迟
- AAudio是在Android O(android 8.0)版本中引入的全新Android C API
- 跨平台方便部署
- 原理
- 概念
- OpenGL
- OpenGL与OpenGL ES的主要区别,在于OpenGL ES主要针对嵌入式设备使用
- 难道不能直接将数据从CPU跨到GPU处理?为什么要多此一举,出现OpenGL这个框架?
- 数据饥饿
#管理维度:
- 招聘的时候如何识别一个人
- 判断简历是否真实
- 判断具体的技术深度 广度 视野 技术习惯 如何
- 不同级别的人标准可能不一样 * 判断逻辑思维 表达能力是否清晰 * 考虑年龄 发展等 问问自己,你真的愿意跟这个人一起战斗么?
- 作为一个10个人的Android team Leader 最应该关注哪些事
- 技术维度上 保证项目工期和质量
- 评估技术方案 代码结构 考虑可维护性 可扩展性等
- 崩溃率,anr,oom等性能指标
- 业务关键技术点的指标数据,如伴奏成功播放率,首帧动画
- 项目工期进度,完成率
- 管理维度上
- 明确目标
- 多同步,多沟通,个人目标与团队目标,不冲突
- 排期是否合理,是否愿意承担更有挑战的任务
- 因地制宜,因人施教,结合兴趣分配任务
- 多同步,多沟通,个人目标与团队目标,不冲突
- 技术规划 团队规划
- 建立信心
- 首当其冲
- 形成规矩
- 千行代码bug率,固定工期bug率
- 增强模块归属感
- 营造氛围
- 营造良好的技术氛围 鼓励OKR自定义目标
- 多换位思考,考虑员工情绪,结合员工自身发展角度考虑问题
- 明确目标
- OKR考核
- 制定目标
- 拆解目标
- 目标由个人提出,再由组织确定,自下而上,再自上而下
- 要求员工本身有创造性,主观能动性,对员工素养要求要高
- 同时换位思考,结合项目需求帮助员工进行职业规划
- 目标要具体可以衡量
- 比如不能是我要把伴奏播放率做好
- 而是目前伴奏播放的成功率是多少,我要提高到多少,目前已有哪些可行的方案
- 明确目标结果
- 结果进行量化
- 制定目标
- 技术维度上 保证项目工期和质量
-
遇到的最大的挑战
本身这个创业过程就很有挑战,唱歌体验不好,伴奏播放失败率高(多cdn 缓存 代理下载),ios人力紧缺 -
缺点 直男
- 你来我们团队能发挥什么样的作用
- 首先肯定能做好本职工作
- 能促进团队一起成长,能使组内的技术氛围更加浓厚
- 年轻,创造力
-
职业发展的瓶颈
- 如何向上管理,向下管理
- 向上管理
- 及时,定期总结工作进展、数据、部门问题、行业关键信息,以清晰文档的方式递交上级。并同时附上下阶段计划及问题解决办法
- 换位思考,忧领导之所忧,替上级解决难题
- 领导有错,合理分析,有理有据地适时反馈给领导
- 向上管理
HR面试
- 可接受的薪资底线
- 应聘的岗位区间
- 分析自己的能力及市场薪资行情
- 了解薪资构成和福利
总包140
- 感谢对方态度真诚
- 表面自己的底线 解释合理的原因 解释不能接受的原因 岗位匹配度高 值这个钱 创造价值 富有挑战工作 take几个核心功能性 业务线
- 请求hr帮我申请 麻烦hr帮我申请 侧面表达自己很多人争取
谁先松口谁先输 我考虑一下 大大方方地谈
我的缺点 性子容易急 一两年前 告诉自己不要生气 自我反思 急于求成
还有管理方面也看看,一般管理就是定目标,做技术规划,团队规划啊 目标怎么制定,团队人员如何培养
不会的东西不要直接说不会,真不会,从逻辑上根据自己的信息去推理
主要你来这边聊一定要表现的特别 让人觉得你是个有想法,有行动力的人 清楚知道自己的优缺点,未来的努力方向
blog comments powered by Disqus