性能优化的目的不是为了优化而优化,而且为了以后不再优化, 给自己统一 一个标准。
这里也许会有人问 APP 启动还需要优化吗?启动又不是我们自己写的代码,难道 Google 工程师会犯这么低级的错吗?其实这还真不是 Google 的错,应该说是给我们开发者留了一个坑吧。应该有的同学知道是怎么一回事儿了,当我们在系统桌面任意点击一个 APP 是不是会发现启动的时候有一瞬间有白屏出现(以前老版本是黑屏) 那么我们怎么来优化这个黑白屏的问题勒,现在我们先来了解一下 Android 手机重开机到启动 APP 的过程吧。
App启动流程这里会设计到 Android 系统源码的知识,但并不会深入解析源码,我们只是了解一个过程
1、系统启动简易流程
我在这里大致分为了 6 个步骤,下面以流程图为准
启动步骤
- 首先拿到一部 Android 系统的手机打开电源,引导芯片代码加载引导程序 BootLoader 到 RAM 中去执行。
- BootLoader 把操作系统拉起来。
- Linux 内核启动开始系统设置,找到一个 init.rc 文件启动初始化进程,init进程pid=1。
- init 进程初始化和启动属性服务,之后开启 Zygote 进程。
- Zygote 开始创建 JVM 并注册 JNI 方法,开启 SystemServer。
- 启动 Binder 线程池和 SystemServiceManager,开启各种服务ActivitymanegerService,CameraService,WindowManegaer。
开机–>[引导程序BootLoader->负责拉起操作系统]–>linux,init.rc–>init进程pid=1–>zygote[孵化器,创建虚拟机JVM]–>启动进程间通讯的工具SystemServer[打开binder线程池,SystemServiceManeger]–>开启各种服务ActivitymanegerService,CameraService,WindowManegaer–>启动Launch
application开启startActivity把信息告诉系统,然后系统打开孵化器,孵化器把信息传入创建ActivityThread main(),再然后传入Application main();onCreate();–>Activity onCreate();
我们所能改动的是Application main()–>Activity onCreate();这中间的过程
冷启动流程冷启动是无法避免的,因为一个应用要想展示出来,系统需要为它做很多事,而这些总是需要一定的时间,开发者能做的就是将这段等待时间尽可能优雅的给用户缩短或者隐藏起来
那冷启动过程到底发生了什么导致了这些并发症呢,说到这里就要了解下应用的启动流程了:
关于应用启动及进程创建可以参考这几篇文章从源码解析-Android中Activity启动流程包含AIDL使用案例和APP启动闪屏的缘由解析Android中Zygote进程是如何fork一个APP进程从Activity加载源码深入理解ActivityThrad的工作逻辑
当用户在Launcher内点击应用图标,Launcher将这个动作发送给AMS,AMS判断这个应用所在进程不存在,那就需要创建一个新的进程;然后AMS发送消息给Zygote进程,让它创建进程;Zygote进程经过fork之后,分配好应用需要的内存,就通过反射加载ActivityThread类的main方法,接下来进入应用进程了;ActivityThread的main方法会创建应用的Application,加载主题,创建应用第一个Activity,加载布局,进行绘制显示,这样你就看到第一个Activity了,应用就这样启动起来了
问题一:白屏、黑屏原因黑白屏生成原因–>App打开过程中Application到Activity之间有一段加载的空白时间。而这段时间之内如果设置了windowsBackground颜色白色的,就为白屏,没设置的则为黑屏。
进程创建,类加载及Activity启动需要时间,如果啥都不做一直等到Activity渲染完显示,那这个空白期就很尴尬了,用户点了之后,啥都没反应,还停留在手机屏幕,出现了假死现象,用户可能会再次点击,非常影响用户体验。
于是Android就推出了一个预览窗口,这个预览窗口是AMS在启动应用的过程中判断如果我们要切换到一个新的栈,或者下一个Activity的进程不存在,那我们就需要告诉WindowManager展示一个预览窗口;给用户反馈,表明你点击有效;但是这个窗口展示什么呢?
这时候就会将你在manifest文件设置的主题中android:windowBackground 属性来设置到窗口中,你设置的是颜色,那就显示颜色;设置图片背景,那就显示图片;如果没有设置,那就默认是一个白屏或者黑屏
看到这里发现好像严格来说这不是问题,反而是个优化的结果;但是对于追求完美的程序猿来说,这种有损应用体验的情况怎么能存在呢,一定要解决
@color/colorPrimary
@color/colorPrimaryDark
@color/colorAccent
//这一段是导致白屏的代码,点击他的parent链接到他的上一层,一直点
...
@color/background_material_light
...
//通过这段代码可以看到默认窗口背景颜色是白色,所以呈现为白屏
@color/colorPrimary
@color/colorPrimaryDark
@color/colorAccent
...
@drawable/screen_background_selector_dark
...
//溯源代码可以看到窗口背景颜色为黑色,所以呈现为黑屏
三种解决方案:
方案一:开启显示背景颜色或者是设置一张splash图片:
@color/colorAccent
@drawable/splashBg
方案二:设置透明,所以没有白屏或黑屏
true
方案三:禁用窗口的预览动画
1、单独做成一个 AppTheme.Launcher
true
@color/colorAccent
2、在清单文件中 启动 Activity 加入该 主题
3、在启动 Activity 页面中加入
setTheme(R.style.AppTheme_Launcher);
方案二和三的缺点是:点击后界面不立即开启应用,会在桌面停留一会,完成加载后再开启
问题二:利用traceView,定位耗时函数:在我们想要分析的函数中头部和尾部添加如下代码,traceView就会帮助我们记录下该代码块中所有函数的耗时:
File file = new File(Environment.getExternalStorageDirectory(), "app1.trace");
Log.i(TAG, "onCreate: " + file.getAbsolutePath());
//把分析结果存在一个文件
Debug.startMethodTracing(file.getAbsolutePath());
... ...
Debug.stopMethodTracing();
例如想分析onCreate()函数中的耗时函数:
@Override
public void onCreate() {
super.onCreate();
File file = new File(Environment.getExternalStorageDirectory(), "app1.trace");
Log.i(TAG, "onCreate: " + file.getAbsolutePath());
//把分析结果存在一个文件
Debug.startMethodTracing(file.getAbsolutePath());
//对全局属性赋值
mContext = getApplicationContext();
mMainThread = Thread.currentThread();
mMainThreadId = android.os.Process.myTid();
mMainLooper = getMainLooper();
mHandler = new Handler();
initNim();
initImagePicker();
initOkHttp();
Debug.stopMethodTracing();
}
将生成的trace文件pull到电脑后再拖进Android studio中即可:
adb pull /storage/emulated/0/app1.trace
如图所示,我们可以清楚的看到在onCreate中每个初始化函数的耗时时间,我么只需要依次分析下耗时较多的函数里面是否是必须要在onCreate中完成的,如果是非必须的,我们可以开启线程或者放到后面一些执行,以免影响应用启动耗时:
可以点击面板上的TopDown按钮,从大到小排序耗时:
可以非常清楚看到onCreate初始化总耗时56ms,其中的intiNIM函数就占了49ms,占比87%,因此我们后面集中精力优化下这个初始化函数,把不必要的操作移动到后面进行,以免它的初始化占用了过多的启动时间。
通过traceView工具找到耗时函数后,我们根据需要可以把这些耗时操作放到子线程中去:
@Override
public void onCreate() {
super.onCreate();
File file = new File(Environment.getExternalStorageDirectory(), "app1.trace");
Log.i(TAG, "onCreate: " + file.getAbsolutePath());
//把分析结果存在一个文件
Debug.startMethodTracing(file.getAbsolutePath());
//对全局属性赋值
mContext = getApplicationContext();
mMainThread = Thread.currentThread();
mMainThreadId = android.os.Process.myTid();
mMainLooper = getMainLooper();
mHandler = new Handler();
//因为LQRUIKit中已经对ImageLoader进行过初始化了
// initImageLoader(getApplicationContext());
new Thread(){
@Override
public void run() {
//如果要用线程来节约了这些初始化的时间
//1.里面的API不能去创建handler
//2.不能有UI操作
//3.对异步要求不高
initNim();
initImagePicker();
initOkHttp();//可以懒加载
}
}.start();
NIMClient.init(this, loginInfo(), options());
Debug.stopMethodTracing();
}
完成后,再次抓取trace文件打开后会发现,从原来的50ms降低到了14ms的初始耗时,节约了70%的耗时操作。
快捷查看APP启动时间: 方式一:在log中使用Displayed关键字即可如下:依次打开了微信、头条、抖音、京东、淘宝,京东冷启动750ms,淘宝冷启动竟然到了3.2S
adb shell am start -W com.lqr.wechat/com.lqr.wechat.activity.SplashActivity
其中,ThisTime表示启动界面前最后一个activity;TotalTime表示启动界面前所有activity的耗时;如果启动界面前仅启动一个activity那么thistime和totaltime是一样的,如果有多个activity启动total肯定比this要多;waitTime则是包含了activity启动和系统的交互时间,是最长的。
上面三个时间的具体计算类是Am.java: