Android启动优化-类预加载

背景

在Android各项优化里面,其中有一块避不开的就是启动优化,大部分的优化往往跟业务相关,比如延迟加载、特定资源预加载等,今天不讨论业务相关,仅从jvm加载类优化聊起,因为它逻辑独立并且实现起来也相对简单。

类加载

简单描述一下Android的类加载,指的是从dex包加载对应的class到方法区中,后续就可使用这个类对象。也是利用这个类加载机制,衍生出了插件化、热修复机制。

ClassLoader

这里就不简述jvm的双亲委派机制了;

  1. BootClassLoader 加载Framework的class;

  2. PathClassLoader 默认的ClassLoader,负责加载已经安装到系统(/data/app)的apk,它的构造函数只允许传入dexPath、libraryPath(native lib路径)。

  3. DexClassLoader 可以不限制地加载不同路径的dex包,DexClassLoaders是插件化、热修复的基础。构造函数可以传入dexPath、libraryPath、optimizedDirectory(dex2oat缓存路径),dex2oat操作会生成 ELF 文件。我们会利用optimizedDirectory参数来对插件的dex进行优化(AOT)。

类加载时序

image.png

如何知道哪些类比较耗时

在线上的阶段发现不少ANR都出现在Class#findClass上面,若是在合适的时机能够提前在非UI线程下预加载耗时的class,也能减少ANR的概率。

我们可以利用Hook ClassLoader的方式在线下/线上统计哪些class比较耗时。

/**
 * 统计loadClass耗时
 */
class LogClassLoader(dexPath: String, parent: ClassLoader) : PathClassLoader(dexPath, parent) {

    override fun loadClass(name: String): Class<*> {
        if (name.startsWith("android.") || name.startsWith("java.lang")) {
            return super.loadClass(name)
        }
        val start = System.currentTimeMillis()
        try {
            return super.loadClass(name)
        } finally {
            val cost= System.currentTimeMillis() - start
            val thread = Thread.currentThread().name
            Log.i("loadClass", "load $name thread:$thread cost: $cost")
        }
    }
} 

在初始化入口,hook classLoader,最后可统计出哪些类相对比较耗时。

fun hook(application: Application) {
    val pathClassLoader = application.classLoader
    try {
        val logClassLoader = LogClassLoader("", pathClassLoader.parent)
        val pathListField = BaseDexClassLoader::class.java.getDeclaredField("pathList")
        pathListField.isAccessible = true
        val pathList = pathListField.get(pathClassLoader)
        pathListField.set(logClassLoader, pathList)

        val parentField = ClassLoader::class.java.getDeclaredField("parent")
        parentField.isAccessible = true
        parentField.set(pathClassLoader, logClassLoader)
    } catch (throwable: Throwable) {
        Log.e("hook", throwable.stackTraceToString())
    }
} 
优化策略、时机
  1. 预加载 Class.forName

通过上面的统计,我们知道哪些类加载比较耗时,可选择在合适的时机,将对应的类(特别是kotlin的很多类)适当在子线程加载。

Class.forName(className, true, context.classLoader) 

其中initialize参数设置为true,表示会初始化静态代码块,和赋值静态变量等操作

  1. 禁用 VerifyClass

在ClassLoader#loadClass流程中,都要经过defineClass(从dex包io加载class)、verifyClass(验证指令)、resolveClass(链接class,分配字段内存)和init(初始化静态变量和代码块)

如果你用Systrace或者perfetto工具去查看加载耗时,其中有VerifyClass耗费的时间是相当多的。

插件化

现在基本各大厂的App都用到了插件化,例如微信、抖音等等。插件化有几个重要的点:加载插件的类class、resource资源、插件的so。

这里面的坑点也很多,例如:

  • 不同的classloader会加载不同namespace的so,插件与插件的so相互依赖就有问题;

  • 插件的ClassLoader跟主包的ClassLoader如何做到可以加载主包和插件包的class,也保证同样的class不会被不同的classLoader重复加载;

  • 插件化的dex包是不会dex2oat,一般我们开额外的进程来做dex2oat操作(AOT),编译优化后就可以设置为插件ClassLoader的optimizedDirectory,但是8.0之后BaseDexClassLoader的 optimizedDirectory 参数已经没用了,意味着插件AOT失效了;

  • Android 5.0之前是在安装的时候全量编译AOT,会变得巨慢;Android 7.0 之后是用了JIT和AOT,这有可能导致启动的时候就开始AOT,其中有一个优化手段就是抑制或者延迟AOT, 以避免ANR、优化启动速度。

上面的先留坑吧,等后面慢慢整理。

总结

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值