title: Android app 启动优化
date: 2017-03-12 18:44:12
tags:
- App优化
categories: 日志
本篇文章已授权微信公众号AndroidChinaNet(Android开发中文站)独家发布
在做食生活的项目时,曾遇到也启动页加载很慢,白屏。不知道是什么原因,后来换了一种思想,这个思想也用在了我的另一个项目里,感觉还挺实用的,现在将其总结余一下分享给大家(图片中有一个优化方法哦!)。
(1)Instant Run造成的白屏或者黑屏现象
Instant Run是用来做什么的?就是你点击是用来提升开发效率的,在Android Studio 2.0及以上有了很大改善,使用instant run,在第一次运行之后,就可以快速的在真机中看见修改后的结果,不仅仅是UI可以直接显示,还包括代码逻辑。不用再苦苦等build了!也就是说,只有在开发阶段才会有Instant Run这个东西,在正式的产品中是完全不存在Instant Run的!
所以如果你是直接点击按钮啊或者是debug版本的时候出现白屏或者黑屏,最好生成一个release版的程序就不会出现这种现象了。
(2)App的启动过程
如果想要优化我们的app,那么我们就要深入他,理解他。下面,简单解释一下Activity的启动过程:
1.Application 构造方法
2.attachBaseContext()
3.onCreate()
4.入口Activity的对象构造
5.setTheme() 设置主题等信息
6.入口Activity的onCreate()
7.入口Activity的onStart()
8.入口Activity的onResume()
9.入口Activity的onAttachToWindow()
10.入口Activity的onWindowFocusChanged()
根据上面的信息,我们就能得到一些额如何减少应用启动时的耗时的信息了,是不是很一目了然?
针可上面的过程我们来思考,采取以下策略:
1、在Application的构造器方法、attachBaseContext()、onCreate()方法中不要进行耗时操作的初始化,一些数据预取放在异步线程中,可以采取回调的方法来去实现。
2、对于sp的初始化,因为sp的特性在初始化时候会对数据全部读出来存在内存中,所以这个初始化放在主线程中不合适,反而会延迟应用的启动速度,对于这个还是需要放在异步线程中处理。
3、对于MainActivity,由于在获取到第一帧前,需要对contentView进行测量布局绘制操作,尽量减少布局的层次,考虑StubView的延迟加载策略,当然在onCreate、onStart、onResume方法中避免做耗时操作。
优化应用启动时的体验
对于应用的启动时间,只能是尽量的避免一些耗时的、非必要的操作在主线程中,这样相对可以缩减一部分启动的耗时。
另外一方面在等待第显示的时间里,如加入Activity的background,这个背景会在显示第一帧前提前显示在界面上,可以通过视觉的欺骗,以让人看着启动时间变快了。
方案:通过设置Style
(1)设置背景图Theme
设置一张背景图。 当程序启动时,首先显示这张背景图,背景图的选择最好和主页面色调相近,避免出现黑屏或者白屏。
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:screenOrientation">portrait</item>
<item name="android:windowBackground">>@mipmap/splash</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
(2)设置透明Theme
通过把样式设置为透明,程序启动后不会黑屏而是整个透明了,等到界面初始化完才一次性显示出来
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:screenOrientation">portrait</item>
</style>
这样是不是感觉稍微快了一点啊?
(3)App的启动页的优化过程
1)、 ViewFlipper的巧妙利用。
ViewFlipper是继承至FrameLayout的,所以它是一个Layout里面可以放置多个View(FrameLayout和糖炒栗子一样,是个好东西啊)。
ViewFlipper一般用来实现滑动翻页,经常和Viewpager一起做比较。因为项目中要用到轮播,所以仔细的看了看他们的源码,后来发现切换操作是通过ViewAnimator提供的方法setDisplayedChild(…)实现的。
FrameLayout的布局上下重叠,setDisplayedChild(…)来实现显示某个View或ViewGroup,而其他的都会设置为Gone,进源码看一下:
public void setDisplayedChild(int whichChild) {
mWhichChild = whichChild;
if (whichChild >= getChildCount()) {
mWhichChild = 0;//从0开始,1,2....
} else if (whichChild < 0) {
mWhichChild = getChildCount() - 1;
}
boolean hasFocus = getFocusedChild() != null;
// This will clear old focus if we had it
showOnly(mWhichChild);
if (hasFocus) {
// Try to retake focus if we had it
requestFocus(FOCUS_FORWARD);
}
}
void showOnly(int childIndex) {
final boolean animate = (!mFirstTime || mAnimateFirstTime);
showOnly(childIndex, animate);
}
void showOnly(int childIndex, boolean animate) {
final int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (i == childIndex) {
if (animate && mInAnimation != null) {
child.startAnimation(mInAnimation);
}
child.setVisibility(View.VISIBLE);
mFirstTime = false;
} else {
if (animate && mOutAnimation != null && child.getVisibility() == View.VISIBLE) {
child.startAnimation(mOutAnimation);
} else if (child.getAnimation() == mInAnimation)
child.clearAnimation();
child.setVisibility(View.GONE);//从这里看出来
}
}
}
晒出我的布局,和逻辑处理(很简单,但是很实用)
布局可以使用ViewStub的形式进行懒加载,我这里没有用到。
代码内处理:
2)、 巧用Fragment,Fragment大法就是好
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。官网这张图很好的说明了两者生命周期的关系:
帅气的Fragment拥有自己的生命周期和接收、处理用户的事件,这样就不必在Activity写一堆控件的事件处理的代码了。更为重要的是,你可以动态的添加、替换和移除某个Fragment(解决问题的思路)。
优化方向:
把SplashActivity改成SplashFragment,应用程序的入口仍然是MainActivity,在MainActivity中先展示SplashFragment
当SplashFragment显示完毕后再将它remove,同时在SplashFragment的2S的友好时间内进行网络数据缓存,在窗口加载完毕后,我们加载activity_main的布局,考虑到这个布局有可能比较复杂,耽误View的解析时间,采用ViewStub的形式进行懒加载。
ViewStub是一个轻量级的View,它一个看不见的,不占布局位置,占用资源非常小的控件。可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时候,或是调用了ViewStub.inflate()的时候,ViewStub所向的布局就会被Inflate和实例化,然后ViewStub的布局属性都会传给它所指向的布局。
代码:
AnotherActivity.java
AnotherActivity布局:
SplashFragment.java
SplashFragment布局:
mainlayout.xml布局:
优化思路及办法
1、避免启动页UI的过度绘制,减少UI重复绘制时间,打开设置中的GPU过度绘制开关,界面整体呈现浅色,特别复杂的界面,红色区域也不应该超过全屏幕的四分之一;
2、主线程中的所有SharedPreference能否在非UI线程中进行,SharedPreferences的apply函数需要注意,因为Commit函数会阻塞IO,这个函数虽然执行很快,但是系统会有另外一个线程来负责写操作,当apply频率高的时候,该线程就会比较占用CPU资源。类似的还有统计埋点等,在主线程埋点但异步线程提交,频率高的情况也会出现这样的问题。
3、 对于首次启动的黑屏问题,对于“黑屏”是否可以设计一个.9图片替换掉,间接减少用户等待时间。
+ 4、对于网络错误界面,友好提示界面,使用ViewStub的方式,减少UI一次性绘制的压力。