Android APP启动优化的简单探讨
APP启动时,会出现一段时间的白屏/黑屏,主要原因就是Application在初始化的时候做了太多的工作。网上有很多关于启动优化的策略,像更改AppTheme设置window背景的方法,都是治标不治本的。这篇文章主要记录下从实质上减少Application初始化的时间的简单方法,主要思路就是异步初始化一些不那么重要的功能。
利用trace查看Application中的耗时操作
我们首先使用Google Debug类中的方法来定位在Application的onCreate()中,那些操作是耗时的。使用方法很简单,在onCreate()前后打上标签即可。
@Override
public void onCreate() {
Debug.startMethodTracing("xxxApp");
······
Debug.stopMethodTracing();
}
然后跑一遍我们的应用,在sdcard/Android/date/应用包名目录下会生成一个“xxxApp.trace”的文件,我们可以将该文件放入DDMS打开。如果是Mac系统,DDMS可能无法使用,我们可以通过AndroidStudio右下角的DeviceFileExplorer工具,找到“xxxApp.trace”文件右键打开即可。
我们通过按时间排序,就可以看到我们的应用中有哪些操作是耗时的。我们可以发现,像TalkingData、友盟分享、极光推送等SDK的初始化工作都是非常耗时的,我们可以考虑将其放入子线程中异步进行。至于哪些操作可以异步进行,判断原则就是:即使没有初始化完成,也不会影响APP进入主页面。所以像友盟分享、极光推送这类服务,用户不可能一进App就去立刻点击分享,也不需要第一时间接受推送通知,晚一两秒中并不会对我们应用的整体功能产生影响,所以我们可以选择将其异步初始化。
不是耗时操作没有必要也异步进行 ,不会实质性的加快App启动,而且有时候开启一个异步线程也是需要花销的。除了代码看起来爽一点,个人觉得没啥必要。
子线程中初始化的方法
在Android中开启一个异步操作的方法有很多,这个可以自己去研究下。推荐看下这篇文章:Android性能优化(十一)之正确的异步姿势
我在项目中选择的是采用IntentService的方法,IntentService比较方便,start之后便不用管它了,毕竟我不需要任何回执。
@SuppressLint("Registered")
class InitializeService : IntentService("InitializeService") {
companion object {
const val ACTION_INIT_APP = "com.xxx.xxx.service.action.INIT_APP"
fun start(context: Context) {
val intent = Intent(context, InitializeService::class.java)
intent.action = ACTION_INIT_APP
context.startService(intent)
}
}
override fun onHandleIntent(intent: Intent?) {
intent?.run {
if (ACTION_INIT_APP == intent.action) {
performInit()
}
}
}
private fun performInit() {
// 初始化友盟第三方分享登录
UMShareAPI.get(this)
// 初始化 JPush
JPushInterface.init(this)
······
}
}
对比优化后的数据
这个我们主要从启动时间来判断。
1、通过之前说过的方法,利用.trace文件可以看到启动时间的差异
2、通过ADB命令:adb shell am start -W com.xxx.xxx/com.xxx.xxx.activity.LaunchActivity
*注意:请确保App未打开,利用冷启动的方式获取数据;你打开的Activity应该是标记为"android.intent.category.LAUNCHER"的Activity
其中ThisTime是该Activit启动的时间,TotalTime是包含了Application启动的时间,WaitTime是系统资源启动耗时。我们可以通过判断TotalTime是否有变小,来判断优化是否有用。
为什么选用IntentService
有人可能觉得,既然是子线程初始化,那我用RxJava不是更简单方便吗?我一开始也是这么觉得的,直到遇到了一个坑。某些初始化工作可能需要用到handler这个东西,大家都知道,handler在子线程中使用时,是需要自己建一个Loop的,然后调用prepare()方法的,否则就会报错。但是IntentService在内部已经定义好了Loop,所以就不会出现这样的问题。当然,你也可以使用RxJava进行线程调度,但是有些初始化方法并不能简单地提取出来。