美文网首页Android高级技术Android开发经验谈Android开发
十五、Android性能优化之提升应用的启动速度和Splash页

十五、Android性能优化之提升应用的启动速度和Splash页

作者: 香沙小熊 | 来源:发表于2017-10-10 18:09 被阅读712次

    1.启动分为两种方式:

    1) 冷启动:当直接从桌面上直接启动,同时后台没有该进程的缓存,这个时候系统就需要重新创建一个新的进程并且分配各种资源。
    2) 热启动:该app后台有该进程的缓存,这时候启动的进程就属于热启动。

    热启动不需要重新分配进程,也不会Application了,直接走的就是app的入口Activity,这样速度就很快

    2.如何测量一个应用的启动时间

           使用命令行来启动app,同时进行时间测量。单位:毫秒
           adb shell am start - W [PackageName] /[PackageName.MainActivity]
           例: adb shell am start -W com.haocai.app/.activity.GuideActivity
    
    冷启动耗时 热启动耗时

    打印的结果为
    ThisTime 该activity启动耗时
    TotalTime 应用自身启动耗时=ThisTime+应用application等资源启动时间
    WaitTime 系统启动应用耗时=TotalTime+系统资源启动时间

    3.应用启动的流程

    Application从构造方法开始 ---> attachBaseContext() ---> onCreate()
    Activity构造方法 ---> onCreate() ---> 设置显示界面布局,设置主题、背景等等属性 ---> onStart()
    ---> onResume() ---> 显示里面的View(测量、布局、绘制,显示到界面上)

    从构造方法我们知道,启动耗时的主要花费在各个启动流程中

    4.减少应用的启动时间的耗时

    根据应用的启动流程,我们从而得到以下减少应用启动耗时操作的建议:

    • 不要在Application的构造方法、attachBaseContext()、onCreate()里面进行初始化耗时操作。
    • MainActivity,由于用户只关心最后显示的这一帧,对我们的布局的层次要求减 自定义控件的测量、布局、绘制的时间。 同时 不要在onCreate、onStart、onResume当中的做耗时操作。
    • 对于SharedPreference的初始化
      因为它初始化的时候是需要将数据全部读取出来放到内存当中。
      优化1:可以尽可能减少sp文件数量(IO需要时间)
      优化2:像这样的初始化最好放到线程里面
      优化3:大量的数据缓存到数据库中

    app启动的耗时主要在:Application初始化 + MainActivity的界面加载绘制时间。

    由于MainActvity的业务和布局复杂度非常高,甚至该界面必须要有一些初始化的数据才能显示。
    那么这个时候MainActivity就可能半天都出不来,这就给用户感觉App太卡了。

    常规方法:

    1.我们要做的就是给用户赶紧利落的体验。点击app就立马弹出我们的界面。
    于是乎想到使用SplashActivity--非常简单的一个欢迎页面上面都不干就只显示一个图片。
    2.但是SplashActivity启动之后,还是需要跳到MainActivity。MainActivity还是需要从头开始加载布局和数据。
    想到SplashActivity里面可以去做一些MainActivity的数据的预加载。然后需要通过意图传到MainActivity。

    更好的优化:

    耗时的问题:Application+Activity的启动及资源加载时间;预加载的数据花的时间。

    如果我们能让这两个时间重叠在一个时间段内并发地做这两个事情就省时间了。
    比如:将SplashActivity和MainActivity合为一个。

    一进来还是显示MainActivity,SplashActivity可以变成一个SplashFragment,然后放一个FrameLayout作为根布局直接显示SplashFragment界面。
    SplashFragment里面非常之简单,就是现实一个图片,启动非常快。

    当SplashFragment显示完毕后再将它remove。同时在splash的2S的友好时间内进行网络数据缓存。

    这个时候我们才看到MainActivity,就不必再去等待网络数据返回了。

    新问题:SplashView和ContentView加载放到一起来做了 ,这可能会影响应用的启动时间?
    解决:可以使用ViewStub延迟加载MainActivity当中的View来达到减轻这个影响。

    viewStub的设计就是为了防止MainActivity的启动加载资源太耗时了。延迟进行加载,不影响启动,用户友好。
    但是viewStub加载也需要时间。等到主界面出来以后。
    viewStub.inflate(xxxx);

    5.如何设计延迟加载DelayLoad

    第一时间想到的就是在onCreate里面调用Handler.postDelayed()方法;
    问题一:这个延时时间如何控制
    不同的机器启动速度不一样,这个时间如何控制?
    假设,先需要splash做一个2s动画,然后在MainActivity中主界面加载完成之后,关闭splash页面
    如果这样写:

            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    mProgressBar.setVisibility(View.GONE);
                    iv.setVisibility(View.VISIBLE);
                }
            }, 2500);
    
    是无法在准确监听页面加载完的

    问题:什么时候应用已经启动并加载完成,界面已经显示出来了。
    采用onResume执行完了之后才显示完毕?不行。

    建议采用getDecorView() 获取上级view 然后添加视图

    综合上诉方案,以下是关键代码:
    public class MainActivity extends AppCompatActivity {
    
        private Handler mHandler = new Handler();
        private SplashFragment splashFragment;
        private ViewStub viewStub;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            splashFragment = new SplashFragment();
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
            transaction.replace(R.id.frame, splashFragment);
            transaction.commit();
    
            /**
             *这么写不好判断视图有没有加载完
             */
            //      mHandler.postDelayed(new Runnable() {
    //          @Override
    //          public void run() {
    //              mProgressBar.setVisibility(View.GONE);
    //              iv.setVisibility(View.VISIBLE);
    //          }
    //      }, 2500);
    
            viewStub = (ViewStub) findViewById(R.id.content_viewstub);
    
            //1.判断当窗体加载完毕的时候,立马再加载真正的布局进来
            getWindow().getDecorView().post(new Runnable() {
    
                @Override
                public void run() {
                    // 开启延迟加载
                    mHandler.post(new Runnable() {
    
                        @Override
                        public void run() {
                            //将viewstub加载进来
                            viewStub.inflate();
                        }
                    });
                }
            });
    
            //2.判断当窗体加载完毕的时候执行,延迟一段时间做动画。
            getWindow().getDecorView().post(new Runnable() {
                @Override
                public void run() {
                    // 开启延迟加载,也可以不用延迟可以立马执行(我这里延迟是为了实现fragment里面的动画效果的耗时)
                    mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment), 2000);
                }
            });
    
        //3.同时进行异步加载数据
            //......
        }
        
    
        static class DelayRunnable implements Runnable {
    
            private WeakReference<Context> contextWeakReference;
            private WeakReference<SplashFragment> splashFragmentWeakReference;
    
            public DelayRunnable(Context context, SplashFragment f) {
                contextWeakReference = new WeakReference<Context>(context);
                splashFragmentWeakReference = new WeakReference<SplashFragment>(f);
            }
    
            @Override
            public void run() {
                //移除Fragment
                if (contextWeakReference != null) {
                    SplashFragment splashFragment = splashFragmentWeakReference.get();
                    if (splashFragment == null) {
                        return;
                    }
                    FragmentActivity activity = (FragmentActivity) contextWeakReference.get();
                    FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
                    transaction.remove(splashFragment);
                    transaction.commit();
                }
    
            }
        }
    }
    

    activity_main.xml

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       >
    
        <ViewStub 
            android:id="@+id/content_viewstub"
            android:layout="@layout/activity_main_viewstub"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    
        <FrameLayout
            android:id="@+id/frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
        </FrameLayout>
    
    </RelativeLayout>
    
    三星手机下显示的耗时
    ps:测试数据是在老款三星手机下所得耗时数据。
    不过相较于SplashActivity+MainActivity启动速度优化还是挺明显的。大家可以在自己手机上试试。
    源码地址:
    SplashOptimize

    特别感谢:
    动脑学院Ricky

    相关文章

      网友评论

      • NIOAG37M:不明白是怎么判断当前窗口已经加载完成,getDecorView获取当前窗口的顶层View,然后post到一个事件,请问如果加载不出来,这个事件就不会执行吗?
        香沙小熊:getDecorView 在这里可以理解为下面这个函数的作用

        //view加载完成时回调
        view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
        // TODO Auto-generated method stub

        }
        });
        香沙小熊:在项目中 SplashFragment 是先加载的, getDecorView 判断的是SplashFragment的是否加载完成

      本文标题:十五、Android性能优化之提升应用的启动速度和Splash页

      本文链接:https://www.haomeiwen.com/subject/ftipyxtx.html