App启动优化
1.操作系统启动流程和Launcher点击事件流程
2.启动方式以及首次启动
3.黑白屏优化
4.启动时间内代码优化
操作系统启动
1.打开电源 引导芯片代码加载引导程序Boot Loader到RAM中去执行。
2.BootLoader把操作系统拉起来。
3.Linux 内核启动开始系统设置,找到一个init.rc文件启动初始化进程。
4.init进程初始化和启动属性服务,之后开启Zygote进程。
5.Zygote开始创建JVM并注册JNI方法,开启SystemServer。
6.启动Binder线程沲和SystemServiceManager,并启动各种服务。
7.AMS启动Launcher即android桌面。
BootLoader是在操作系统内核运行之前运行。可以初始化硬件设备、建立内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操作系统内核准备好正确的环境。在嵌入式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。
Launcher桌面图标点击事件
E:\tools\android-src\android-6.0.1_r1\packages\apps\Launcher2\src\com\android\launcher2\Launcher.java
public final class Launcher extends Activity
执行onClick(View view)方法,会把这个应用的相关信息传入
先获取一个intent--->startActivitySafely(v, intent, tag)--->startActivity(v, intent, tag);
-->startActivity(intent);
startActivity(intent)会开一个APP进程(fork zogyte进程),通过systemserver--->调用
ActivityThread.main()。
ActivityThread.java做为入口,用attach开启app,再加载application和activity
thread.attach(false);--->mgr.attachApplication(mAppThread)会通过远端进程去回调private void handleBindApplication(AppBindData data)
Application app = data.info.makeApplication(创建Application对象
mInstrumentation.callApplicationOnCreate(app);---> app.onCreate();--->加载xml
冷启动(Cold start)
冷启动是指APP在手机启动后第一次运行,或者APP进程被kill掉后在再次启动。
可见冷启动的必要条件是该APP进程不存在,这就意味着系统需要创建进程,通过ActivityThread.main()入口启动进程和主线程,创建activity和application并初始化。在这三种启动方式中,冷启动耗时最长。
1. Loading and launching the app.
2. Displaying a blank starting window for the app immediately after launch.
3. Creating the app process.
在创建APP进程之后
1. Creating the app object.
2. Launching the main thread.
3. Creating the main activity.
4. Inflating views.
5. Laying out the screen.
6. Performing the initial draw.
当app进程完成第一次绘制,系统进程会用mainActivity替换当前的背景窗口,这时用户才能使用app。
温启动(Warm start)
场景就类似打开淘宝逛了一圈然后切到微信去聊天去了,过了很久淘宝的进程存在,但是Activity可能被回收。
App进程存在,当时Activity可能因为内存不足被回收。这时候启动App不需要重新创建进程,但是Activity的onCreate还是需要重新执行的。可以从savedInstanceState获取。
热启动(Hot start)
场景就类似你打开微信聊了一会天,这时候出去看了下日历,再次打开微信 。
App进程存在,并且所有的Activity对象仍然存在内存中没有被回收。可以重复避免对象初始化,布局填充和呈现,如果有onTrimMemory()方法,就需要对已回收的对象重新创建。
首次启动
首次启动严格来说也是冷启动,之所以把首次启动单独列出来,一般来说,首次启动会比非首次启动时间长,首次启动会做一些系统初始化工作,如缓存目录的生产,数据库的建立,SharedPreference的初始化,如果存在多 dex 和插件的情况下,首次启动会有一些特殊需要处理的逻辑,而且对启动速度有很大的影响,所以首次启动的速度非常重要,毕竟影响用户对 App 的第一印象。可以类似QQ做一个第一次安装的初始化环境的进度条。
在最近任务给App加锁和启动方式有什么关系
某些厂商为了用户体验提供了给APP上锁的功能,目的就是让用户自己做主是上锁的APP不被杀,启动的时候不会处于冷启动方式,但是加锁也不是万能的,Low memory killer在内存极度吃紧的情况下也会杀死加锁APP,在此启动时也将以冷启动方式运行。
AI和启动方式有什么关系
AI在进程管理方面可谓是大有可为。MIUI10发布了进程AI唤醒功能,是APP启动速度远超友商。这其中的道理简单说就是学习用户的使用习惯,提前将App进程创建好,当用户打开APP时不会出去冷启动。比如你是微信重度用户你发现用了MIUI10就再也见不到微信启动页面的那个地球了,这就是AI唤醒的功劳。
以下是Google官方不建议各种启动时长,
Cold startup takes 5 seconds or longer.
Warm startup takes 2 seconds or longer.
Hot startup takes 1.5 seconds or longer.
黑白屏优化(伪优化)
白屏 <style name="AppTheme" parent="Theme.AppCompat.Light">
黑屏 <style name="AppTheme">(在5.0以前的老版本上有效,现在的版本默认使用透明处理了)
产生时间:Application.Oncreate() 到 Activity.onCreate()加载XML 到 Activity.onPostResume()之间;Activity.onPostResume后App的业务布局页面可见。
产生原因:系统显示一个空白预览窗口,其背景色是主题背景色显示,在Activity.onResume添加View之前
其他原因:手机性能 和 Application.Oncreate()到Activity.onPostResume()之间执行方法耗时过的造成。
因为手机性能我们无法决定,所以只能在背景色和方法性能上去做处理。通常建议黑白屏优化和代码优化结合处理。
只是从体验上优化,不解决或者无法解决性能问题。
修改主题背景色为公司logo图片,或者背景色透明,或者背景色空并且关闭预览(节省一点点内存);然后进入广告倒计时页面,为初始化、广告轮播图下载、页面显示图片或者页面背景图片下载以及各种线性加载提供时间。当然要记得双进程保活更新广告图片,不然就要每次使用时更新图片。
背景色透明,或者背景色空并且关闭预览都给用户一种延时体验感官,官方也不建议关闭预览。
对主题的更改建议通过代码或者xml设置 给启动activity
背景色空节省内存可以在通过代码getWindow().setBackgroundDrawable(null);
经典APP: 京东、QQ、微信
-
设置背景图片
利用<layer-list>设置android:windowBackground 时,
<layer-list>的android:opacity=”opaque”,为了防止在启动的时候出现背景的闪烁。
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowBackground">@drawable/bg</item>
</style>
- 或者设置为透明。
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowIsTranslucent">true</item>
</style>
- 可以看不见但是这里的view仍然存在,而且占的内存依旧存在,没有背景色且关闭预览功能,改善上面的问题
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowBackground">@null</item>
<item name="android:windowDisablePreview">true</item>
</style>
- 上面直接在主题操作会对使每个activity都有效果。其实我们只需要对启动activity设置。
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
<style name="AppTheme.Laucher1">
<item name="android:windowBackground">@null</item>
<item name="android:windowDisablePreview">true</item>
</style>
</resources>
<activity
android:theme="@style/AppTheme.Laucher1"
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
MainActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTheme(R.style.AppTheme_Laucher1);
}
2. 代码优化(真优化)
优化分析
AM
4.4以前 adb shell am start -W com.lqr.wechat/com.lqr.wechat.activity.SplashActivity
ThisTime:最后一个启动的Activity的启动耗时;
TotalTime:自己的所有Activity的启动耗时;
WaitTime: ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)。
AM路径:
E:\tools\android-src\android-6.0.1_r1\frameworks\base\cmds\am\src\com\android\commands\am
Am.java:946行开始打印启动时间信息,其中一个result对象,
初始化 :在871行result = mAm.startActivityAndWait(...)在这个初始化时就已经进行了时间的计算:
计算时间:在android-src\android-6.0.1_r1\frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java
完成计算:void windowsDrawnLocked() --->reportLaunchTimeLocked(SystemClock.uptimeMillis())中完成时间的统计;
Display
4.4版本以后Logcat 输入Display筛选系统日志 不过滤信息No Filters;AM在4.4依旧可以使用,只是display比较方便;
display包含以下时间:
1.Launch the process.
2.Initialize the objects.
3.Create and initialize the activity.
4.Inflate the layout.
5.Draw your application for the first time.
不包含:未在layout中引用的资源和作为对象初始化中一部分的资源
如果整个过程中有其他activity但是未显示就会显示total时间,也就是总时间和单个activity之间存在差异时。
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
reportFullyDrawn()
用户界面已完全加载,并绘制了一些文本,但尚未显示应用程序必须从网络中获取的图像。调用reportFullyDrawn() 让知道你的activity已完成lazy loading。有时也会包含total时间。
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
DisplayedTime和reportFullyDrawn()区分,DisplayedTime是在onPostResume之后官图这点是错的
reportFullyDrawn使用
TraceView分析
Debug.startMethodTracing(filePath);
中间为需要统计执行时间的代码
Debug.stopMethodTracing();
adb pull /storage/emulated/0/app1.trace把文件拉出来分析
把pull到电脑上的文件拖到AS中就可以分析了
Android vitals
分析需要 Google Play Console,可以参照官方文档
Inspect CPU activity with CPU Profiler.
可以参照官方文档,就是Androidstudio的Profiler的CPU模块,可以记录一段时间内代码执行过程和执行时间,个人建议使用也可以参考本人的文章
Overview of system tracing.
可以参照官方文档,短时间内记录设备活动称为 system tracing,System Tracing app是一个android工具,可以将设备活动保存到跟踪文件中。在运行android 10(api级别29)或更高版本的设备上,跟踪文件以perfetto格式保存。在运行早期版本android的设备上,跟踪文件以systrace格式保存。Systrace和Perfetto不会收集应用程序进程中有关代码执行的详细信息。有关应用程序正在执行哪些方法以及使用了多少CPU资源的详细信息。本人文章
优化方案:
1. 开子线程,做初始化,比如第三方SDK
适合不定义handler,不操作UI ,对异步要求不高(主线调用的方法,在子>线程中的初始化完成后才能调用),比如glide和greenDAO的初始化
2. 懒加载,用到的时候再初始化
如:网络OKHttp,数据库操作,这里的网络和数据库初始化比较快,如果像数据库升级这种比较耗时的就不要放在这里了;全局静态对象,建议使用singleton模式或者dagger;ContentProvider中在Static Block中初始化一些UriMatcher。
3. 启动页(Spalsh Screen)或者启动页+广告页倒计时页,启动页尽量和背景图片一样,多图通过<layer-list>处理
不能在子线程初始化的,可以在这里初始化,支持单例,application全局化提供与不提供都可以,还有首页图片资源下载。
以下是广告倒计时页图片下载流程,
4. 升级引导页
下载插件后解压和升级过程,包括数据库升级也可以通过升级引导页完成,类似QQ第一次安装后启动时的初始化环境页面。
5. 减少冗余布局或者减少嵌套布局来展示视图层次结构
比如用ConstraintLayout替代linearlayout嵌套
6. 使用viewstub替代不即时显示部分
使用viewstub对象作为子层次结构的占位符,可以在更合适的时间对其进行扩展。
7.dex问题处理
随着代码数量的膨胀,工程本身的代码加上引用的第三方库的代码中方法数量会超过65536的限制。是由于DEX文件格式限制,一个DEX文件中method个数采用使用原生类型short来索引文件中的方法,也就是4个字节共计最多表达65536个method,field/class的个数也均有此限制。 Google为构建超过65K方法数的应用提供官方支持的方案:MultiDex。
但是在Dalvik下MultiDex有个问题:5.0以下某些低端机会出现ANR或者长时间卡顿不进入引导页,而罪魁祸首是MultiDex.install(Context context)的dexopt过程耗时过长。因此需要在初次启动时做特别处理。
而5.0以上会使用ART,在ART下MultiDex是不存在这个问题的,这主要是因为ART下采用Ahead-of-time (AOT) compilation技术,系统在APK的安装过程中会使用自带的dex2oat工具对APK中可用的DEX文件进行编译并生成一个可在本地机器上运行的文件,这样能提高应用的启动速度,只是在安装过程中进行了处理这样会影响应用的安装速度。
4.2解决思路
1、在Application.attachBaseContext(Context base)中,判断是否初次启动,以及系统版本是否小于5.0,如果是,跳到2;否则,直接执行MultiDex.install(Context context)。
2、开启一个新进程,在这个进程中执行MultiDex.install(Context context)。执行完毕,唤醒主进程,自身结束。主进程在开启新进程后,自身是挂起的,直到被唤醒。
3、唤醒的主进程继续执行初始化操作。
问题总结:
布局太过复杂
I/O流、序列化和反序列化、网络操作阻塞屏幕绘制
加载解码位图
光栅化矢量图(VectorDrawable)
其他子系统初始化