索引
Android中的App启动,是从用户点击应用图标的那一刻起,到应用界面显示出来的过程。这段过程有的应用耗时少,有的应用则耗时多,如果我们想要对那些耗时多的应用进行优化。那么,首先我们需要知道从应用图标被点击,到界面加载出来发生了什么。然后,我们需要知道这段过程中为什么会发生部分耗时长的操作,最后,我们需要知道如何优化一款App的启动速度
目录
- 应用图标被点击到界面加载出来的过程
- 启动中出现的问题及其原因
- 常用的启动优化手段
1. App的启动过程
1.1 App的点击时的启动逻辑
首先,我们要知道Android系统的桌面就是一个应用,我们的应用图标在桌面上的那个界面其实就是一个Activity,当我们的应用图标被点击时,就触发了这个Activity的点击事件
位于com.android.launcher2
包下
public final class Launcher extends Activity
当点击Activity界面上的一个item(也就是一款应用的icon)的时候,会调用它的onClick方法
public void onClick(View v) {
if (v.getWindowToken() == null) {
return;
}
if (!mWorkspace.isFinishedSwitchingState()) {
return;
}
Object tag = v.getTag();
if (tag instanceof ShortcutInfo) {
...
//这里是启动Activity
boolean success = startActivitySafely(v, intent, tag);
if (success && v instanceof BubbleTextView) {
mWaitingForResume = (BubbleTextView) v;
mWaitingForResume.setStayPressed(true);
}
} ...
}
上面代码较多,所以省去其他的逻辑,只需要看到其中一行调用startActivitySafely,就是启动Activity
boolean startActivitySafely(View v, Intent intent, Object tag) {
boolean success = false;
try {
success = startActivity(v, intent, tag);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
}
return success;
}
//从startActivitySafely调用而来的
boolean startActivity(View v, Intent intent, Object tag) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
boolean useLaunchAnimation = (v != null) &&
!intent.hasExtra(INTENT_EXTRA_IGNORE_LAUNCH_ANIMATION);
UserHandle user = (UserHandle) intent.getParcelableExtra(ApplicationInfo.EXTRA_PROFILE);
LauncherApps launcherApps = (LauncherApps)
this.getSystemService(Context.LAUNCHER_APPS_SERVICE);
if (useLaunchAnimation) {
...
} else {
if (user == null || user.equals(android.os.Process.myUserHandle())) {
//这里是真正启动Activity的代码
startActivity(intent);
} else {
launcherApps.startMainActivity(intent.getComponent(), user,
intent.getSourceBounds(), null);
}
}
return true;
} catch (SecurityException e) {
...
}
return false;
}
从startActivitySafely到startActivity,最终会调用到Activity中的startActivity(intent)方法,这个方法就和平时启动Activity是一样的
1.2 startActivity的调用步骤
- 这时的startActivity会开启一个新的app
- 首先会加载一个main函数,并分配一些内存区域(包括方法区,堆区和java栈)
- java栈中会调用到ActivityThread中的main函数,实例化一个Application
- 调用Application的onCreate方法
- 开启MainActivity
2 启动中出现的问题及其原因
2.1 启动白屏
白屏:是由于AppCompat的主题属性:windowBackgroud导致的,他是一个白色的背景
3 启动优化手段
3.1 启动速度
通过Display
这个关键字,就可以在log里获取到系统打印的启动时间
3.1.1 启动速度测试
adb shell am start -W 包名/全类名
参考对比:QQ
adb shell am start -W com.tencent.mobileqq/com.tencent.mobileqq.activity.SplashActivity
启动时就会有时间
调用这句命令时,会走到这个方法
//AM.java
result = mAm.startActivityAndWait(null, null, intent, mimeType,
null, null, 0, mStartFlags, profilerInfo, null, mUserId);
//返回的result里面,就有时间信息
3.2.1 启动速度相关时间
WaitTime
startTime记录的刚准备调用startActivityAndWait()的时间点
endTime记录的是startActivityAndWait()函数调用返回的时间点
WaitTime = startActivityAndWait()调用耗时。
ThisTime和TotalTime
解释下代码里curTime、displayStartTime、mLaunchStartTime三个时间变量.
curTime表示该函数调用的时间点.
displayStartTime表示一连串启动Activity中的最后一个Activity的启动时间点.
mLaunchStartTime表示一连串启动Activity中第一个Activity的启动时间点
正常情况下点击桌面图标只启动一个有界面的 Activity,此时 displayStartTime 与mLaunchStartTime 便指向同一时间点,此时 ThisTime=TotalTime。另一种情况是点击桌面图标应用会先启动一个无界面的 Activity 做逻辑处理,接着又启动一个有界面的Activity,在这种启动一连串 Activity 的情况下(知乎的启动就是属于这种情况),displayStartTime 便指向最后一个 Activity 的开始启动时间点,mLaunchStartTime 指向第一个无界面Activity的开始启动时间点,此时 ThisTime!=TotalTime。
一个应用 Activity pause 的耗时。也就是说,开发者一般只要关心 TotalTime 即可,这个时间才是自己应用真正启动的耗时。
最后总结一下,如果只关心某个应用自身启动耗时,参考TotalTime;如果关心系统启动应用耗时,参考WaitTime;如果关心应用有界面Activity启动耗时,参考ThisTime
ThisTime、TotalTime 的计算在 frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java 文件的 reportLaunchTimeLocked() 函数中
3.2 主题优化(简单)
给splash界面设置主题。例如app启动时启动的是AActivity,那么就给AActivity设置一个单独的主题Launch
然后在AActivity的onCreate方法中,在super.onCreate之前,将主题还原
<!--APP的基础主题-->
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
<!--APP的启动时使用的主题-->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowBackground">@drawable/bg</item>
</style>
然后在第一个显示的activity中,将主题替换回来
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState)
Thread.sleep(2000)//do something
setTheme(R.style.AppTheme);
setContentView(R.layout.activity_main)
}
这样使用,就可以避免app在启动时,耗时过长导致的白屏问题。当然,也可以不使用特定的背景,而使用透明背景,也是可行的。例如
<!--APP的启动时使用的主题-->
<style name="LaunchTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowIsTranslucent">true</item>
</style>
3.3 trace文件优化
File file = new File(Environment.getExternalStorageDirectory(), "app7");
Debug.startMethodTracing(file.getAbsolutePath());
//执行方法
Debug.stopMethodTracing()
通过startMethodTracing方法可以获取trace文件,来分析方法执行的时机和次数
对于那些耗时较长的方法,可以通过以下几种方式优化
1、子线程
2、懒加载
对于那些耗时特别长的方法,可以具体分析,优化代码
总结
到此,App启动也基本总结完了,最好还是那一句,良好的代码习惯才是最重要的
注:此文属作者原创,转载请标明出处
网友评论