第三方框架源码

LeakCanary 原理

  1. lifecycleCallbacks 监听 Activity,onActivity Destroyed 方法最终会调用 RefWatcher. watch 方法::
  2. 通过将 Activity 包装到 WeakReference(弱引用)中,弱引⽤在引⽤对象被垃圾回收之前,会将引⽤放⼊它关联的队列中。所以可以通过队列中是否有对应的引⽤来判断对象是否被垃圾回收了。(有的话被回收了,没有的话就没有被回收)
1
2
3
 private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
  1. 如果 Activity 没有被回收,调用 GcTigger. runGc 方法运行 GC,如果这时候还没有被回收,那就说明 Activity 可能已经泄露。
  2. 生成 dunp 文件,对他进行分析,开启一个前台服务

监控 Activity 和 Fragment 原理。
通过注册 Application 和 Fragment 上的⽣命周期回调来完成在 Activity 和 Fragment 销毁的时候开始观察。

为什么生成文件时会 ANR
导出 heap 堆文件会短暂冻结 APP

watch () ⽅法
原理:就是通过弱引⽤的⽅式来判断队列中是否有弱引⽤ ,来判断对象是否被垃圾回收了
KeyedWeakReference(key 是是個随机值,object 就是 view、fragment、activity)

1
2
3
4
5
6
7
8
9
10
11
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;

KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}

触发 GC 的正确姿势
unknown_filename.2

通过 dumpHprofData 来获取 hprof ⽂件

unknown_filename.1

2.0 LeakCanary 不需要主动初始化的原理
unknown_filename.3

存放路径
QQ截图20191125132921

可以直接 studio 里打开分析
QQ截图20191125133038

AndroidExcludeRefs 过滤

Shark 是 Leakcanary 2.0.0时推出的 Heap 分析工具,替代了之前使用的 HAHA 库,其作者称它比 haha 使用的 perflib 快6倍,使用的内存却是之前的10分之一

BlockCanary 原理

BlockCanary — 轻松找出Android App界面卡顿元凶 | markzhai’s home

  • View 的绘制也是通过 Handler 来执行的,所以如果能知道每次 Handler 处理消息的时间,就能知道每次绘制的耗时了, 那 Handler 消息的处理时间怎么获取呢?
  • 可以发现,loop 方法内有一个 Printer 类,在 dispatchMessage 处理消息的前后分别打印了两次日志。
  • 那我们把这个日志类 Printer 替换成我们自己的 Printer,然后统计两次打印日志的时间不就相当于处理消息的时间了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 public static void loop () {
...
for (;;) {
...
Printer logging = me. mLogging;
if (logging != null) {
logging.println (">>>>> Dispatching to " + msg. target + " " +msg. callback + ": " + msg. what);
}
msg.target.dispatchMessage (msg);
if (logging != null) {
logging.println (" Finished to " + msg. target + " " + msg. callback);
}
}
}

Looper.getMainLooper (). setMessageLogging (mainLooperPrinter);

public void setMessageLogging (@Nullable Printer printer) {
mLogging = printer;
}

log 分析的过程。
看 timecost 和 threadtimecost,如果两者差得很多,则是主线程被等待或者资源被抢占。
卡顿发生前最近的几次堆栈,如果堆栈相同,则可以判定为是该处发生卡顿,否则需要比较分析。

ARouter 原理

核心思想:
我们在代码里加入的@Route 注解,会在编译时期通过 apt 生成一些存储 path 和 activityClass 映射关系的类文件,然后 app 进程启动的时候会拿到这些类文件,把保存这些映射关系的数据读到内存里 (保存在 map 里),生成文件。
然后在进行路由跳转的时候,通过 build ()方法传入要到达页面的路由地址,ARouter 会通过它自己存储的路由表找到路由地址对应的 Activity.class (activity. class = map.get (path)),然后 new Intent (),当调用 ARouter 的 withString ()方法它的内部会调用 intent.putExtra (String name, String value)。
调用 navigation ()方法,它的内部会调用 startActivity (intent)进行跳转,这样便可以实现两个相互没有依赖的 module 顺利的启动对方的 Activity 了。

arouter-compiler

自定义注解解析器生成文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class RouteProcessor extends AbstractProcessor 

javaCompileOptions {
   annotationProcessorOptions {
    arguments = [ moduleName : project.getName () ]
   }
  }


@Override
public boolean process (Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isNotEmpty (annotations)) {
// 获取所有添加 Route 注解的元素
Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith (Route. class);
try {
logger.info (">>> Found routes, start... <<<");
// 调用 arseRoute ()函数进行处理获取的注解元素集合
this.parseRoutes (routeElements);

} catch (Exception e) {
logger.error (e);
}
// 如果有 Route 元素的注解,并且处理过程中无异常则返回 true
return true;
}
// 否则返回 false
return false;
}

unknown_filename.9
unknown_filename.10

[[注解、AOP、APT#APT]]

ARouter原理剖析及手动实现 - 掘金

探索Android路由框架-ARouter之基本使用(一) - 简书

探索Android路由框架-ARouter之深挖源码(二) - 简书

RouteMeta:基本路由信息

1
2
3
4
5
6
7
8
9
@Target ({ElementType. TYPE})
@Retention (RetentionPolicy. CLASS)
public @interface Route {
String path ();
String group () default "";
String name () default "";
int extras () default Integer. MIN_VALUE;
int priority () default -1;
}

初始化:ARouter.init (Application. this),实则是 LogisticsCenter 在帮我们管理逻辑,这是一个外观模式(创建一个统一的类,用来包装子系统中一个或多个复杂的类)
![[Pasted image 20240309214714.png]]

  • LogisticsCenter 内部先获取arouter-compiler生成的文件,然后将该文件,存储在sp中,下次启动应用的时候,直接从sp缓存中读取。
  • 存储 SP,然后遍历、匹配(满足条件则添加到具体的集合中,按照文件的前缀不同,将他们添加到路由映射表 Warehouse 的 groupsIndex、interceptorsIndex、providersIndex 中)
  • Warehouse:路由元数据和其他数据的存储。这个类本质就是路由文件映射表。里面提供了各种HashMap集合(Map不允许重复的key),去存储SP存储的值。
  • 如果navigation()不传入Activity作为context,则使用Application作为context

group分组

在 ARouter 中会要求路由地址至少需要两级,如”/xx/xx”, 一个模块下可以有多个分组。这里我们就将路由地址定为必须大于等于两级,其中第一级是 group。如 app module 下的路由注解:

1
2
3
4
ARouter.getInstance (). build ("/test/login")
. withString ("password", 666666)
. withString ("name", "小三")
. navigation ();

ARouter group 作用:ARouter 在编译期框架扫描了所有的注册页面/字段/拦截器等,那么很明显运行期不可能一股脑全部加载进来,这样就太不和谐了。所以就使用分组来管理。
Group 的值默认就是第一个 / /(两个分隔符) 之间的内容。

图片加载框架比较

共同优点

  1. 都对多级缓存、线程池、缓存算法做了处理
  2. 自适应程度高,根据系统性能初始化缓存配置、系统信息变更后动态调整策略。比如根据 CPU 核数确定最大并发数,根据可用内存确定内存缓存大小,网络状态变化时调整最大并发数等。
  3. 支持多种数据源支持多种数据源,网络、本地、资源、Assets 等

不同点

  1. Picasso 所能实现的功能,Glide 都能做,无非是所需的设置不同。但是 Picasso 体积比起 Glide 小太多。
  2. Glide 不仅是一个图片缓存,它支持 Gif、WebP、缩略图、视频的某一帧。Glide 支持加载 Gif 动态图,而 Picasso 不支持该特性
  3. Fresco 在5.0以下的内存优化非常好,代价就是体积也非常的大,按体积算 Fresco>Glide>Picasso
  4. UIL 可以算是老牌最火的图片加载库了,该作者在项目中说明已经停止了对该项目的维护。

图片框架的缓存

  1. MemorycCache 图片内存缓存。默认使用了 LRU 算法。
  2. DiskCache 图片磁盘缓存,默认使用 LruDiskCache 算法,在缓存满时删除最近最少使用的图片

Glide 源码

一般看源码先看他的使用方法,通过使用的方法看对应的代码。
Glide.with (MainActivity. this). load (url). into (headerImage);

  • with 方法把 context 传进去,返回 RequestManager 。并且调用 GlideBuilder. build(做初始化的),在这里做一些初始化操作,比如构建线程池(包括 sourceExecutor ,diskCacheExecutor ),缓存大小和缓存器,默认的连接监听工厂(connectivityMonitorFactory ),Engine 对象(怎么使用缓存的,先弱引用,后 Lru,最后走网络)和 RequestManagerRetriever 对象、
    DecodeJob(解析 InputStream 生成图片)。
  • RequestManager 监听了 ActivityFragmentLifecycle ,会在 onStop pauseRequests (); onStart 恢复 resumeRequests();
  • load(URL)Glide.with (context)已经返回了 RequestManager, 其实就是 RequestManager.load (“”)。主要就是把 URL 传进去,获取 RequestBuilder 对象。
  • 主要的操作都在 into 方法里
  • 在这里判断是 取活动缓存(是否有另一个view展示这张图片)还是 lru 缓存 还是本地缓存,还是没有,告诉 RequestBuilder。
  • RequestBuilder 的 into 方法里(上面返回了)开启了线程池进行加载资源。网络请求是通过 url 打开连接,返回一个 HttpURLConnection 对象,进行网络请求的。加载得资源后转换到主线程并进行回调设置给 imageview。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized void start (DecodeJob<R> decodeJob) {
this. decodeJob = decodeJob;
//获取 GlideExecutor 线程池
GlideExecutor executor = decodeJob.willDecodeFromCache () ? diskCacheExecutor : getActiveSourceExecutor ();
//开始执行 decodeJob 这个 Runnable
executor.execute (decodeJob);
}
private static final Executor MAIN_THREAD_EXECUTOR =
new Executor () {
private final Handler handler = new Handler (Looper.getMainLooper ());

@Override
public void execute (@NonNull Runnable command) {
handler.post (command);
}
};

  • Glide 支持图片的二级缓存 (并不是三级缓存,因为从网络加载并不属于缓存),即内存缓存和磁盘缓存
  • glide 为什么有 lru 还会内存溢出。因为直接把整个大图片的整个内存加载进去了。对于大图可以下载下来,asdrawale 来加载,drawable 更省内存,Drawable 应该不属于常驻内存的对象,不然的话,不可能不会出现 OOM 的~~
  • Glide 内部处理了网络图片加载的错位或者闪烁 (tag)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
   public Request getRequest () {
        //本质还是 getTag
        Object tag = getTag ();
        Request request = null;
        if (tag != null) {
            if (tag instanceof Request) {
                request = (Request) tag;
            } else {
                throw new IllegalArgumentException ("You must not call setTag () on a view
Glide is targeting");
            }
        }
        return request;
    }

   
    @Override
    public void setRequest (Request request) {
        //本质是 setTag
        setTag (request);
    }

对图片加载用到了 LruCache(最少最近使用)算法

解析文章

unknown_filename.4|600

缓存

Glide 中的 ActiveResource(活动资源)是用于跟踪当前正在展示的图片资源的机制。ActiveResource 用于判断当前是否有其他 View 正在展示同一张图片。

在 Glide 中,每当一个 View 请求加载一张图片时,Glide 会将该图片关联到对应的 ActiveResource 中。当图片加载完成并显示在 View 上时,Glide 会将 ActiveResource 中的引用计数加1。同样地,当图片从 View 中被移除时,Glide 会将 ActiveResource 中的引用计数减1。

通过检查 ActiveResource 中的引用计数,可以判断是否有其他 View 正在展示同一张图片。如果引用计数大于1,则表示当前有其他 View 正在展示该图片;如果引用计数等于1,则表示当前没有其他 View 正在展示该图片。

unknown_filename.5|800|600

unknown_filename.6|800|600

unknown_filename.7|600


EventBus 源码

register (this)就是去当前类,遍历所有的方法,找到 onEvent 开头的然后进行存储(把匹配的方法最终保存在 subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList<Subscription> ),eventType 是我们方法参数的 Class,Subscription 中则保存着 subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切)。
然后 post 的时候,根据 post 传入的参数,去找到匹配的方法,数据传递是通过 handler。在 handmessage 里判断

BroadcastReceiver 和 EventBus 有啥不同?
系统系统级的事件都是通过广播来通知的,比如说网络的变化、电量的变化、短信接收和发送状态等。所以,如果是和 Android 系统相关的通知,我们还得选择本地广播。但是,广播相对于其他实现方式,是很重量级的,它消耗的资源较多,耗电占内存。
eventbus 调度灵活,使用简单、快速轻量,但是阅读性比较差。

Evenbus 是做什么的?和 RXjava 有什么区别?

  • EventBus 是一款针对 Android 优化的发布/订阅事件总线。主要功能是替代 Intent, Handler, BroadCast 在 Fragment,Activity,Service,线程之间传递消息. 优点是开销小,代码更优雅。以及将发送者和接收者解耦。
  • 以前我们做组件间的消息分发更新,一般会采用观察者模式, 或者接口数据回调的相关方式。但是这样的做法虽然可以解决问题,但是组件之间的耦合比较严重,而且代码也不易阅读和相关维护。为了解决这样的问题我们可以使用消息总线 EventBus 框架。
  • 原生的异步 AsnyTask 简直就是个坑,它就是一个任务队列,多个任务执行并不是并发的,有可能就卡在其中一个出不来了。。。
  • RxJava 要比 EventBus 的应用更广泛,RxJava 里面几乎可以做任何事情。做异步、网络的数据处理,写出来的代码比较优雅。

黏性事件
简单讲,就是在发送事件之后再订阅该事件也能收到该事件,跟黏性广播类似,但是它只能收到最新的一次消息,比如说在未订阅之前已经发送了多条黏性消息了,然后再订阅只能收到最近的一条消息。

如何读源码

  1. 理想情况下,逐⾏通读可以最⾼效率读通⼀个项⽬的代码,因为每⾏代码都只需要读⼀遍;但实时情况下,逐⾏通读会导致脑中积累太多没有成体系的代码,导致你读个⼏⼗⼏百⾏就读不下去了,因此⼀点也不实⽤。⽽从切⼊点开始读,可以在最快时间内把看到的代码体系化,形成⼀个「完整的⼩世界」;在把「⼩世界」看明⽩之后,再去⼀步步扩⼤和深⼊,就能够逐渐掌握更多的细节。

  2. 寻找切⼊点的⽅式:离你最近的位置就是切⼊点,通常是业务代码中的最后⼀⾏。

  3. 以 Retrofifit 为例,最后的 Call.enqueue () 会被我作为切⼊点;在尝试从 Call.enqueue () 切⼊失败后,逐步回退到 Retrofit.create ()⽅法,找到项⽬结构的核⼼,然后开始继续发散和深⼊

  4. 代码阅读过程中,不懂的代码会越来越多,脑⼦就会越来越乱。如果不断尝试把看到的代码结合起来组合成完整逻辑,就能让头脑始终保持清晰,⽽不是深⼊到某个细节好久之后忽然⼀抬头:「我为什么点进这个⽅法来着?」可以试着在读源码的时候,经常把多⾏或多段代码在脑⼦⾥(或者笔记⾥)组合成⼀整块,从⽽让代码结构更清晰,让阅读过程不断增加进度感,也减⼩继续阅读的难度。

  5. 以 Retrofifit 为例,当读懂 Proxy.newProxyInstance () ⽅法实际上是创建了⼀个代理对象的时候,可以停下来做⼀个总结:「这是 Retrofifit 的⼤框架」,在脑⼦⾥或者笔记上都可以。总结消化过后,再继续阅读。

  6. 读代码经常会出现「横向逻辑还没看清晰,纵向深度也没挖透」的情况。那么到底是要横向扩展阅读结构,还是纵向挖深度,最好是在每次遇到这种分岔路⼝的时候就先做好决定。不能在每个分岔路⼝都想也不想地看到不懂的就追下去,容易迷路。

  7. 在遇到「横向也⼴,纵向也深」的时候,根据情况选择其中⼀个就好,并没有必然哪种选择更优的铁律。⽽如果遇到越钻越头⼤的情况,可以退回之前的某⼀步,换条路继续⾛。换路的时候记得做好标记:「我在哪⾥探路失败了」。


第三方框架源码
http://peiniwan.github.io/2024/04/dd1959e6425b.html
作者
六月的雨
发布于
2024年4月6日
许可协议