一、错误现场
 java.lang.ClassCastException: androidx.appcompat.widget.TintContextWrapper cannot be cast to android.app.Activityat leakcanary.internal.navigation.ViewsKt.getActivity(Views.kt:16)at leakcanary.internal.activity.screen.LeaksScreen.onGroupsRetrieved(LeaksScreen.kt:40)at leakcanary.internal.activity.screen.LeaksScreen.access$onGroupsRetrieved(LeaksScreen.kt:23)at leakcanary.internal.activity.screen.LeaksScreen$createView$$inlined$apply$lambda$1$1.invoke(LeaksScreen.kt:35)at leakcanary.internal.activity.screen.LeaksScreen$createView$$inlined$apply$lambda$1$1.invoke(LeaksScreen.kt:23)at leakcanary.internal.activity.db.Io$execute$2$1.invoke(Io.kt:52)at leakcanary.internal.activity.db.Io$execute$2$1.run(Io.kt:12)

发生错误的代码:

// LeakCanary中Views.kt文件中的扩展属性
internal val View.activityget() = context as Activity

突如其来的Bug,让我措手不及。好好滴,leakcanary怎么会crash了?事出蹊跷必有妖啊,虽然crash发生在Leakcanary,
作为一个正义的程序猿,也不能把锅甩给Leakcanary。所以,为了分析这个问题,我趁机把leakcanary源码撸了一遍。原来发现是换肤框架利用

二、Leakcanary原理抽丝剥茧

作为一个Android程序猿,想必一定使用过Leakcanary这个三方库,他可以很方便的帮我们发现内存泄漏的问题。那么为了方便我们快速的抓住核心内容,我们从问题出发来剖析:

  • 问题一:Leakcanary如何检测和获取堆转储文件?

我们知道新版本的LC(为方便打字,以下用LC代替Leakcanary的拼写)是不需要初始化的,是通过ContentProvider来初始化的,当我们的应用启动的时候再Application的onCreate被调用之前,ContentProvider的onCreate会先调用,LeakCanary正好利用的这个机制,下面我们先看下LeakCanary监控内存泄漏以及如何获得堆转储文件

上面的流程是LeakCanary的整个方法调用的细节,我们不需要记忆的,这里写出来只是供大家参考的,其中关键的几个步骤我用绿色的背景标识了。

第一个LeakCanary初始化的机制,这个上面介绍过;

第二个LeakCanary如何判断去评判一个页面等是否有内存泄漏,通过注册一个生命周期的回调,当页面调用到onDestroy时,也就是页面应该可以被销毁时,这个时机去做接下来的一些列判断;

第三个利用了当一个弱引用的对象编程弱可达对象,就会被放到队列里,这个时候我们通过将队列中的数据取出来,如果有值,说明对象已经是弱可达了,也就不会存在内存泄漏了,那我们就将其从被监测的对象列表中删除,否则就认为对象可能存在内存泄漏;

第四个关键的是通过调用GC,来强制GC一遍,但是我们知道**System.gc()**这个API是不能保证垃圾回收马上开始的,所以可以使用这个办法确保进行垃圾回收:

    override fun runGc() {// Code taken from AOSP FinalizationTest:// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/// java/lang/ref/FinalizationTester.java// System.gc() does not garbage collect every time. Runtime.gc() is// more likely to perform a gc.Runtime.getRuntime().gc()enqueueReferences()System.runFinalization()}private fun enqueueReferences() {// Hack. We don't have a programmatic way to wait for the reference queue daemon to move// references to the appropriate queues.try {Thread.sleep(100)} catch (e: InterruptedException) {throw AssertionError()}}

第五个我们关心的是如何获取到堆信息并转储成文件呢(最关键的一行代码),那么我们可以使用Android系统的API如下:

Debug.dumpHprofData(heapDumpFile.absolutePath)

到这里拿到堆转储文件就是完成了我们的监控和收集的工作,接下来就是对文件的处理,以及怎样将处理后的信息通过页面清晰的展示出内存泄漏的原因。

早期的LeakCanary使用的是haha的库进行分析的,新版的LeakCanary使用的是shark库,进行分析的,那么这里就不再介绍shark是如何进行分析堆转储文件的。

三、换肤框架skin原理抽丝剥茧

换肤框架的一般思路是借助于Factory2来完成自定义inflate的。
为什么可以这么做?我们得先分析下当我们在Activity中通过setContentView()设置布局时,系统是如何创建对应的View的,下面是简化的时序图:

图中画的是系统会优先使用Factory2来创建View,但是默认情况下Factory2是空的,并没有实现。所以这个实现逻辑就提供给开发者一个可以自定义这个创建过程的逻辑,而换肤框架skin也是利用这一点来实现的。

skin换肤框架通过注入一个Factory2实例,从而改变系统默认的创建View的过程,再来看下skin框架实现Factory2接口的类:SkinCompatDelegate

所以使用换肤框架时,一些类型的View创建时传入的Context对象是使用TintContextWrapper包装后的实例,所以这时候View的getContext返回的对象是context的一个包装类实例,这个对象当然是不能强制转换成Activity的。所以才会出现文章开头出现的这个问题。

到这里,问题的根源就找到了,那如何解决呢,一种思路是,skin框架提供通过注解来声明哪些页面需要实现换肤的逻辑,默认是所有的Activity,所以我们只要在我们的基类里加注解(注解要稍微修改一下,改成可继承的),这个时候LeakCanary的页面就不会走换肤框架注入的逻辑,就能恢复正常了。

文章推荐:
史上最有趣的HTTPS漫画趣谈
通俗易懂的HashMap源码分析

Leakcanary原理解析以及换肤框架skin的原理分析相关推荐

  1. android 换肤框架原理,换肤框架Android-skin-support 源码分析

    基本原理: Android-skin-support 这个框架的主要思路是: 1. 更换LayoutInflaterFactory,对从xml布局文件要解析出来的 View,如果有相对应的支持换肤功能 ...

  2. Android换肤功能实现与换肤框架QSkinLoader使用方式介绍

    框架地址:https://github.com/qqliu10u/QSkinLoader 效果图 https://github.com/qqliu10u/QSkinLoader/raw/master/ ...

  3. Android-skin-support 一款用心去做的Android 换肤框架

    介绍 Github地址: https://github.com/ximsfei/Android-skin-support Android-skin-support: 一款用心去做的Android 换肤 ...

  4. 红橙Darren视频笔记 换肤框架4 换肤的功能完善 内存泄漏分析

    上一篇完成了换肤框架的基本搭建,这一次 我们继续补完上一次遗留的一些可以完善的部分 1.完善换肤 1.1退出后再进入应用 不会丢失上一次保存的皮肤 基本原理:将上一次切换的皮肤path保存在Share ...

  5. Android 手写实现插件化换肤框架 兼容Android10 Android11

    目录 一.收集所有需要换肤的view及相关属性 二.统一为所有Activity设置工厂(兼容Android9以上) 三.加载皮肤包资源 四.处理支持库或者自定义view的换肤 五.处理状态栏换肤 六. ...

  6. android的资源混淆和压缩工具,换肤框架

    介绍一款可以对android资源进行压缩的工具,超好用(已经在项目中使用到) https://github.com/shwenzhang/AndResGuard Android-skin-suppor ...

  7. 换肤框架Android-Skin-Support问题记录

    换肤框架Android-Skin-Support问题记录 换肤框架Android-Skin-Support问题记录 换肤框架Android-Skin-Support问题记录 换肤框架使用 问题 1:其 ...

  8. android 换肤框架搭建及使用 (3 完结篇)

    本系列计划3篇: Android 换肤之资源(Resources)加载(一) setContentView() / LayoutInflater源码分析(二) 换肤框架搭建(三) - 本篇 tips: ...

  9. Android-skin-support换肤框架使用指南

    1.介绍 针对Android应用的换肤需求,通过Android-skin-support框架可以很好地优化APP的代码结构,不需要将所有皮肤的图片资源和xml放在一起,通过相关接口,加载相应主题的皮肤 ...

最新文章

  1. 快速构建Windows 8风格应用27-漫游应用数据
  2. usb接口供电不足_1个USB接口变成4个?什么东西那么“牛”?请你花2分钟了解一下...
  3. 梦中的统计(洛谷P1554题题解,Java语言描述)
  4. 抽象类与抽象方法 c# 1613720553
  5. 1.5不同类型的循环神经网络
  6. iZotope RX 9 for Mac(受损音频修复工具)
  7. linux安装红警教程,红警2任务安装教程_红色警戒2任务安装方法一览
  8. Nginx教程之Rewrite语法规则和开源网站程序伪静态规则详解
  9. 防火墙开放21端口linux,linux防火墙开放80,3306,21,443端口
  10. Entrust - Laravel 用户权限系统解决方案 | Laravel China 社区 - 高品质的 Laravel 和 PHP 开发者社区 - Powered by PHPHub...
  11. win10电脑如何隐藏下方任务栏
  12. 多御浏览器新出的手机版本有什么功能?
  13. Wrapper中的QueryWrapper常用ge,gt,lt,le具体含义
  14. react手写签名功能
  15. 嵌入式C语言之零碎知识
  16. 青藤 #10115 栈练习1
  17. 如何实现伸缩 /折叠报表
  18. nutch1.9和solr4.5集成 输出信息
  19. Fixcel电子表单——Excel导入
  20. 平台搭建---Spark提交应用程序---Spark Submit提交应用程序及yarn

热门文章

  1. 面试题整理 !=!=未看 *****面试题整理最全 有用
  2. 经典语录-每日积累-05
  3. 【Linux】sysfs文件系统
  4. 解决vue弹窗被遮罩层遮挡和关闭弹窗后遮罩层不消失的两个问题
  5. [1st of series ABE] Installation of “libopenabe-1.0.0“
  6. 20190308搜索测试
  7. html5图片的纵向排列,css让图片横着排列的方法
  8. 电压、电流、电阻的概念
  9. SNOWFLAKE(雪花ID)的实现
  10. android如何远程,如何远程控制我的Android?