文章目录
前言
随着安卓技术的发展,面试难度也逐步提高,从两年前面试初中级岗位都会问一些三方框架原理,所以这次特意把一些常用的原理给过了一遍,本篇依旧是对常见框架的实现原理进行高度概括以及亮点,实际理解建议结合源码理解
资料参考:
LeakCanary
- 功能:检测内存泄漏
- 内存简单划分
- 栈(基本类型)(虚拟机栈、本地方法栈)
- 堆(new分配的对象)
- 方法区(静态变量、class对象、常量池)
- 本地方法区(Native 存储与C语言交互)
- 内存泄漏原因
- 对象不再使用
- 有些对象存在有限的生命周期,但持有长周期对象
- 常见例子
- 单例泄漏(可使用applicationContext)
- 非静态内部类创建出的静态实例对象
- Handler泄漏(Activity创建的匿名Handler,Msg - Handler - Activity,可以使用静态内部类和弱引用解决)
- Webview泄漏(渲染页面产生的堆内存,可以单独进程,退出杀进程解决)
- 线程(匿名线程,持有外部引用)
- 别人的详细源码分析
- 判断是否泄漏
创建弱引用并关联到一个
ReferenceQueue
,当软/弱引用创建时关联了ReferenceQueue,则在对象被回收时,会将Reference 对象添加到 ReferenceQueue中,以activity为例,则是在生命周期onDestroy时对activity对象进行检查
- 流程
- 在Activity onDestroy 时,将对象放在一个weekRef 中
- 将这个weekRef 关联到 ReferenceQueue
- 查看ReferenceQueue是否存在这个引用
- 如果泄漏了则dump出 heap信息,然后进行分析
- 源码大概流程
- 通过lifecycleCallback 监听,在Activity的onDestroy监听(创建refWatcher- ActivityRefWatcher,新版的是ObjectWatcher)
- 通过watch监听方法,在一个map里面,创建key、weakRef(关联key和ReferenceQueue)
- onDestroy 时,通过ensureGone方法,进行状态判断,如果不是Debug/已经销毁 的状态,则调用一次GC,再次判断是否泄漏,如果还是不存在,则dump出内存进行分析
- 分析过程(checkForLeak):通过库导出的hprof,解析成一个snapshot对象,然后通过优化GCRoots、找出泄漏对象的最短距离和一系列处理(通过在快照中找到之前创建的weekRef 对象可以快速查找,然后遍历对象实例,再通过key值找到该对象),最后如果找到了该泄漏的对象则
插件化换肤框架
- 核心思路:我们设置的布局本质上解析XML -> 调用
LayoutInflater#createViewFromTag
-> 调用Factory/Factory2 创建一个View;通过设置一个自定义的Factory2,系统创建View的时候我们就可以拿到相关的信息,然后通过 观察者 模式在需要的时候进行换肤;换肤的原理是根据 Resource 几个相关的API实现的; - 核心步骤
- 实现
Factory2
,hookView的创建流程(这里可以把创建的View替换成我们自己的实现),创建View的时候 获取/实现 换肤相关的操作,保存到一个集合- 使用一个管理类,使用
Application.ActivityLifecycleCallbacks
注册监听生命周期- 通知换肤操作:利用 resId(一堆数字) 获取主包的资源的名字(R.color.xxx),然后通过这个名字去获取插件包的资源
- 对各个插件的Resource 进行管理
- 核心API
- 实现
LayoutInflater#Factory2
接口,设置到LayoutInflater
,获取到加载View的信息Application.ActivityLifecycleCallbacks
注册监听生命周期Resource
类getResourceEntryName(int resid)
、getResourceTypeName(int resid)
、getIdentifier(String name, String defType, String defPackage)
,进行资源的替换- 加载插件包的Resource代码
AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPkgPath);
Resources superRes = mAppContext.getResources();
new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
EventBus
- 功能:事件总线
- 关键点
- 注册
- 事件发送和分发
- 线程调度
- 默认是一条总线,可以根据需要新建多条
- 关键类和参数
EventBus 类
public class EventBus {
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType; // 根据事件类型保存的观察者们
private final Map<Object, List<Class<?>>> typesBySubscriber; // 保存了观察者所观察的事件类型列表(后面方法反注册)
private final Map<Class<?>, Object> stickyEvents; // 黏性事件
private final ThreadLocal<PostingThreadState> currentPostingThreadState // 通过ThreadLocal 保存了当前线程事件分发的队列的状态;
// 线程调度,分别对应ThreadMode的几个类型
private final Poster mainThreadPoster; // 主线程
private final BackgroundPoster backgroundPoster; // 后台Poster,会依次取完所有消息进行分发使用线程池执行
private final AsyncPoster asyncPoster; // 每次只取一消息,使用线程池执行
}
注解和线程类型
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
boolean sticky() default false;
int priority() default 0;
}
public enum ThreadMode {
POSTING, // 直接调用
MAIN,
MAIN_ORDERED,
BACKGROUND,
ASYNC
}
注册
EventBus.getDefault().register(this)
这里主要做两件事:
- 通过反射拿到当前观察者的Class信息,得到一个封装了 事件信息、对应的观察方法的SubscriberMethod列表;这里会通过一个Map缓存已经查找过的类型,加快效率
- 遍历上面获取的SubscriberMethod列表,把当前的观察者对象和事件一一关联起来,主要是将观察者和SubscriberMethod封装成一个Subscription对象,方便后面反射调用;还有黏性事件的处理
简要概括第一步是查找,第二步是注册,注册的相关逻辑在EventBus#subscribe
方法,其中主要逻辑为
- 判断是否注册
- 根据优先级添加 【事件 - 观察者list】
- 关联 【当前观察者 - 事件 map】
- 黏性事件的检测和分发
分发
主要流程
- 通过
ThreadLocal
获取当前线程的分发队列- 判断是否正在分发消息,进行消息分发
- 经过一系列的判断(父类查找观察者等),最终调用到
EventBus#postToSubscription
,根据之前封装Subscription
,取出ThreadMode类型,调用不同的poster 进行消息分发
线程切换
- 主要是通过三种不同类型的Poster进行反射调用,其中BACKGROUND和 ASYNC 都是在线程池中执行,不同的是ASYNC是每次都会新起一个单独的线程,BACKGROUND是每次会取完所有的消息;
- 主线程是通过一个hander实现的,有个细节是他会判断每次调用消息分发的时间,如果超过10ms,会发送一个空消息避免对主线程的过度占用
与广播的区别
- EventBus 不依赖Context,使用方便但不支持跨进程
- EventBus 使用方便
ButterKnife
大概原理
- 主要源码构成
- annotation : 定义各个注解
- compiler : 解析注解 -> 判断要生成的类 -> 利用APT和AST 解析注解生成对应的类
- api :根据生成的类的命名规则 对外封装一层统一的api进行调用
主要还是通过APT 编译时注解动态生成绑定类(持有activity) 再通过反射去获取到生成类进行绑定
OKHTTP
Retrofit
ARouter
Glide
概览
- 文章参考
- 核心
- 生命周期检测:注入无UI的fragment,检测activity生命周期,控制图片的加载、停止、销毁
- 加载:使用原生的HTTP请求库(4.4 之后底层改为OKHTTP实现)
- 缓存:使用三级缓存(LRU算法和LinkedHashMap数据结构)
流程与缓存机制参考
- 三级缓存
- 弱引用缓存(内存缓存)
- 基于LRU算法的内存缓存
- 磁盘缓存(DiskLruCache)
- 获取与写入图片顺序(4.x版本)
获取:弱引用 --> LruCache --> DiskLruCache -->网络
写入:网络 --> DiskLruCache–> 弱引用–>LruCache(图片引用计数为0时加入)
- 磁盘缓存策略
- DiskCacheStrategy.DATA: 只缓存原始图片;
- DiskCacheStrategy.RESOURCE:只缓存转换过后的图片;
- DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换过后的图片;对于远程图片,缓存 DATA和 RESOURCE;对于本地图片,只缓存 RESOURCE;
- DiskCacheStrategy.NONE:不缓存任何内容;
- DiskCacheStrategy.AUTOMATIC:默认策略,尝试对本地和远程图片使用最佳的策略。当下载网络图片时,使用DATA(原因很简单,对本地图片的处理可比网络要容易得多);对于本地图片,使用RESOURCE。
常见问题
- 获取LruCache过程
- 从弱引用缓存取不到时,会尝试从LruCache获取,此时会先将这个缓存从LruCache从移除,加入到弱引用缓存,同时**图片引用计数器加一 **
- 当释放图片时如果此时弱引用缓存的引用为0时会重新加入到LruCache
- LRU的实现
- 使用了LinkedHashMap (继承HashMap,维护了一个双向链表)
- LinkedHashMap和HashMap的区别
- LinkedHashMap继承自HashMap,是基于HashMap和双向链表实现的。
- hashmap是无序的,LinkedHashMap是有序的,分为插入顺序和访问顺序。如果是访问顺序,使用put和get时,都会把entry移动到双向链表的表尾。
- LinkedHashMap存取数据还是和HashMap一样,使用entry[]数组的形式,双向链表只是为了保证顺序。
- LinkedHashMap也是线程不安全的