1\. C->S:SYN,seq=x(你能听到吗?)2\. S->C:SYN,seq=y,ack=x+1(我能听到,你能听到吗?)3\. C->S:ACK,seq=x+1,ack=y+1(我能听到,开始吧)两方都要能确保:我说的话,你能听到;你说的话,我能听到。所以需要三次握手
1\. C->S:FIN,seq=p(我说完了)2\. S->C:ACK,ack=p+1(我知道了,等一下,我可能还没说完)3\. S->C:FIN,seq=q,ACK,ack=p+1(我也说完了)4\. C->S:ACK,ack=q+1(我知道了,结束吧)S 收到 C 结束的消息后 S 可能还没说完,没法立即回复结束标示,只能等说完后再告诉 C :我说完了
Exception 和 Error 都继承自 Throwable
Error 大部分是指不可恢复的错误状态,比如 OOM,所以也不需要捕获
Exception 分为 CheckedException 和 UnCheckedException
CheckedException:必须显式捕获,受编译器检查,比如 io 操作
UnCheckedException:不用显示捕获,比如空指针、数组越界等
ArrayList
基于数组实现,查找快:o(1),增删慢:o(n)
初始容量为10,扩容通过 System.arrayCopy 方法
LinkedList
基于双向链表实现,查找慢:o(n),增删快:o(1)
封装了队列和栈的调用
HashMap(允许 key/value 为 null)
基于数组和单向链表实现,数组是 HashMap 的主体;链表是为解决哈希冲突而存在的,存放的是key和value结合的实体
数组索引通过 key.hashCode(还会二次 hash) 得到,在链表上通过 key.equals 索引
哈希冲突落在同一个桶中时,直接放在链表头部(java1.8后放到尾部)
JAVA 8 中链表数量大于 8 时会转为红黑树存储,查找时间由 O(n) 变为 O(logn)
数组长度总是2的n次方:这样就能通过位运算实现取余,从而让 index 能落在数组长度范围内
加载因子(默认0.75)表示添加到多少填充比时进行扩容,填充比大:链表较长,查找慢;填充比小:链表短,查找快
扩容时直接创建原数组两倍的长度,然后将原有对象再进行hash找到新的index,重新放
HashTable(不允许 key/value 为 null)
数据结构和 HashMap 一样
线程安全
HashSet
基于 HashMap 实现,元素就是 HashMap 的 key,Value 传入了一个固定值
ArrayMap
基于两个数组实现,一个存放 hash;一个存放键值对
存放 hash 的数组是有序的,查找时使用二分法查找
发生哈希冲突时键值对数组里连续存放,查找时也是通过 key.equals索引,找不到时先向后再向前遍历相同hash值的键值对数组
扩容时不像 HashMap 直接 double,内存利用率高;也不需要重建哈希表,只需要调用 system.arraycopy 数组拷贝,性能较高
不适合存大量数据(1000以下),因为数据量大的时候二分查找相比红黑树会慢很多
SparseArray
基于 ArrayMap,key 只能是特定类型
ConcurrentHashMap
数据结构跟 HashMap 一样,还是数组加链表
采用 segment 分段锁技术,不像 HashTable 无脑直接同步 put 和 get 操作
get 操作没有加锁,因为 value 用 volatile 修饰来保证可见行,性能很高
java1.8 后去除分段锁,采用 CAS 乐观锁加 synchronized 来实现
只能用来修饰变量,适用修饰可能被多线程同时访问的变量
相当于轻量级的 synchronized,volatitle 能保证有序性(禁用指令重排序)、可见性
变量位于主内存中,每个线程还有自己的工作内存,变量在自己线程的工作内存中有份拷贝,线程直接操作的是这个拷贝
被 volatile 修饰的变量改变后会立即同步到主内存,保持变量的可见性
双重检查单例,为什么要加 violate?
volatile想要解决的问题是,在另一个线程中想要使用instance,发现instance!=null,但是实际上instance还未初始化完毕这个问题。将instance = newInstance();拆分为3句话是。1.分配内存2.初始化3.将instance指向分配的内存空间,volatile可以禁止指令重排序,确保先执行2,后执行3
1995 年 GoF(四人组)出了一本设计模式的书,收录了 23 种设计模式,树立设计模式里程碑,也叫:GoF 设计模式
创建型(5):描述怎么创建对象
1.单例模式
2.原型模式:对象的拷贝
3.建造者模式
4.工厂模式:建立一个工厂方法来制造新的对象
5.抽象工厂模式:
结构型(7):描述如何将类或对象按某种规则组成更大的结构
1.桥接模式:对于两个或以上纬度独立变化的场景,将抽象与具体实现分离,实例:用不同颜色画不同形状
2.外观模式:对外有一个统一接口,外部不用关心内部子系统的具体实现,这是"迪米特原则"的典型应用
3.适配器模式:改变类的接口,使原本由于接口不匹配而无法一起工作的两个类能够在一工作,实例:RecycleView 的 Adapter 不管什么类型的 View 都返回 ViewHolder
4.代理模式:由代理对象控制对原对象的引用,包括静态代理和动态代理
5.组合模式:将对象组成树形结构,用于对单个对象和组合对象的使用具有一致性,实例:ViewGroup
6.装饰模式:对对象包装一层,动态的增加一些额外功能,实例:ContextWrapper 包装 Context
7.享元模式:复用对象,实例:java 的常量池(比如 String),线程池,Message.obtain 等
行为型(11):描述类或对象之间怎么相互协作,怎样分配指责
1.观察者模式:一对多依赖关系,多个观察者可以同时监听某一个对象,实例:jetpack 的 lifeCycle 添加生命周期观察者
2.中介者模式:定义一个中介对象封装一系列对象的交互,解耦这些对象,实例:MVP 的 P
3.访问者模式:将作用于某数据结构中各元素的操作分离出来封装成独立的类,对这些元素添加新的操作,但不改变原数据结构,实例:asm 中的 classVisitor 中再分别对类注解、变量、方法等进行处理
4.状态模式:行为由状态决定,不同状态下由不同行为,与策略模式类似,实例:不同状态下有同一种操作的不同行为的子类实现
5.命令模式:将一个请求封装为一个对象发出,交给别的对象去处理请求,实例:Handler 发送定义好的消息事件
6.策略模式:将一系列的算法封装起来,方便替换,实例:动画的时间插值器
7.责任链模式:让多个对象都有机会处理一个事件,实例:View 事件传递机制
8.备忘录模式:保存对象之前的状态,方便后面恢复
9.迭代器模式:提供一种方法遍历容器中的元素,而不需要暴露该对象的内部表示,实例:集合的迭代器
10.解释器模式:多次出现的问题有一定规律,就可以归纳成一种简单的语言来解释,实例:AndroidManifest 文件、GLES 着色器语言
11.模版方法模式:定义一套固定步骤,方便直接执行,实例:AsyncTask
线程私有:
1.程序计数器:记录正在执行的字节码指令地址,若正在执行 Native 方法则为空
2.虚拟机栈:执行方法时把方法所需数据存为一个栈帧入栈,执行完后出栈
3.本地方法栈:同虚拟机栈,但是针对的是 Native 方法
线程共享:
1.堆:存储 Java 实例,GC 主要区域,分代收集 GC 方法会吧堆划分为新生代、老年代
2.方法区:存储类信息,常量池,静态变量等数据
回收区域:只针对堆、方法区;线程私有区域数据会随线程结束销毁,不用回收
回收类型:
1.堆中的对象:分代收集 GC 方法会吧堆划分为新生代、老年代。新生代:新建小对象会进入新生代;通过复制算法回收对象;老年代:新建大对象及老对象会进入老年代;通过标记-清除算法回收对象。
2.方法区中的类信息、常量池
判断一个对象是否可被回收:
1.引用计数法:有循环引用的缺点
2.可达性分析法:从 GC ROOT 开始搜索,不可达的对象都是可以被回收的。其中 GC ROOT 包括虚拟机栈/本地方法栈中引用的对象、方法区中常量/静态变量引用的对象。
Minor GC(Young GC):即新生代(分为一个 Eden 区和两个 Survivor 区)的垃圾回收
Eden 区无用对象被回收,存活对象会移到 Survivor 区
Survivor 区的存活对象会被复制到另一个 Survivor 区,复制次数也记做年龄,年龄足够大时(15)会移到老年代
如果 Survivor 区已满,则存活对象会被提前移动到老年代(过早提升),如果老年代也无法容纳,则会触发 Full GC(提升失败)
老年代的对象可能引用新生代对象,所以这个引用会被作为 GC Roots
Major GC:通常是跟 Full GC 等价的,回收整个堆
Full GC:回收整个堆,包括新生代和老年代
当要在老年代分配空间但无法容纳时触发
当主动调用 System.gc 时触发
类的生命周期:1.加载;2.验证;3.准备;4.解析;5.初始化;6.使用;7.卸载
类加载过程:1.加载:获取类的二进制字节流;生成方法区的运行时存储结构;在内存中生成 Class 对象 2.验证:确保该 Class 字节流符合虚拟机要求 3.准备:初始化静态变量 4.解析:将常量池的符号引用替换为直接引用 5.初始化:执行静态块代码、类变量赋值
类加载时机:1.实例化对象 2.调用类的静态方法 3.调用类的静态变量(放入常量池的常量除外)
类加载器:负责加载 class 文件 1.引导类加载器 - 没有父类加载器 2.拓展类加载器 - 继承自引导类加载器 3.系统类加载器 - 继承自拓展类加载器
双亲委托模型:
当要加载一个 class 时,会先逐层向上让父加载器先加载,加载失败才会自己加载
为什么叫双亲?不考虑自定义加载器,系统类加载器需要网上询问两层,所以叫双亲
判断是否是同一个类时,除了类信息,还必须时同一个类加载器
优点:防止重复加载,父加载器加载过了就没必要加载了;安全,防止篡改核心库类
standard 标准模式
singleTop 栈顶复用模式,适用于推送点击消息界面
singleTask 栈内复用模式,适用于 App 首页
singleInstance 单例模式,单独位于一个任务栈中,适用于拨打电话界面
细节:
taskAffinity:任务相关性,用于指定任务栈名称,默认为应用包名
allowTaskReparenting:允许转移任务栈
ViewRoot 的 performTraversals 方法调用触发开始 View 的绘制,然后会依次调用:
performMeasure:遍历 View 的 measure 测量尺寸
performLayout:遍历 View 的 layout 确定位置
performDraw:遍历 View 的 draw 绘制
View 动画:
作用对象是 View,可用 xml 定义,建议 xml 实现比较易读
支持四种效果:平移、缩放、旋转、透明度
帧动画:
通过 AnimationDrawable 实现,容易 OOM
属性动画:
可作用于任何对象,可用 xml 定义,Android 3 引入,建议代码实现比较灵活
包括 ObjectAnimator、ValuetAnimator、AnimatorSet
时间插值器:根据时间流逝的百分比计算当前属性改变的百分比,系统预置匀速、加速、减速等插值器
类型估值器:根据当前属性改变的百分比计算改变后的属性值,系统预置整型、浮点、色值等类型估值器
使用注意事项:避免使用帧动画,容易OOM;界面销毁时停止动画,避免内存泄漏;开启硬件加速,提高动画流畅性
硬件加速原理:将 cpu 一部分工作分担给 gpu ,使用 gpu 完成绘制工作;从工作分摊和绘制机制两个方面优化了绘制速度
一个 MotionEvent 产生后,按 Activity -> Window -> decorView -> View 顺序传递,View 传递过程就是事件分发,主要依赖三个方法:
dispatchTouchEvent:用于分发事件,只要接受到点击事件就会被调用,返回结果表示是否消耗了当前事件
onInterceptTouchEvent:用于判断是否拦截事件,当 ViewGroup 确定要拦截事件后,该事件序列都不会再触发调用此 ViewGroup 的 onIntercept
onTouchEvent:用于处理事件,返回结果表示是否处理了当前事件,未处理则传递给父容器处理
细节:
一个事件序列只能被一个 View 拦截且消耗
View 没有 onIntercept 方法,直接调用 onTouchEvent 处理
OnTouchListener 优先级比 OnTouchEvent 高,onClickListener 优先级最低
requestDisallowInterceptTouchEvent 可以屏蔽父容器 onIntercept 方法的调用
Intent extras、Bundle:要求传递数据能被序列化,实现 Parcelable、Serializable ,适用于四大组件通信
文件共享:适用于交换简单的数据实时性不高的场景
AIDL:AIDL 接口实质上是系统提供给我们可以方便实现 Binder 的工具
Android Interface Definition Language,可实现跨进程调用方法
服务端:将暴漏给客户端的接口声明在 AIDL 文件中,创建 Service 实现 AIDL 接口并监听客户端连接请求
客户端:绑定服务端 Service ,绑定成功后拿到服务端 Binder 对象转为 AIDL 接口调用
RemoteCallbackList 实现跨进程接口监听,同个 Binder 对象做 key 存储客户端注册的 listener
监听 Binder 断开:1.Binder.linkToDeath 设置死亡代理;2. onServiceDisconnected 回调
Messenger:基于 AIDL 实现,服务端串行处理,主要用于传递消息,适用于低并发一对多通信
ContentProvider:基于 Binder 实现,适用于一对多进程间数据共享
Socket:TCP、UDP,适用于网络数据交换
Dalvik
谷歌设计专用于 Android 平台的 Java 虚拟机,可直接运行 .dex 文件,适合内存和处理速度有限的系统
JVM 指令集是基于栈的;Dalvik 指令集是基于寄存器的,代码执行效率更优
ART
Dalvik 每次运行都要将字节码转换成机器码;ART 在应用安装时就会转换成机器码,执行速度更快
ART 存储机器码占用空间更大,空间换时间
内存问题
内存泄漏
内存抖动:频繁创建临时对象
Bitmap 大内存:规避位图超标
代码质量:intdef 代替枚举,使用 SparseArray 代替 HashMap
检测工具
MAT(Memory Analysis Tools) ,可分析 Java 堆数据,可查看实例占用空间、引用关系等
Android Studio 自带的 Profiler
LeakCanary:通过弱引用和引用队列监控对象是否被回收,比如 Activity 销毁时开始监控此对象,检测到未被回收则主动 gc ,然后继续监控
1.静态变量、单例强引跟生命周期相关的数据或资源,包括 EventBus
2.游标、IO 流等资源忘记主动释放
3.界面相关动画在界面销毁时及时暂停
4.内部类持有外部类引用导致的内存泄漏
handler 内部类内存泄漏规避:1.使用静态内部类+弱引用 2.界面销毁时清空消息队列
检测:Android Studio Profiler
anr 分类
主线程 5s 内没有处理完输入事件
service 阻塞 20s
前台广播阻塞 10s 或后台广告阻塞 20s
ContentProvider publish 在 20s 内没有处理完
anr 发生过程
1.捕获到 anr,发送 linux 信号量 3
2.进程接受到信号量将 anr 信息写入 data/anr/traces.txt 文件
3.Log 打印 anr 信息
4.进程进入 anr 状态,弹出 anr 提示框
监控 anr
1.Android 5.0 以下监听 traces.txt 文件写入
2.每隔 5s 向主线程发送消息判断主线程是否阻塞
分析 anr
查看 cpu 负载是否是 cpu 资源紧张导致
查看堆栈看是否是我们的代码耗时过长
避免 anr
主线程中不要做耗时操作,注意使用 IntentService
降低子线程优先级,让主线程可以更多的获取到 cpu 资源
热修复原理:
Native Hook(AndFix):直接在 native 层进行方法的结构体信息对换
分包(QFix):插入新 dex 到 dexElements[],利用 ClassLoader 通过遍历 dexElements[] 来 findClass 的特性
Java Hook(Robust):hook 每个方法,在每个方法里埋好准备替换的逻辑
插件化:DexClassLoader 动态加载,四大组件未注册问题通过 hook AMS、Instrumentation 等解决,VirtualAPK 源码分析
组件化:ARoute 路由实现:通过 APT 解析 @Route 等注解,结合 JavaPoet 生成路由表,即路由与 Activity 的映射关系
进程优先级:1.前台进程 ;2.可见进程;3.服务进程;4.后台进程;5.空进程
进程被 kill 场景:1.切到后台内存不足时被杀;2.切到后台厂商省电机制杀死;3.用户主动清理
保活方式:
1.Activity 提权:挂一个 1像素 Activity 将进程优先级提高到前台进程
2.Service 提权:启动一个前台服务(API>18会有正在运行通知栏)
3.广播拉活
4.Service 拉活
5.JobScheduler 定时任务拉活
6.双进程拉活
视频播放原理:(mp4、flv)-> 解封装 -> (mp3/aac、h264/h265)-> 解码 -> (pcm、yuv)-> 音视频同步 -> 渲染播放
音视频同步:
选择参考时钟源:音频时间戳、视频时间戳和外部时间三者选择一个作为参考时钟源(一般选择音频,因为人对音频更敏感,ijk 默认也是音频)
通过等待或丢帧将视频流与参考时钟源对齐,实现同步
IjkPlayer 原理
集成了 MediaPlayer、ExoPlayer 和 IjkPlayer 三种实现,其中 IjkPlayer 基于 FFmpeg 的 ffplay
音频输出方式:AudioTrack、OpenSL ES;视频输出方式:NativeWindow、OpenGL ES
LiveData 感知声明周期原理:像 Glide 一样给界面添加了无视图的 Fragment
ViewModel 界面旋转短暂销毁重建时保存数据原理:
ViewModel 保存在 ViewModelStore 中
当 Activity 配置变更销毁时,系统会调用 onRetainNonConfigurationInstance 保存 NonConfigurationInstances,而 ViewModel 就保存在 NonConfigurationInstances 中
重建时 onCreate 方法通过 getLastNonConfigurationInstance 方法获取到 NonConfigurationInstances,从而获取到 ViewModelStore
JetPack 与 MVVM:
先了解下 MVP:Model:处理数据;View:控制视图;Presenter:分离 Activity 和 Model
再看 MVVM:Model:处理获取保存数据;View:控制视图;ViewModel:数据容器
使用 Jetpack 组件架构的 LiveData、ViewModel 可以便捷的实现 MVVM
本文来源于Android 面试官 ,作者Ahab。