【安卓面试】Android三方框架原理学习笔记

前言

随着安卓技术的发展,面试难度也逐步提高,从两年前面试初中级岗位都会问一些三方框架原理,所以这次特意把一些常用的原理给过了一遍,本篇依旧是对常见框架的实现原理进行高度概括以及亮点,实际理解建议结合源码理解

资料参考:

LeakCanary

  • 功能:检测内存泄漏
  • 内存简单划分
  1. 栈(基本类型)(虚拟机栈、本地方法栈)
  2. 堆(new分配的对象)
  3. 方法区(静态变量、class对象、常量池)
  4. 本地方法区(Native 存储与C语言交互)
  • 内存泄漏原因
  1. 对象不再使用
  2. 有些对象存在有限的生命周期,但持有长周期对象
  • 常见例子
  1. 单例泄漏(可使用applicationContext)
  2. 非静态内部类创建出的静态实例对象
  3. Handler泄漏(Activity创建的匿名Handler,Msg - Handler - Activity,可以使用静态内部类和弱引用解决)
  4. Webview泄漏(渲染页面产生的堆内存,可以单独进程,退出杀进程解决)
  5. 线程(匿名线程,持有外部引用)

创建弱引用并关联到一个 ReferenceQueue ,当软/弱引用创建时关联了ReferenceQueue,则在对象被回收时,会将Reference 对象添加到 ReferenceQueue中,以activity为例,则是在生命周期onDestroy时对activity对象进行检查

  • 流程
  1. 在Activity onDestroy 时,将对象放在一个weekRef 中
  2. 将这个weekRef 关联到 ReferenceQueue
  3. 查看ReferenceQueue是否存在这个引用
  4. 如果泄漏了则dump出 heap信息,然后进行分析
  • 源码大概流程
  1. 通过lifecycleCallback 监听,在Activity的onDestroy监听(创建refWatcher- ActivityRefWatcher,新版的是ObjectWatcher)
  2. 通过watch监听方法,在一个map里面,创建key、weakRef(关联key和ReferenceQueue)
  3. onDestroy 时,通过ensureGone方法,进行状态判断,如果不是Debug/已经销毁 的状态,则调用一次GC,再次判断是否泄漏,如果还是不存在,则dump出内存进行分析
  4. 分析过程(checkForLeak):通过库导出的hprof,解析成一个snapshot对象,然后通过优化GCRoots、找出泄漏对象的最短距离和一系列处理(通过在快照中找到之前创建的weekRef 对象可以快速查找,然后遍历对象实例,再通过key值找到该对象),最后如果找到了该泄漏的对象则

插件化换肤框架

  • 核心思路:我们设置的布局本质上解析XML -> 调用LayoutInflater#createViewFromTag -> 调用Factory/Factory2 创建一个View;通过设置一个自定义的Factory2,系统创建View的时候我们就可以拿到相关的信息,然后通过 观察者 模式在需要的时候进行换肤;换肤的原理是根据 Resource 几个相关的API实现的;
  • 核心步骤
  1. 实现Factory2,hookView的创建流程(这里可以把创建的View替换成我们自己的实现),创建View的时候 获取/实现 换肤相关的操作,保存到一个集合
  2. 使用一个管理类,使用Application.ActivityLifecycleCallbacks 注册监听生命周期
  3. 通知换肤操作:利用 resId(一堆数字) 获取主包的资源的名字(R.color.xxx),然后通过这个名字去获取插件包的资源
  4. 对各个插件的Resource 进行管理
  • 核心API
  1. 实现LayoutInflater#Factory2 接口,设置到LayoutInflater,获取到加载View的信息
  2. Application.ActivityLifecycleCallbacks 注册监听生命周期
  3. ResourcegetResourceEntryName(int resid)getResourceTypeName(int resid)getIdentifier(String name, String defType, String defPackage),进行资源的替换
  4. 加载插件包的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

  • 功能:事件总线
  • 关键点
  1. 注册
  2. 事件发送和分发
  3. 线程调度
  • 默认是一条总线,可以根据需要新建多条
  • 关键类和参数

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)

这里主要做两件事:

  1. 通过反射拿到当前观察者的Class信息,得到一个封装了 事件信息、对应的观察方法的SubscriberMethod列表;这里会通过一个Map缓存已经查找过的类型,加快效率
  2. 遍历上面获取的SubscriberMethod列表,把当前的观察者对象和事件一一关联起来,主要是将观察者和SubscriberMethod封装成一个Subscription对象,方便后面反射调用;还有黏性事件的处理

简要概括第一步是查找,第二步是注册,注册的相关逻辑在EventBus#subscribe方法,其中主要逻辑为

  1. 判断是否注册
  2. 根据优先级添加 【事件 - 观察者list】
  3. 关联 【当前观察者 - 事件 map】
  4. 黏性事件的检测和分发
分发

主要流程

  1. 通过ThreadLocal获取当前线程的分发队列
  2. 判断是否正在分发消息,进行消息分发
  3. 经过一系列的判断(父类查找观察者等),最终调用到EventBus#postToSubscription ,根据之前封装Subscription ,取出ThreadMode类型,调用不同的poster 进行消息分发
线程切换
  • 主要是通过三种不同类型的Poster进行反射调用,其中BACKGROUND和 ASYNC 都是在线程池中执行,不同的是ASYNC是每次都会新起一个单独的线程,BACKGROUND是每次会取完所有的消息;
  • 主线程是通过一个hander实现的,有个细节是他会判断每次调用消息分发的时间,如果超过10ms,会发送一个空消息避免对主线程的过度占用
与广播的区别
  • EventBus 不依赖Context,使用方便但不支持跨进程
  • EventBus 使用方便

ButterKnife

大概原理

  • 主要源码构成
  1. annotation : 定义各个注解
  2. compiler : 解析注解 -> 判断要生成的类 -> 利用APT和AST 解析注解生成对应的类
  3. api :根据生成的类的命名规则 对外封装一层统一的api进行调用

主要还是通过APT 编译时注解动态生成绑定类(持有activity) 再通过反射去获取到生成类进行绑定

OKHTTP

Retrofit

ARouter

Glide

概览
  • 文章参考
  • 核心
  • 生命周期检测:注入无UI的fragment,检测activity生命周期,控制图片的加载、停止、销毁
  • 加载:使用原生的HTTP请求库(4.4 之后底层改为OKHTTP实现)
  • 缓存:使用三级缓存(LRU算法和LinkedHashMap数据结构)
流程与缓存机制参考
  • 三级缓存
  1. 弱引用缓存(内存缓存)
  2. 基于LRU算法的内存缓存
  3. 磁盘缓存(DiskLruCache)
  • 获取与写入图片顺序(4.x版本)

获取:弱引用 --> LruCache --> DiskLruCache -->网络
写入:网络 --> DiskLruCache–> 弱引用–>LruCache(图片引用计数为0时加入)

  • 磁盘缓存策略
  • DiskCacheStrategy.DATA: 只缓存原始图片;
  • DiskCacheStrategy.RESOURCE:只缓存转换过后的图片;
  • DiskCacheStrategy.ALL:既缓存原始图片,也缓存转换过后的图片;对于远程图片,缓存 DATA和 RESOURCE;对于本地图片,只缓存 RESOURCE;
  • DiskCacheStrategy.NONE:不缓存任何内容;
  • DiskCacheStrategy.AUTOMATIC:默认策略,尝试对本地和远程图片使用最佳的策略。当下载网络图片时,使用DATA(原因很简单,对本地图片的处理可比网络要容易得多);对于本地图片,使用RESOURCE。

在这里插入图片描述
在这里插入图片描述

常见问题
  • 获取LruCache过程
  1. 从弱引用缓存取不到时,会尝试从LruCache获取,此时会先将这个缓存从LruCache从移除,加入到弱引用缓存,同时**图片引用计数器加一 **
  2. 当释放图片时如果此时弱引用缓存的引用为0时会重新加入到LruCache
  • LRU的实现
  • 使用了LinkedHashMap (继承HashMap,维护了一个双向链表)
  • LinkedHashMap和HashMap的区别
  • LinkedHashMap继承自HashMap,是基于HashMap和双向链表实现的。
  • hashmap是无序的,LinkedHashMap是有序的,分为插入顺序和访问顺序。如果是访问顺序,使用put和get时,都会把entry移动到双向链表的表尾。
  • LinkedHashMap存取数据还是和HashMap一样,使用entry[]数组的形式,双向链表只是为了保证顺序。
  • LinkedHashMap也是线程不安全的

Jetpack框架

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值