Android app 启动优化

Posted 单灿灿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android app 启动优化相关的知识,希望对你有一定的参考价值。

本篇文章已授权微信公众号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一次性绘制的压力。

本文源码gthub地址

以上是关于Android app 启动优化的主要内容,如果未能解决你的问题,请参考以下文章

Android性能优化系列之App启动优化

Android 性能优化的方面方面都在这儿

Android性能优化第(八)篇---App启动速度优化之耗时检测处理

Android APP启动时间优化

Android 应用启动时优化白屏问题

Android 应用启动时优化白屏问题