9.基于FrameWork 的App 终极启动优化

相信很多人在被问到这个问题的时候,心里面能想到的就只有开启异步线程去做sdk的初始化,其他可以优化的点绞尽脑汁也想不到,其实在没有系统的学习framework之前,关于这些方面我能做的也少的可怜,不过经过系统性的学习后,我觉得今天可以给大家提供一些比较好的思路

想要做启动优化就需要知道在启动过程中都经历了哪些步骤,哪些步骤我们是可以参与进来的,下面就简单的介绍一下App 启动过程中都需要经过哪些步骤

1.组装 request ,显示黑白屏 , 此时可以通过 Theme 中的windows 属性将黑白屏变成我们想要的背景

2. zygote 创建进程 这个过程完全是有 SystemServer 来决定的,不能人为的进行干扰,

3.进程创建后启动 ActivityThread ,创建 context ,调用 AMS attachApplication 绑定进程, 同时解析所有的provider, 这里也是由 ActivityThread 来决定的,由于没有任何可以获得实例化的接口,所以这个过程也无法操作

4.AMS 调用 ApplicationThread 的 bindApplication方法, 在这里 就到了Application 的入口了,而且这里进行了非常重要的三步,下面贴一下代码

(1)创建 Application 调用 application 的attch 方法, 向下传递到Application.attachBaseContext 方法
ActivityThread.handleBindApplication()  
app = data.info.makeApplication(data.restrictedBackupMode, null);// TODO 创建Application
        -->LoadedApk.makeApplication()
        app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext);反射创建Applicaiton
                    -->Instrumentation.newApplication()
                    app.attach(context);
                              --> Application.attachBaseContext(context);

从上面代码的逻辑能分析出来,Application 第一个被调用的方法就是 attachBaseContext 这个方法,而在5.0之前如果出现了65526 方法个数超限的这个问题时,也是在这里解决的,现在已经放弃了这种机制了,如果在项目中没有使用 插件化的方案,这里其实也是没有什么需要改动的,但是如果使用了插件化的方案,由于在这里需要进行反射,必然就会带来加载性能上的问题,这个也是无法避免的,毕竟慢一点总比崩溃强,
说道插件化,可能很多人并不知道他的原理,在这里就来大体的说一下插件化的实现方案,以及为什么需要在 attachBaseContext 进行反射
在类的加载过程中的ClassLoader 可以使用的PathClassLoader 与 DexClassLoader , 但是他们的loadClass 方法没有实现,在调用findclass 的方法都调用到了BaseDexClassLoader, 在加载类的过程中由于我们所需要加载的类在系统中没有,就需要调用 BaseDexClassLoader 他自身的findclass 方法, BaseDexClassLoader 构造方法中存在一个DexPathList 这样一个数据,里面存在的是 Element[] 数组,在findclass 的过程是遍历这个数组找到需要的类,如果找到了就直接返回,不回继续去加载,这也就给插件化方案一个漏洞,如果我把我需要修改的dex 插入到这个Element[]数组的前面,那么 BaseDexClassLoader 在findclass 的过程中就会先遍历我的修复包,找到后退出,那么有问题的包就永远也不会被加载,这样问题就修复了具体代码逻辑如下

public Class findClass(string name, List<Throwable>suppressed){
  for (Element element : dexElements) 
      fDexFile dex = element.dexFile;
      if (dex != null) {
          Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
          if (clazz != null) {
              return clazz;
          }
  if (dexElementsSuppressedExceptions != nul1) {
      suppressed.addA11(Arrays.asList(dexElementsSuppressedExceptions))
  }
  return null;
}

但是随之而来的问题就是在系统在加载我们的类之前,我们就需要将新的dex 插入进去,而且是必须每次打开app 都需要执行插入这个动作,至于为什么要在 attachBaseContext 里面进行,因为他是我们可以接触到的第一个方法,为了防止 classload 将错误的类加载进来,我们就需要将这个事情提前到所有方法之前

(2) installContentProviders 这段代码就在 创建Application 之后,也就是说,如果apk中如果大量的使用了provider 的话,这会极大的导致app启动速度变慢,又看过字节他们的方案是将FileProvider 的初始化放到了使用时在初始化,可以见得他们除了这个provider其他都是内部的库,没有使用provider这种方法,所有的provider是通过aidl 的binder 传递到ActivityThread mBoundApplication 这个数据上,如果我们可以在 attachBaseContext 通过反射获取到这个list ,并将这list保存起来,同时将这个list 设置为空,那么就不在会初始化provider了,同时可以在适当的时机通过反射在来初始化相应的代码,但是在实际开发过程中,这种操作也只能在隐私权限中使用,其他的就会导致我们调整非常多的初始化方案有点得不偿失,而且我在分析了我原来公司的apk后发现,虽然我们自己就使用FileProvider ,但是合并后的清单文件中的 provider 一共有20多个,如果想要做到极致的优化,相信这里还是有非常多的可以优化的地方的

(3) Application.onCreate

进行到了这里相信绝大多数人就已经非常清楚了,而且大家也都非常有经验了,但是对于开启线程来初始化三方库的过程,如果想要分析清楚,或者有什么比较好的方案,我会在后续详细的出一篇相应的文章,而且现在市面上大多数app都会有一个广告页面,为什么呢,仅仅只是为了推广的广告费用吗,其实他们将很大一部分初始化的工作放到了这里
我们需要解决的问题如下

1. 一个任务在随意插入任务队列后,如何保证他的执行顺序

2.一个任务想要执行,如何明确他的前置任务已经执行完毕

3.后置任务使用前置任务的结果,如同解耦获取前置任务结果

4.如何保证多线程调度任务的准确性

5.如何准确的控制初始化任务的节点,在特定任务执行完成后,恢复主线程开启开始启动app,后续初始化工作继续进行,但是不影响首屏工作

这里我写了一个简单的启动任务构建器,上面写了非常多的注释,大家可以下载看一下
https://github.com/tsm1991/StartUp ,当然他还要很多的不足,希望大家提出自己的意见

5. 启动Activity

到了这里可以优化的点我想到的有3个

(1) 在广告页面或者 application 中提前加载 首页需要的简单数据,快速相应,在进入首页后快速填充,看了一下京东 淘宝都是这么做的,即使没有网络也会有简单的数据填充,不让用户等待,这是一个非常好的操作
(2)AsyncLayoutInfter 异步加载布局, 了解了ViewRootImpl 的原理就知道,在Activity onCreate 的过程中由于 ViewRootImpl 还没有创建,我们将View 的创建与初始化的工作放到了异步线程来创建和初始是没有问题,但是需要注意的是在 一些自定义View 中的handelr 需要绑定 Looper.getMainLooper(),由于子线程中没有looper ,此时创建Handler 就会报错,也可以使用X2C 的方式来加速首页控件的速度
(3) Message 相信大家肯定都知道android 系统都是基于Message 来运行的,那么如果某些sdk 在初始化的过程中即使你是在异步线程中初始化的,但是由于他创建了一个主线程的handler ,并通过他来做一些耗时不多但是比较频繁的事情,也会导致Activity 的启动时机延后,此时我们就可以 在Applicaiton 的attachBaseContext方法最前面添加下面代码 Looper.getMainLooper().setMessageLogging. 来查看具体是哪个sdk 使用了handler来发送消息,适当延后这个sdk的初始化工作
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,639评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,093评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,079评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,329评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,343评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,047评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,645评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,565评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,201评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,338评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,014评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,701评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,194评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,685评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,345评论 2 358

推荐阅读更多精彩内容