一、前言

发动功用是百度App最中心方针之一。用户期望运用能够及时呼应并快速加载,发动时刻过长的运用不能满意这个期望,而且或许会令用户绝望,这种糟糕的体会或许会导致用户在运用商店针对您的运用给出很低的评分,乃至彻底扔掉您的运用。发动功用的优化成为了体会优化中最关键的一环,百度App在此方向持续投入,不断优化,进步用户体会。

发动功用优化分为概述篇、东西篇、优化篇和防劣化篇,本篇文章首要论述功用优化相关内容,前期已发表文章能够参看:

百度App 低端机优化-发动功用优化(概述篇)

百度App Android发动功用优化-东西篇

百度App功用优化东西篇-Thor原理及实践

二、优化理论

对发动功用优化的认知,决议了发动功用优化的方向与思路,然后会决议优化的作用。较多开发者对发动进程的认知,来源于Google 开发者文档中有段对发动进程的描绘:

百度 App 启动性能优化实践篇

1、创立运用方针;

2、发动主线程;

3、创立主 activity;

4、扩充视图;

5、布局屏幕;

6、履行初始制作。

一旦运用进程完结第一次制作,体系进程就会换掉当时显现的后台窗口,替换为主 activity。此刻,用户能够开始运用运用。

上面首要介绍了运用在发动进程中的各个阶段,但其实只是大致归纳,其实发动办法会比较多,极有或许在不同的发动途径履行的逻辑有差异,因而全途径的认知在优化进程中起到了十分重要的作用,如下图所示:

百度 App 启动性能优化实践篇

在发动进程中,点击桌面图标是干流冷发动办法,而Push调起,浏览器调起等端外转化也是比较常见的调起办法,各种发动办法的发动进程根本可拆解为:进程创立、结构加载、主页烘托、预加载四个环节。而发动功用优化首要面临的不只是点击桌面图标这一种途径,更多的需求发动全途径的优化,到达体会的极致优化。

发动进程也需求结合体系层面来了解,然后挖掘优化点,探究优化的极限。发动进程是十分杂乱的进程,需求较多体系级进程合作才干完结页面的展示,供用户正常运用,下图展示的点击icon的发动进程:

百度 App 启动性能优化实践篇

发动进程大约可归纳为:

1、Launcher告诉AMS发动APP的主Activity;

2、ActivityManagerService(以下简称AMS)记载要发动的Activity信息,而且告诉Launcher进入pause状况;

3、Launcher进入pause状况后,告诉AMS现已paused了,开始发动App;

4、App未开启过,AMS发动新的进程,而且在新进程中创立ActivityThread方针,履行其间的main函数办法;

5、App主线程发动结束后告诉AMS,并传入applicationThread以便通讯;

6、AMS告诉App绑定Application并发动MainActivity;

7、发动MainActivitiy,而且创立和关联Context,终究调用onCreate办法,终究完结页面制作和上屏。

首要进程的功用首要是:

1、Launcher进程:为手机桌面进程,担任接纳用户的点击事情,并将事情告诉到AMS

2、SystemServer进程:担任运用的发动流程调度、进程的创立和办理、窗口的创立和办理(StartingWindow 和 AppWindow) 等,比较中心的服务有AMS和WMS(WindowManagerService);

3、Zygote进程:经过fork创立运用程序进程,Zygote进程在初始化时就会会创立虚拟机,同时把需求的体系类库和资源文件加载到内存中。而Zygote在fork出子进程后,这个子进程也会得到一个现已加载好根底资源的虚拟机,然后加速运用进程的发动;

4、SurfaceFlinger进程:首要和运用的烘托相关,如Vsync信号处理、窗口的合成处理、帧缓冲区办理等。

有了大局的认知和视界后,咱们就能够站在更高的角度,愈加深化的考虑与剖析功用瓶颈,如手机负载合理性、体系资源运用等等,愈加全面的考虑发动功用的优化办法,到达对发动功用的极致优化。

三、优化落地

百度App的发动功用的优化,大致分为三部分,常规优化、根底机制优化和底层技能优化三部分。

3.1 常规优化

假如是事务发展初期,事务的快速迭代较快,此刻的优化会相对简略,极有或许会呈现短时刻内,发动速度进步秒等级的优化作用。发动功用的优化,也是基于对冷发动的了解以及发动使命的梳理,到达快速优化的方针。可经过功用东西,如前文提过的Trace东西、Thor Hook东西,发现耗时较为突出问题,评价是否可经过推迟、异步、删去等办法优化,根据投入产出状况评价工作优先级,到达快速优化发动功用的意图。

百度 App 启动性能优化实践篇

随着发动场景承载事务逐步庞大,手百逐步生长为承载事务最多,体量巨大的航母级移动端运用,庞大事务的预加载不或许彻底去除或许经过异步来处理,此部分是发动功用优化中面临的较大难题,需求有机制批量处理事务预加载问题,因而根底机制中的调度机制逐步衍生出来,处理发动进程不同事务的预加载需求。

3.2 根底机制优化

根底机制优化首要分为调度机制优化、根底组件功用优化。

3.2.1 使命调度优化

事务多,预加载使命的履行诉求各有不同,平衡发动功用和事务预加载,百度App需建设使命调度结构,事务方经过接入可快速优化功用问题。

图片转存失败,建议直接上传图片文件

使命调度全体建设状况如下,现在还处在快速迭代中:

百度 App 启动性能优化实践篇

智能调度能够根据使命输入和信息输入,做出不同的调度反响,如:

1、个性化调度战略:辨认出事务预加载使命ID和用户行为习惯相匹配,则会将使命提早做初始化,使命优先级会做进步,与此同时,在用户进入事务对应页面时,非事务相关使命需做避让;

2、分级体会战略:辨认出在指定的机型装备中有对应的调度战略,则会履行对应的调度才能,如当即调度、推迟调度、不调度等,首要用于体会降级;

3、精细化调度战略:在不同的场景精细化调度事务预加载使命,如在闪屏场景,会辨认闪屏相关事务信息并做预加载,在端外调起场景,会辨认落地页所属事务信息并做对应预加载;

4、分优先级推迟调度:有较很多的使命初始化会依赖于推迟调度,需保证有序操控事务初始化,因而在推迟调度根底上增加优先级概念,能够在推迟调度中也分优先级调度,让更高优先级使命能够更快的履行;

5、主页UI并行烘托调度:首要服务于冷发动阶段商业闪屏事务,商业闪屏是否需求展示、展示哪个物料均是冷发动阶段的实时网络恳求决议的,需在冷发动阶段尽量进步商业网络恳求的可用时刻,然后进步网络恳求成功率,百度App现在能够完结,主页能够先初始化,但不做上屏,待主页烘托事务提交的时分再检查商业闪屏是否展示,做到了供给给商业网络恳求更多可用时刻的同时不堵塞主页初始化,经过此项技能大幅进步商业网络恳求的成功率,带来了商业收入的进步。

由于调度器结构中涉及细节十分多,在这里只简略介绍其间一种调度器的规划:分级体会调度器。

百度 App 启动性能优化实践篇

首要分为3个模块,机型评分、分级装备和分级调度机制,到达不同装备的手机上的最优体会。

  • 机型评分:

  • 经过设备信息计算评分信息,称为静态评分;

  • 经过功用方针计算评分信息,称为动态评分;

  • 根据模型训练评分信息,得出终究机型评分;

  • 分级装备:

  • 云端装备表:供给各事务等级按设备评分条件下的分级装备表,该表支撑动态更新,增量更新,更新后端上及时收效。

  • 本地预置表:本地会预置一份装备表,供初次装置时运用;

  • 根据机型评分信息和分级装备信息得出操控战略;

  • 分级调度:

  • 事务方根据机型评分操控不同的事务逻辑,到达高端机悉数功用最优体会,中端机部分功用杰出体会,低端机中心功用流畅体会,如主页点赞动画在高端机上选择开启战略,中端机上选择推迟加载战略,低端机上选择关闭状况;

3.2.2 KV存储优化

SharedPreferences是Android平台轻量级的存储类,用来保存运用程序的装备信息,其本质是以“键-值”对的办法保存数据的xml文件,其文件保存在/data/data/pkg/shared_prefs目录下,长处是以键值对的办法进行存储,运用方便,易于了解;但SharedPreferences的缺陷很显着,读写功用慢,IO读写运用xml数据格局,全量更新功率低;多进程支撑差,存储数据易丢掉;创立线程多,导致功用差。

读取功用差

每加载一个SP文件均会创立子线程,源码如下:

private final Object mLock = new Object();
private boolean mLoaded = false;
private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}

但是在获取key-value时假如没有加载完结,则会wait等待SP文件加载完结:

public String getString(String key, @Nullable String defValue) {    synchronized (mLock) {        awaitLoadedLocked();        String v = (String)mMap.get(key);        return v != null ? v : defValue;    }}

写入功用差

SP选用XML格局,每次写入是全量更新,功率低,写入供给两种办法:

  • commit:堵塞当时线程办法,修改提交到内存后,等待IO完结,假如主线程运用commit办法,极有或许呈现卡顿;

  • apply:不堵塞当时线程,但也有躲藏的坑,或许会导致主线程的卡顿问题,首要原由于apply办法将写入Runnable加入到QueueWork中,而在Android 四大组件生命周期轮转时,会检查QueueWork是否完结,假如没有完结则会wait,代码如:

 public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,        int configChanges, PendingTransactionActions pendingActions, String reason) {        ......        // 保证写使命都现已完结        QueuedWork.waitToFinish();        ......    }}

因而,在ANR/卡顿监控中能看到十分多的SharedPreferences仓库,看仓库是体系级仓库,但其实是SP apply办法引进的问题,仓库如:

java.lang.Object.wait(Native Method)
java.lang.Thread.parkFor$(Thread.java: ) 
sun.misc.Unsafe.park(Unsafe.java: )
java.util.concurrent.locks.LockSupport.park(LockSupport.java: ) 
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java: ) 
java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java: )
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java: ) 
java.util.concurrent.CountDownLatch.await(CountDownLatch.java: ) 
android.app.SharedPreferencesImpl$EditorImpl$1.run(SharedPreferencesImpl.java: ) 
android.app.QueuedWork.waitToFinish(QueuedWork.java: ) 
android.app.ActivityThread.handleServiceArgs(ActivityThread.java: )
android.app.ActivityThread. - wrap21(ActivityThread.java) 
android.app.ActivityThread$H.handleMessage(ActivityThread.java: ) 
android.os.Handler.dispatchMessage(Handler.java: ) 
ndroid.os.Looper.loop(Looper.java: ) 
ndroid.app.ActivityThread.main(ActivityThread.java: )
java.lang.reflect.Method.invoke(Native Method) 
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java: ) 
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:)

多进程支撑差

当运用MODE_MULTI_PROCESS这个字段时,其实并不牢靠,由于Android内部并没有适宜的机制去防止多个进程所形成的冲突,运用不应该运用它,引荐运用ContentProvider。上面这段介绍咱们得知:多个进程拜访{MODE_MULTI_PROCESS}标识的SharedPreferences时,会形成冲突,举个比方便是,在A进程,明明set了一个key进去,跳到B进程去取,却提示null的错误。

百度 App 启动性能优化实践篇

3.2.2.1 优化计划规划

现在各大厂商也对SP做了必定优化,有保存优化,在SP当时机制根底上做优化,首要是处理写入导致的ANR问题;也有颠覆性优化,比较有代表性的为MMKV和Data Store,但经评价后,或许均有必定问题,因而在百度App的优化中,也是学习借鉴业界干流的处理办法,终究选用两种优化并存的办法:

  • 供给颠覆性优化组件:UniKV,彻底处理原生SP一系列问题,中心场景极致体会,事务方自动接入;

  • 在体系SP机制上做优化,处理写入时ANR等痛点问题,首要服务于未接入UniKV的SP文件;

3.2.2.1.1 UniKV规划

层级规划

百度 App 启动性能优化实践篇

1: 事务运用时直接依赖UniKV,UniKV承继SharedPreferences,对齐原生SP接口;

2: 工程中包含原生完结和UniKV完结,代码中直接依赖原生完结,编译打包时替换为UniKV完结,保证事务中台输出才能;

文件存储格局规划

分位文件头、数据块。文件头40个字节,首要存储版本号、回写次数、保存字段、容灾数据长度、容灾CRC、实践数据长度、实践CRC。

百度 App 启动性能优化实践篇

1:以4KB位单位分配空间,最小占用4KB空间,经过mmap映射文件,操作体系担任数据写入文件;

2:经过容灾数据长度和容灾CRC可做数据恢复;

3:经过保存字段可做功用拓宽,比方是否从SP搬迁成功标识;

百度 App 启动性能优化实践篇

数据块中存储首要数据体,以append办法写入,必要时再做数据整理

1:支撑类型存储,对齐SP原生getAll接口;

2:支撑类型有:BOOL、INT、FLOAT、DOUBLE、SHORT、LONG、STRING、STRING_ARRAY、BYTE_ARRAY9种类型,比较于原生SP完结支撑类型更多;

数据搬迁

数据搬迁进程需求先读取SP内容,再写入KV文件,耗时会较久,写入完结后KV文件才可用,这在线上会有危险,需求处理。

百度 App 启动性能优化实践篇

UniKV中数据搬迁选用不影响事务办法,假如搬迁完结,则会直接运用KV文件,假如未搬迁完结,则继续运用SP文件,并将数据搬迁Runnable提交至线程池。为防止数据搬迁期间SP文件呈现改动导致数据丢掉,注册SP文件更改的数据监听。搬迁完结符号位由保存字段来存储,往往数据搬迁时需求符号位来保存是否搬迁完结的Flag,需求引进其他文件来保存,此处UniKV里的保存字段很好的处理了此问题。

多进程完结

选用mmap机制 + 自定义文件锁完结进程间数据同步,mmap文件至每个进程的内存空间,自定义文件锁首要完结的递归锁和锁的升降级,多进程读时共享锁,多进程写时排他锁,原生文件锁不支撑递归锁,升降级容易死锁或锁会被彻底释放,因而自定义文件锁完结进程间数据同步。关于多进程这块完结,首要学习了MMKV的多进程完结逻辑,感兴趣的能够参看:github.com/Tencent/MMK…

完结作用

彻底处理原生SP的功用问题,读写功用显着进步,支撑多进程读写,削减线程创立,全体功用方针和事务方针均呈现了显着优化。

百度 App 启动性能优化实践篇

3.2.2.1.2 体系SP机制优化

有些SP是在插件、第三方SDK中运用的,因而无法运用UniKV一致优化,需供给一种优化原生SP机制的计划。

优化计划:

百度 App 启动性能优化实践篇

现在百度App 在Android 12上暂未优化,首要原因是Android 12完结办法有改变,代理办法相对杂乱,且开销较大,而SP引起的ANR问题较少,因而暂未上线优化。

优化作用:

此计划对大局均有优化,除了ANR方针有显着下降外,DAU和留存也呈现正向。有同学会忧虑优化后数据写入是否会受影响,咱们经过打点监控到SP写入及时性没有显着改变,而写入成功率出了正向,低端机进步显着,阐明SP优化削减ANR的产生,更多使命被履行,写入成功率进步。

3.2.3 锁优化

多线程功用调优是功用优化中不可防止的话题,为了完结线程同步,加入了同步锁机制(Synchronized同步锁、Lock同步锁等),同步锁的诞生尽管保证了操作的原子性、线程的安全性,但是(比较不加锁的状况下)形成了程序功用下降。所以,咱们这里要做的一件事便是“锁优化”,即既要保证完结锁的功用(即保证多线程下操作安全)又要进步程序功用(即不要让程序由于安全而损失太大功率)。

常见的锁优化办法:

百度 App 启动性能优化实践篇

下面以一个优化项,介绍百度App在锁优化中的实践优化落地。

在项目展开初期,经过Trace东西剖析发现有较多的“monitor contentation XXX”,此部分信息是Android ART虚拟机输出的锁相关信息,其间会包含持有锁的线程、办法、等锁线程、等锁办法。详细如下图所示:

百度 App 启动性能优化实践篇

经剖析,首要是根底组件的AB在初始化时由于synchronized关键字不正确运用导致,需对AB做功用优化,必要时做架构升级。而经过剖析,AB根底组件在多线程、文件IO功用均存在功用问题,因而对AB根底组件做了重构升级,彻底处理功用问题。

百度 App 启动性能优化实践篇

经过优化后,读写选用无锁完结,彻底处理事务运用ABTest组件锁同步问题;兼容新老AB数据,缓存试验开关和试验sid数据,并选用JSON/PB数据格局存储,初次读取功用118ms,优化95%(小米5机器)。

3.2.4 其他根底机制优化

在百度App的发动功用优化中,展开过较多的根底机制相关优化,如:线程优化、IO优化、SO优化、主线程优先级优化、ContentProvider优化、类/图片预加载优化、图片预上传GPU优化等。

线程优化

经过Hook才能编写插件,发现线程运用不规范问题,拟定线程运用规范,如:

1: 事务制止私自设置线程优先级;

2: 供给一致的线程池,防止各事务各一个线程池;

3:优先选择线程池/使命调度器调度,事务制止独自创立线程/线程池;

4: 线程池需防止线程频频创立,参数标准化。

IO优化

经过Hook才能编写插件,发现不合理IO问题,首要包含:

  1. 主线程读写时刻超越100ms,主线程读写时刻过长会导致主线程长耗时问题,严重时或许会导致ANR问题;

  2. 读写buffer过小问题,假如buffer太小,会导致过屡次体系调用和内存复制,read/wirte次数过多,然后影响功用。

SO优化

经过Hook才能编写插件,发现So加载问题,优化不必要的SO加载进程,对于必要的加载,尝试经过异步线程提早战略处理,到达优化功用的意图。

Binder优化

经过Hook才能编写插件,发现Binder通讯相关问题,优化不必要Binder通讯,必要时可经过内存缓存、文件耐久化等办法,到达优化功用的意图。

主线程优先级

主线程的优先级决议了体系为主线程分配的资源,假如线程优先级有问题,被改成了低优先级,极有或许呈现得不到CPU时刻片导致运转慢的问题。在主线程优先级的问题排查中,最有代表性的是事务在为相关子线程设置优先级时误设置了优先级,出问题办法:

Thread t = new Thread();t.start();t.setPriority(3);

Android的古怪陷阱—设置线程优先级导致的微信卡顿惨案:

mp.weixin.qq.com/s/oLz\\_F7z…

在百度App排查优先级设置时,原生库也有更改线程优先级逻辑,也需自动批改,如facebook react库的部分逻辑:

百度 App 启动性能优化实践篇

ContentProvider/FileProvider优化

在Application.attachbaseContext和Application.onCreate之间,会履行installContentProviders办法,在此办法中会履行AndroidManifest中声明的ContentProvider/FileProvider,一般耗时较大的为FileProvider,首要原因是FileProvider初始化时有IO操作。首要优化为将ContentProvder/FileProvider移除,并经过android:process特点做操控,或许经过懒加载办法,必要进程中初始化。

图片prepareToDraw优化

百度 App 启动性能优化实践篇

在Trace东西有会看到RenderThread中履行syncFrameState时会upload XXX Texture相关耗时问题,首先检查在trace里边显现的图片的宽和高,保证图片的巨细不比它显现出来的区域大太多。也能够经过prepareToDraw办法提早触发Bitmap上传GPU操作,这种办法能够使Bitmap在RenderThread空闲的时分提早完结。理想状况下,图片加载库会协助你完结这些;假如你想要自己掌控图片加载,或许需求保证不在制作的时分触发Bitmap上传,能够直接在代码里边调用 prepareToDraw。

或许有的同学比较疑惑,此优化没有优化主线程,会对发动功用有优化吗?答案是能够优化主线程,在发动的前几帧,每一帧耗时均会比较大,而每一帧的使命在RenderThread中以DrawFrame Task运转,假如上一帧的使命没有完结,则会堵塞当时帧的制作,主线程中体现出来的便是draw进程变慢,如nSyncAndDrawFrame履行时长过长。

3.3 底层机制优化

首要经过探究底层的技能,来完结优化功用方针,然后撬动事务价值的方针,此方向风险性相对较高,成本也会较大,需根据详细人力状况及优化作用做终究决议计划。

百度App中现在已尝试过的有VerifyClass优化、CPU Booster优化、GC相关优化等,现在还在探究一些技能点,此部分优化根本为大局优化,会在后续的流畅度专题中为我们揭晓。

四、小结

发动功用优化是相对杂乱的技能方向,不只有较多的事务会和发动功用有千丝万缕的联络,在发动进程中也有十分多的体系行为值得重视与投入,现在百度App发动功用已逐步步入瓶颈期,怎么打破瓶颈并与事务紧密结合,是发动功用优化的应战与机遇。发动功用的优化是不断学习、不断颠覆、不断进步的进程,中心或许会遇到十分多的应战,也会呈现十分多的机遇,因而,发动功用优化永无止境,任重而道远。

—— END——

参考资料:

1、抖音发动优化

heapdump.cn/article/362…

2、快手TTI 管理经验分享

zhuanlan.zhihu.com/p/422859543

3、浅析Android发动优化

/post/718314…

4、MMKV:

github.com/Tencent/MMK…

引荐阅读:

从php5.6到golang1.19-文库App功用跃迁之路

扫光动效在移动端运用实践

Android SDK安全加固问题与剖析

查找语义模型的大规模量化实践

怎么规划一个高效的分布式日志服务平台

视频与图片检索中的多模态语义匹配模型:原理、启示、运用与展望