Android性能优化之启动优化

Posted bubbleben

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android性能优化之启动优化相关的知识,希望对你有一定的参考价值。

android性能优化之启动优化

1. 启动窗口优化

Android系统在Activity的窗口尚未启动完成前,会先显示一个启动窗口(Starting Window),等界面的第一帧渲染完成后再从启动窗口切换到真正的界面显示,启动窗口通常情况下为纯黑或者纯白的背景,如果应用启动慢则启动窗口的显示时间就会变长,给用户的感官就是白屏或者黑屏时间长即启动慢,通常会通过windowDisablePreview这个属性禁用系统默认启动窗口,然后为Activity提供自定义的主题背景(尽量使主题背景与和主页一致),这么做的目的主要是为了消除启动时的黑白屏,并不会真正减少用户启动时间,而且可能导致启动时间比不禁用启动窗口的应用更长(启动自己的窗口需要的时间可能要比直接显示系统的启动窗口所花的时间要长)。

[-> ActivityRecord.java]

boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
        CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
        IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
        boolean allowTaskSnapshot, boolean activityCreated) {
        ...
        // If this is a translucent window, then don't show a starting window -- the current
        // effect (a full-screen opaque starting window that fades away to the real contents
        // when it is ready) does not work for this.
        ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Checking theme of starting window: 0x%x", theme);
        if (theme != 0) {
            AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
                    com.android.internal.R.styleable.Window,
                    mWmService.mCurrentUserId);
            if (ent == null) {
                // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
                // see that.
                return false;
            }
            final boolean windowIsTranslucent = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowIsTranslucent, false);
            final boolean windowIsFloating = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowIsFloating, false);
            final boolean windowShowWallpaper = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowShowWallpaper, false);
            final boolean windowDisableStarting = ent.array.getBoolean(
                    com.android.internal.R.styleable.Window_windowDisablePreview, false);
            ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Translucent=%s Floating=%s ShowWallpaper=%s",
                    windowIsTranslucent, windowIsFloating, windowShowWallpaper);
            if (windowIsTranslucent) {
                return false;
            }
            if (windowIsFloating || windowDisableStarting) {
                return false;
            }

从代码中我们可以到,除了windowDisablePreview以外,我们还可以通过配置windowIsFloating或者windowIsTranslucent属性来禁用启动窗口;

2. 布局优化

2.1 减少冗余或嵌套布局

2.2 使用 ViewStub 替代在启动过程中不需要显示的 UI 控件

2.3 使用merge标签减少布局的嵌套层次

3. 线程优化

Java的线程是抢占式的,任一时刻只有一个线程占用CPU并处于运行状态;多线程并发的条件下,虚拟机按照一定的规则进行线程调度并分配CPU使用权;线程优化主要是减少 CPU 调度带来的波动,让启动时间更稳定。如果启动过程中有太多线程,会导致CPU频繁切换反而降低运行效率,尤其是比较低端的机器。同时运行过多的线程会让主线程的 SleepRunnable 状态变多,降低了应用的启动速度;

3.1 异步初始化

Android的任务是以消息的方式驱动的,包括四大组件生命周期在内的所有事件都是在主线程的消息队列中排队执行,如果主线程执行了耗时任务,将会阻塞启动过程导致启动时间增长甚至可能触发ANR,所以需要将IO 、网络等耗时任务放在子线程处理。

异步初始化应当尽量避免通过创建Thread来实现,因为通过Thread创建的线程无法复用,而且频繁创建和销毁会带来更大的开销,而且如果在创建线程时未通过Thread.setName设置线程名称,则无法准确定位线程归属;建议使用Android自带的HandlerThreadIntentServiceAsyncTask等工具也可以用来实现异步;

3.2 延时初始化

除了将耗时任务放在异步线程初始化以外,还可以通过延时初始化的方式将不重要的工作推后进行;通常情况下我们可以通过Handler.postDelayed将任务延时执行,但是缺点比较明显,那就是延时时机不容易把握,如果延时太短可能会影响主线程消息的执行,太长则可能错过用户的交互和界面的展示;

推荐的做法是通过IdleHandler的特性,在消息队列空闲时执行任务:当 MessageQueue 空闲时任务才会被调用,如果返回 true则会一直执行,否则执行完一次后就会从消息队列中移除。

3.3 通过线程池管理线程

通过线程池控制线程的数量:线程数量太多会相互竞争 CPU 资源,从而使 CPU 上下文切换的次数变多,导致分给主线程的时间片减少,从而导致启动速度变慢;线程池除了能够复用线程减少频繁创建和销毁带来的开销以外,还能够提供如定时和任务队列等更强大的并发功能;

3.4 设置子线程优先级

根据任务具体情况来评估优先级,通过Process.setThreadPriority()设置线程优先级:优先级类别定义在Process.java文件中,可以根据实际需要来设置,建议非紧急任务设置为 THREAD_GROUP_BACKGROUND,这样即能保证工作线程运行,也不会阻塞主线程;

[ -> Process.java]

/**
 * Default thread group -
 * has meaning with setProcessGroup() only, cannot be used with setThreadGroup().
 * When used with setProcessGroup(), the group of each thread in the process
 * is conditionally changed based on that thread's current priority, as follows:
 * threads with priority numerically less than THREAD_PRIORITY_BACKGROUND
 * are moved to foreground thread group.  All other threads are left unchanged.
 * @hide
 */
public static final int THREAD_GROUP_DEFAULT = -1;

/**
 * Background thread group - All threads in
 * this group are scheduled with a reduced share of the CPU.
 * Value is same as constant SP_BACKGROUND of enum SchedPolicy.
 * @hide
 */
public static final int THREAD_GROUP_BACKGROUND = 0;

/**
 * Foreground thread group - All threads in
 * this group are scheduled with a normal share of the CPU.
 * Value is same as constant SP_FOREGROUND of enum SchedPolicy.
 * Not used at this level.
 * @hide
 **/
private static final int THREAD_GROUP_FOREGROUND = 1;

/**
 * System thread group.
 * @hide
 **/
public static final int THREAD_GROUP_SYSTEM = 2;

/**
 * Application audio thread group.
 * @hide
 **/
public static final int THREAD_GROUP_AUDIO_APP = 3;

/**
 * System audio thread group.
 * @hide
 **/
public static final int THREAD_GROUP_AUDIO_SYS = 4;

/**
 * Thread group for top foreground app.
 * @hide
 **/
public static final int THREAD_GROUP_TOP_APP = 5;

/**
 * Thread group for RT app.
 * @hide
 **/
public static final int THREAD_GROUP_RT_APP = 6;

/**
 * Thread group for bound foreground services that should
 * have additional CPU restrictions during screen off
 * @hide
 **/
public static final int THREAD_GROUP_RESTRICTED = 7;

4. GC优化

在启动过程中要尽量减少 GC 的次数,避免造成主线程长时间的卡顿;启动过程中要避免创建大量的对象,特别是序列化跟反序列化过程:要避免进行大量的字符串操作,特别是序列化和反序列化;避免对象的频繁创建,需要考虑对象复用;考虑将逻辑转移到 Native 实现;Java 对象的逃逸也很容易引起 GC 问题,应该保证对象生命周期尽量的短,在栈上就进行销毁;

5. IO优化

启动过程中系统负载比较高,有许多系统 IO 都在此时发生,这时候 IO 的性能下降会比较快,此时应用的 IO 操作会比平时更慢一些,尤其是在性能比较差的机器上;

IO 分网络 IO 和磁盘 IO ,启动过程中不建议进行网络 IO ,而对于磁盘 IO 则需要细分:

  1. 需要清楚启动过程中读了什么文件、多少个字节、 Buffer 是多大,使用了多长时间、在什么线程等一系列信息;
  2. 进行启动过程中的 IO 监控,微信在监控 IO 时发现有用户的 db 文件达到了 500MB

6. 厂商优化

6.1 微信

mmkernel

微信Android热补丁实践演进之路:https://mp.weixin.qq.com/s/-NmkSwZu83HAmzKPawdTqQ

6.2 阿里

Alpha

6.3 Facebook

Redex:https://github.com/facebook/redex

6.4 抖音

抖音BoostMultiDex优化实践:https://juejin.cn/post/6844904079206907911

6.5 支付宝

支付宝 App 构建优化解析:通过安装包重排布优化 Android 端启动性能:https://mp.weixin.qq.com/s/79tAFx6zi3JRG-ewoapIVQ

支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」:https://juejin.cn/post/6844903705028853767

6.6 爱奇艺

爱奇艺Android客户端启动优化与分析:https://mp.weixin.qq.com/s/eaArt5Udc4WZ3NoH5RlEkQ

7. 系统优化

7.1 PreFork

Android Q 加入了 PreFork 机制,会先 fork 几个空进程,当应用启动的时候,可以直接复用这几个空进程,而不用重新fork

7.2 启动加速

应用启动时系统会对其做绝对的资源倾斜,比如 CPUIOGPU 等,这一点大家抓个 Systrace 看一下即可,不管是频率还是调度算法,正在启动的 App 绝对是当时的系统 VIP 客户;

7.3 主线程/渲染线程加速

部分厂家会对应用的主线程和渲染线程做特殊对待,比如让他们直接跑到大核上,将其他不重要的线程移到小核;

7.4 iowrap

7.2 启动加速

应用启动时系统会对其做绝对的资源倾斜,比如 CPUIOGPU 等,这一点大家抓个 Systrace 看一下即可,不管是频率还是调度算法,正在启动的 App 绝对是当时的系统 VIP 客户;

7.3 主线程/渲染线程加速

部分厂家会对应用的主线程和渲染线程做特殊对待,比如让他们直接跑到大核上,将其他不重要的线程移到小核;

7.4 iowrap

以上是关于Android性能优化之启动优化的主要内容,如果未能解决你的问题,请参考以下文章

Android性能优化系列之App启动优化

Android性能优化之启动优化

Android性能优化之启动优化

Android性能优化之启动优化

Android性能优化第(八)篇---App启动速度优化之耗时检测处理

Android 面试之必问性能优化