Android 的发动有热发动和冷发动,热发动是比较快的,因为热发动是从后台切到前台,使用的 Activity 还驻留在内存中,无需重复履行方针初始化,创立 Applicaiton 和烘托布局等操作,而冷发动经历了创立进程,发动使用和制作界面等一系列流程,耗时较长,所以,发动优化一般是以冷发动速度为指标的,下面,基于冷发动,剖析一下咱们能做哪些优化 ?

发动流程

  1. 点击 APP 桌面图标时,Launcher 的 startActivity 办法,经过 Binder 通讯,向 system_server 进程中 AMS 服务发起发动恳求。
  2. system_server 进程接收到恳求后,向 Zygote 进程发送创立进程的恳求。
  3. Zygote 进程 fork 出 App 进程,并履行 ActivityThread 的 main 办法,创立 ActivityThread 线程,初始化 Looper,主线程 Handler,一起初始化 ApplicationThread 用于和 AMS 通讯交互。
  4. App 进程,经过 Binder 向 sytem_server 进程发起 attachApplication 恳求,将 ApplicationThread 方针与 AMS 绑定。
  5. system_server 进程在收到的恳求后,进行一些准备工作后,再经过 binder 向 App 进程发送handleBindApplication 和 scheduleLaunchActivity 恳求,用于初始化 Application 和创立发动 Activity。
  6. App 进程的在收到恳求后,经过 Handler 向主线程发送 BIND_APPLICATION 和 LAUNCH_ACTIVITY 消息,这里是 AMS 和主线程的内部类 ApplicationThread 经过 Binder 通讯,ApplicationThread 再和主线程经过 Handler 消息交互。
  7. 主线程在收到消息后,创立 Application 并调用 onCreate 办法,再经过反射机制创立方针 Activity,并回调Activity.onCreate 等办法。
  8. App 正式发动,开端进入 Activity 生命周期,履行完 onCreate,onStart,onResume 办法,UI 烘托后显示 APP 主界面。

Android 启动优化,看过来 ~

丈量办法

adb 指令丈量

终端输入 adb shell am start -W packageName/packageName.FirstActivity,就能输出使用的发动时刻,但这种办法只适合线下丈量。

Android 启动优化,看过来 ~

TotalTime 表明所有 Activity 发动耗时,WaitTime 表明 AMS 发动 Activity 的总耗时。

埋点丈量

使用埋点丈量进行用户数据的收集,能够很方便地带到线上,把数据上报给服务器。

界说一个计时器

class StartTimer {
    companion object {
        const val tag = "StartTimer"
        private var time = 0L
        fun startRecord() {
            time = System.currentTimeMillis()
        }
        fun stopRecord() {
            val consume = System.currentTimeMillis() - time
            Log.i(tag, "start timer: $consume")
        }
    }
}

开端记载的方位放在 Application 的 attachBaseContext 办法中,attachBaseContext 是咱们使用能接收到的最早的一个生命周期回调办法。

class MyApplication : Application() {
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        StartTimer.startRecord()
    }
}

停止记载的时机是要等实在的数据展现出来,比方,首屏 Activity 中有个 TextView

val textView: TextView = findViewById(R.id.textView)
textView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
    override fun onPreDraw(): Boolean {
        textView.viewTreeObserver.removeOnPreDrawListener(this)
        StartTimer.stopRecord()
        return false
    }
})

剖析东西

得出了发动时刻后,咱们需求知道哪里比较耗时,这里使用 Debug 和 Android Studio 自带的 Profiler。经过 Debug 的 startMethodTracing 开端跟踪,记载一段时刻内的 CPU 使用情况,然后调用 stopMethodTracing 停止跟踪后,这时就会生成一个文件,咱们能够经过 Profiler 查看这个文件记载的内容。

举个例子,咱们在 MyApplication 的 onCreate 办法里追寻,这里就简单履行一个计算办法。

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        val path = getExternalFilesDir(null)?.path + "/launch.trace"
        Debug.startMethodTracing(path)
        calculate()
        Debug.stopMethodTracing()
    }
    private fun calculate() {
        var x = 0
        for (i in 0..1000) {
            x += i
        }
    }
}

运行之后,咱们能够在对应的文件夹下找到 trace 文件,将它拿出来。

Android 启动优化,看过来 ~

然后咱们翻开 Profiler,加载这个文件。

Android 启动优化,看过来 ~

然后就能看到文件记载的内容了,详尽到每个办法。

Android 启动优化,看过来 ~

优化办法

闪屏页

咱们知道,APP 发动的时分会创立一个空白的 Window,闪屏页就是利用这个 Window 来显示占位图。这个实际上并没有加速发动速度,只是从用户的感官上进行优化,让用户感觉是快了一点。

比方咱们有个 LaunchActivity,作为发动页咱们就显示一张图,然后咱们一起将这个图片设置到主题上

<style name="launch" parent="Theme.MaterialComponents.DayNight.NoActionBar">
    <item name="android:background">@drawable/launch_img</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowNoTitle">true</item>
</style>

然后将其设置到 LaunchActivity 中

<activity
    android:name=".LaunchActivity"
    android:exported="true"
    android:theme="@style/launch">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

作用如下,咱们能够看到,空白的 window 现已被发动图占用了,用户感官上相对好些。

Android 启动优化,看过来 ~

异步初始化

一般咱们会在 Application 的 onCreate 里做一些初始化的操作,这些初始化或许每个耗时并不多,可是串行履行,累加起来,总耗时就不必定少了。有些使命不需求主线程履行的,咱们能够把它放到子线程上,这样能够加速发动速度。

比方,咱们在 Application 中履行初始化操作,这里用线程睡觉来模仿耗费的时刻,然后用上面埋点丈量的办法得到发动时长。

class MyApplication : Application() {
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        // 开端记载
        StartTimer.startRecord()
    }
    override fun onCreate() {
        super.onCreate()
        initialization()
    }
    private fun initialization() {
        // 模仿初始化使命耗时
        Thread.sleep(666)
    }
}
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val textView: TextView = findViewById(R.id.textView)
        textView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
            override fun onPreDraw(): Boolean {
                textView.viewTreeObserver.removeOnPreDrawListener(this)
                // 停止记载
                StartTimer.stopRecord()
                return false
            }
        })
    }
}

得到的发动时长如下

Android 启动优化,看过来 ~

然后咱们把这个初始化使命放到子线程中

class MyApplication : Application() {
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        // 开端记载
        StartTimer.startRecord()
    }
    override fun onCreate() {
        super.onCreate()
        thread {
            initialization()
        }
    }
    private fun initialization() {
        // 模仿初始化使命耗时
        Thread.sleep(666)
    }
}

作用仍是比较明显的,得到的发动时长如下

Android 启动优化,看过来 ~

懒加载

对于暂时不会用到的库,能够先不去初始化,等到要用到的时分再去初始化,实现懒加载的目的。

ContentProvider 优化

Application 的发动流程中,会顺次履行 Application.attachBaseContext,ContentProvider.onCreate,Application.onCreate。所以,ContentProvider 的 onCreate 办法中,要防止耗时操作,否则会拖慢发动速度。例如,加载数据库是属于耗时操作,咱们不应该放在 ContentProvider 的 onCreate 办法中,能够把该操作放到查询中,或者等到发动完成后延时加载。

首屏 Activity 优化

在制作榜首帧之前,主线程会履行 Activity 的 onCreate,onStart,onResume 这三个办法,所以在这三个办法中,主线程不能有耗时操作。