Android 的发动有热发动和冷发动,热发动是比较快的,因为热发动是从后台切到前台,使用的 Activity 还驻留在内存中,无需重复履行方针初始化,创立 Applicaiton 和烘托布局等操作,而冷发动经历了创立进程,发动使用和制作界面等一系列流程,耗时较长,所以,发动优化一般是以冷发动速度为指标的,下面,基于冷发动,剖析一下咱们能做哪些优化 ?
发动流程
- 点击 APP 桌面图标时,Launcher 的 startActivity 办法,经过 Binder 通讯,向 system_server 进程中 AMS 服务发起发动恳求。
- system_server 进程接收到恳求后,向 Zygote 进程发送创立进程的恳求。
- Zygote 进程 fork 出 App 进程,并履行 ActivityThread 的 main 办法,创立 ActivityThread 线程,初始化 Looper,主线程 Handler,一起初始化 ApplicationThread 用于和 AMS 通讯交互。
- App 进程,经过 Binder 向 sytem_server 进程发起 attachApplication 恳求,将 ApplicationThread 方针与 AMS 绑定。
- system_server 进程在收到的恳求后,进行一些准备工作后,再经过 binder 向 App 进程发送handleBindApplication 和 scheduleLaunchActivity 恳求,用于初始化 Application 和创立发动 Activity。
- App 进程的在收到恳求后,经过 Handler 向主线程发送 BIND_APPLICATION 和 LAUNCH_ACTIVITY 消息,这里是 AMS 和主线程的内部类 ApplicationThread 经过 Binder 通讯,ApplicationThread 再和主线程经过 Handler 消息交互。
- 主线程在收到消息后,创立 Application 并调用 onCreate 办法,再经过反射机制创立方针 Activity,并回调Activity.onCreate 等办法。
- App 正式发动,开端进入 Activity 生命周期,履行完 onCreate,onStart,onResume 办法,UI 烘托后显示 APP 主界面。
丈量办法
adb 指令丈量
终端输入 adb shell am start -W packageName/packageName.FirstActivity,就能输出使用的发动时刻,但这种办法只适合线下丈量。
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 文件,将它拿出来。
然后咱们翻开 Profiler,加载这个文件。
然后就能看到文件记载的内容了,详尽到每个办法。
优化办法
闪屏页
咱们知道,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 现已被发动图占用了,用户感官上相对好些。
异步初始化
一般咱们会在 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
}
})
}
}
得到的发动时长如下
然后咱们把这个初始化使命放到子线程中
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)
}
}
作用仍是比较明显的,得到的发动时长如下
懒加载
对于暂时不会用到的库,能够先不去初始化,等到要用到的时分再去初始化,实现懒加载的目的。
ContentProvider 优化
Application 的发动流程中,会顺次履行 Application.attachBaseContext,ContentProvider.onCreate,Application.onCreate。所以,ContentProvider 的 onCreate 办法中,要防止耗时操作,否则会拖慢发动速度。例如,加载数据库是属于耗时操作,咱们不应该放在 ContentProvider 的 onCreate 办法中,能够把该操作放到查询中,或者等到发动完成后延时加载。
首屏 Activity 优化
在制作榜首帧之前,主线程会履行 Activity 的 onCreate,onStart,onResume 这三个办法,所以在这三个办法中,主线程不能有耗时操作。