【Android进阶笔记】APP启动优化

379 阅读4分钟

1. 视觉优化

1.1. 基本概念

冷启动时,系统有三个任务:

  • 加载并启动应用程序
  • 启动后立即显示应用程序空白启动窗口
  • 创建应用程序进程

应用程序进程创建后,就会负责下一阶段:

  • 创建APP对象
  • 启动主线程
  • 创建应用入口的Activity对象
  • 填充加载布局的Views
  • 在屏幕上执行View的绘制过程 measure-layout-draw

完成第一次绘制之后,系统进程会交换当前显示的背景窗口,将其替换为主Activity,此时用户才可以真正使用该应用程序

视觉优化就是在冷启动的第1~2阶段,设置空白启动窗口的主题,来从视觉层面使黑白屏消失,但是对APP启动速度没有任何改善

1.2. 默认情况

如果对APP没做任何处理,即使用默认主题,则会有黑白屏问题。大概是onWindowFocusChanged回调的时候,启动窗口消失

1.3. 透明主题优化

为Application设置一个透明背景主题,例如

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowIsTranslucent">true</item>
</style>            

虽然没有白屏,但是点击APP图标到首个页面呈现,仍然会有视觉延迟

1.4. 图片主题

为Application设置一个闪屏图片的主题,例如

<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <item name="android:windowBackground">@drawable/lunch</item>
    <item name="android:windowFullscreen">true</item>
    <!--显示虚拟按键,并腾出空间-->
    <item name="android:windowDrawsSystemBarBackgrounds">false</item>
</style>

这样设置之后,如果闪屏图片和闪屏Activity长得一模一样,就可以无缝衔接。


2. 代码优化

针对视觉优化的治标不治本,根本的解决办法还是在于代码上优化

2.1. 冷启动耗时统计

2.1.1. 方法一:adb

adb shell am start -S -W 包名/类的全路径限定名,-S表示重启当前应用

dst07130:~ renpeng$ adb shell am start -S -W com.pren.androidtest/com.pren.androidtest.MainActivity
Stopping: com.pren.androidtest
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.pren.androidtest/.MainActivity }
Status: ok
Activity: com.pren.androidtest/.MainActivity
ThisTime: 594
TotalTime: 594
WaitTime: 638
Complete

三个Time的含义:

  • ThisTime:最后一个Activity的启动耗时,即命令里面输入的那个Activity(冷启动优化关注)
  • TotalTime:启动一连串的Activity的总耗时
  • WaitTime:创建应用进程的耗时 + TotalTime(用户感知)

2.1.2. 方法二:Logcat

Logcat 输入“Display”筛选系统日志,不过滤信息“No Filters”,不过滤层级“Verbose”

显示的耗时为ThisTime

2.2. Application优化

很多第三方组件都在Application的onCreact中抢占先机完成初始化

但在Application中完成繁重的初始化操作和复杂的逻辑就会影响到启动性能例如:

  • 复杂且繁琐的初始化布局
  • 阻塞主线程的操作,如I/O、数据库等
  • 加载Bitmap大图片或者VectorDrawable等
  • 其他占用主线程的操作

优化一般分为三类:

  • 只能在主线程且必须立即执行——无法优化
  • 只能在主线程但不必立即执行——延迟
handler.postDelayed(new Runnable() {
        @Override
        public void run() { /* ... */ }
    }, 3000);
  • 可以不在主线程中执行—子线程
new Thread(new Runnable() {
        @Override
        public void run() {
            // 设置线程的优先级,不与主线程抢资源
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            // 子线程延迟执行,如Bugly、x5、SP、友盟等初始化
            // 建议延迟,可以发现是否影响其它功能,或者是崩溃!
            Thread.sleep(5000);
        }
    }).start();

2.3. 闪屏页优化

只能在主线程且必须立即执行无法优化的,则可以增加一个闪屏页面抵消掉它的影响

目标闪屏总时间 = 空白页时间 + 真实闪屏时间

Application初始化完成后会调用attachBaseContext方法(它是整个APP中回调的第一个方法),再回调Application的onCreate方法,所以就可以在Application中记录启动时间。

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // 可以使用SP,也可以使用全局变量记录开始的时间
    long startTime = System.currentTimeMillis();
}

Activity获取用户触摸事件和View绘制完毕(首帧回执完毕,但不代表数据完成展示),会回调onWindowFocusChanged方法,所以就可以计算APP启动耗时了。

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    // 从application到入口Acitity的时间
    long diffTime = System.currentTimeMillis() - startTime;
    // 所以闪屏页展示的时间为 目标闪屏总时间 - diffTime.
}

这样就能动态设置闪屏的显示时间,让不同性能的手机闪屏时间尽量保持一致。