前言
一下子来到了,面试的高潮季。金三银四的三月份;在我们Android开发的众多面试中,扑面而来的超多面试题难道很多程序员。
Android的性能优化,主要是从以下几个方面进行优化的: 稳定(内存溢出、崩溃) 流畅(卡顿) 耗损(耗电、流量) 安装包(APK瘦身) 影响稳定性的原因很多,比如内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等,都会对应用的稳定性造成影响。
本篇我们就来解析,面试题常见面试——Android性能优化之启动优化。
启动优化,常问面试的问题?
1、你做启动优化是怎么做的
2、是怎么异步的,异步遇到问题没有
3、你做了启动优化,觉得有哪些容易忽略的
4、版本迭代导致的启动变慢有好的解决吗
等等一系列演绎出来的面试题。我们直接从0到1;往原理出发到实战来理解启动优化。
如何进行启动优化
冷启动
后台没有该应用的进程,这时系统会首先会创建一个新的进程分配给该应用 由于冷启动过程中,系统和APP的许多工作都要重新开始,所以一般而言这种启动方式是最慢且最具有挑战性的。除了创建和初始化Application和MainActivity之外,冷启动还要加载主题样式Theme,inflate布局,setContentView ,测量、布局、绘制以后显示,我们才看到了屏幕的第一帧。
也就是说当用户点击你的app那一刻到系统调用Activity.onCreate()之间的这个时间段内,WindowManager会先加载app主题样式中的windowBackground做为app的预览元素,然后再真正去加载activity的layout布局。
为什么会出现黑屏白屏呢?
系统进程在创建Application的过程中会产生一个BackgroudWindow,等到App完成了第一次绘制,系统进程才会用MainActivity的界面替换掉原来的BackgroudWindow。
很显然,如果你的application或activity启动的过程太慢,导致系统的BackgroundWindow没有及时被替换,就会出现启动时白屏或黑屏的情况(取决于你的主题是Dark还是Light)。
冷启动优化
1.主题替换
我们在style中自定义一个样式Lancher,在其中放一张背景图片,或是广告图片之类的
1. <style name="AppTheme.Launcher">
2. <item name="android:windowBackground">@drawable/bg</item>
3. </style>
把这个样式设置给启动的Activity
4. <activity
5. android:name=".activity.SplashActivity"
6. android:screenOrientation="portrait"
7. android:theme="@style/AppTheme.Launcher"
5. >
然后在Activity的onCreate方法,把Activity设置回原来的主题
9. @Override
10. protected void onCreate(Bundle savedInstanceState) {
11. //替换为原来的主题,在onCreate之前调用
12. setTheme(R.style.AppTheme);
13. super.onCreate(savedInstanceState);
6. }
这样在启动时就通过给用户看一张图片或是广告来防止黑白屏的尴尬。
还一种方式,就是把windowBackground属性设为null,这样在启动时,backgroundWindow的背景就会变成透明的,给人的感觉就是点了应用图标以后,延迟了一会儿然后加载第一个activity的界面。
1. <style name="AppTheme.Launcher">
2. <item name="android:windowBackground">@null</item>
3. </style>
第二种方式可能会出现黑屏,第一种的图要选取好,不然也会有突兀感,所以很多公司都选择就让他白屏,改变主题实际上是一种伪优化,因为它实质上并没有真正减少App启动的时间 另外还可以设置style
<style name="LaunchStyle" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
或者
<item name="android:windowDisablePreview">true</item>
这个就是直接甩锅个系统
其他方法 就是进入后直接动画,或者是MD模式的效果, GitHub 上的开源项目 saulmm/onboarding-examples-android
Application是程序的主入口
延迟初始化,后台任务,界面预加载在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,一些数据预取放在异步线程中。
数据库,IO操作,密集网络请求不要放在Application的构造方法中,能使用工作线程的尽量使用工作线程,不要在Application的onCreate中创建线程池,因为那样会有比较大的开销,可以考虑延后再创建。 第三方SDK如果主线程中没有立即使用,可以考虑延迟几秒再初始化,总之一句话,尽早让用户看到应用的界面,其他操作都可以先让路。
对于MainActivity,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的层次,考虑StubView的延迟加载策略,当然在onCreate、onStart、onResume方法中避免做耗时操作
- MultiDex以及Tinker的初始化,最先执行;关于MultiDex的优化本文不再赘述,参考我之前Multidex的系列文章。
- Application中主要做了各种三方组件的初始化;
项目中除听云之外其余所有三方组件都抢占先机,在Application主线程初始化。这样的初始化方式肯定是过重的:
- 考虑异步初始化三方组件,不阻塞主线程;
- 延迟部分三方组件的初始化;实际上我们粗粒度的把所有三方组件都放到异步任务里,可能会出现WorkThread中尚未初始化完毕但MainThread中已经使用的错误,因此这种情况建议延迟到使用前再去初始化;
- 而如何开启WorkThread同样也有讲究,这个话题在下文详谈。
项目修改:
- 将友盟、Bugly、听云、GrowingIO、BlockCanary等组件放在WorkThread中初始化;
- 延迟地图定位、ImageLoader、自有统计等组件的初始化:地图及自有统计延迟4秒,此时应用已经打开;而ImageLoader因为调用关系不能异步以及过久延迟,初始化从Application延迟到SplashActivity;而EventBus因为再Activity中使用所以必须在Application中初始化
2.温启动
一台已有该应用的进程,但是启动的入口Activity被干掉了,比如按了back键,应用虽然退出了,但是该应用的进程是依然会保留在后台
3.热启动
一后台已有该应用的进程,比如按下home键,这种在已有进程的情况下,这种启动会从已有的进程中来启动应用
应用的启动时间统计
在现在的公司最近还在搞启动优化,提供2种方式
1.查看启动时间adb 命令
adb shell am start -W [PackageName]/[PackageName.XX.XXXActivity] 这个PackageName 需要你到你自己项目的清单文件去自己找,输入adb命令后,程序会等待你启动然后在统计这个时间,
ThisTime:最后一个启动的Activity的启动耗时; ThisTime:一般和TotalTime时间一样,我们的是中间开了一个TotalTime:自己的所有Activity的启动耗时;包括创建进程+Application初始化+Activity初始化到界面显示WaitTime: ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)一般比TotalTime大点,包括系统影响的耗时
2.使用Android Studio 的日志,在过滤框输入display
然后清空日志,然后启动如下:
Application开始到首页显示出来
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量、布局、绘制显示在界面上
上面这些阶段全部都是在主线程中执行的,任何不经意的操作都可能拖慢应用的启动速度。所以我们不应在Application以及Activity的生命周期回调中做任何费时操作,具体指标大概是你在onCreate,onResume,onStart等回调中所花费的总时间最好不要超过400ms,否则用户在桌面点击你的应用图标后,将感觉到明显的卡顿
针对上面的需求,一般处理是将任务分优先级启动,应用启动开始加载 ;首页渲染后加载;渲染后延迟加载 具体延迟多久 在性能好的手机上面启动非常快,很短的延迟就行了,但是在低端手机上缺很慢,还要为了兼容久手机,一般延长很长的时间,
第一种写法:直接PostDelay 300ms.
myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);
第二种写法:优化的DelayLoad
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});
以上就是Android 性能优化的一些优化方式, Android启动优化更多的是一种优化思路,因为每个项目都不一样,导致优化的点也不一样,但掌握了优化思路可以以不变应万变。 有关更多 【Android核心技术 】及 【Android面试题】可以点击参考这个文档《Android核心技术与面试相关文档》;里面技术板块30几个面试文档50多个。
其他方案优化
1、UI渲染优化,去除重复绘制,减少UI重复绘制时间,打开设置中的GPU过度绘制开关,各界面过度绘制不应超过2.5x;也就是打开此调试开关后,界面整体呈现浅色,特别复杂的界面,红色区域也不应该超过全屏幕的四分之一;
2、根据优先级的划分,KoMobileApplication的一些初始化工作能否将任务优先级划分成3,在首页渲染完成后进行加载,比如:PaySDKManager。
3、主线程中的所有SharedPreference能否在非UI线程中进行,SharedPreferences的apply函数需要注意,因为Commit函数会阻塞IO,这个函数虽然执行很快,但是系统会有另外一个线程来负责写操作,当apply频率高的时候,该线程就会比较占用CPU资源。类似的还有统计埋点等,在主线程埋点但异步线程提交,频率高的情况也会出现这样的问题。
4、检查BaseActivity,不恰当的操作会影响所有子Activity的启动。
5、对于首次启动的黑屏问题,对于“黑屏”是否可以设计一个.9图片替换掉,间接减少用户等待时间。
6、对于网络错误界面,友好提示界面,使用ViewStub的方式,减少UI一次性绘制的压力。 7、Multidex的使用,也是拖慢启动速度的元凶,必须要做优化。后面有空专门写一篇Multidex。