提升应用的启动速度 和 splash页面的设计

1,242 阅读8分钟
原文链接: mp.weixin.qq.com

当我们开发一个比较大型的APP时,在Application做了很多初始化工作,然后SplashActivity中又做了很多工作,我们点击桌面启动APP时,会发现要很久才能启动起来,这时测试跟产品就会说,怎么APP启动这么慢啊,得想想办法优化啊…哈哈,作为开发的我们,得苦逼得想办法优化了,下面让我讲讲我在工作中对启动APP做的一些优化。

我们知道APP启动,其实有两种方式,一种是冷启动,另一种是热启动。

启动的两种方式
  • 1.冷启动:当直接从桌面上直接启动,同时后台没有该进程的缓存,这个时候系统就需要重新创建一个新的进程并且分配各种资源。这个是最耗时的一种启动方式,这个也是我们作重要优化的。

  • 2.热启动:该app后台有该进程的缓存,这时候启动的进程就属于热启动。热启动不需要重新分配进程,也不会走Application了,直接走的就是app的入口Activity,这样就速度快很多,就是我们平时按HOME 键退回到桌面,然后点击app桌面图标启动APP,这时你会发现启动的很快,这个就是属于热启动。

当我们感觉APP启动很慢时,你是不是会想,我怎么知道他启动是多慢,这时你得想办法测量出APP从开始点击桌面图标到完全启动到底花了多长时间。

如何测量一个应用的启动时间。
使用命令行来启动app,同时进行时间测量。单位:毫秒
adb shell am start -W [PackageName]/[PackageName.MainActivity]adb shell am start -W com.gzsll.hupu/.ui.splash.SplashActivityadb shell am start -W com.example.applicationstartoptimizedemo/com.example.applicationstartoptimizedemo.SplashActivityadb shell am start -W com.dn.splashoptimize/com.dn.splashoptimize.MainActivity
以下是测试的结果试例:
ThisTime: 165 指当前指定的MainActivity的启动时间TotalTime: 165 整个应用的启动时间,Application+Activity的使用的时间。WaitTime: 175 包括系统的影响时间---比较上面大。
应用启动的流程
Application从构造方法开始--->attachBaseContext()--->onCreate()Activity构造方法--->onCreate()--->设置显示界面布局,设置主题、背景等等属性--->onStart()--->onResume()--->显示里面的view(测量、布局、绘制,显示到界面上) 时间花在哪里了?
app启动的耗时主要是在:
Application初始化 + MainActivity的界面加载绘制时间。
减少应用的启动时间的耗时
  • 1)不要在Application的构造方法、attachBaseContext()、onCreate()里面进行初始化耗时操作。
  • 2)MainActivity,由于用户只关心最后的显示的这一帧,对我们的布局的层次要求要减少,自定义控件的话测量、布局、绘制的时间。不要在onCreate、onStart、onResume当中做耗时操作。
  • 3)对于SharedPreference的初始化。因为他初始化的时候是需要将数据全部读取出来放到内存当中。 优化:

    1:可以尽可能减少sp文件数量(IO需要时间);

    2.像这样的初始化最好放到线程里面;

    3.大的数据缓存到数据库里面。

由于MainActivity的业务和布局复杂度非常高,甚至该界面必须要有一些初始化的数据才能显示。 那么这个时候MainActivity就可能半天都出不来,这就给用户感觉app太卡了。 我们要做的就是给用户赶紧利落的体验。点击app就立马弹出我们的界面。 于是乎想到使用SplashActivity--非常简单的一个欢迎页面上面都不干就只显示一个图片。 但是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);
如何设计延迟加载DelayLoad
第一时间想到的就是在onCreate里面调用handler.postDelayed()方法。 问题:这个延迟时间如何控制? 不同的机器启动速度不一样。这个时间如何控制? 假设,需要在splash做一个动画--2S 需要达到的效果:应用已经启动并加载完成,界面已经显示出来了,然后我们再去做其他的事情。 如果我们这样:

    mHandler.postDelayed(new Runnable() {        @Override        public void run() {            mProgressBar.setVisibility(View.GONE);            iv.setVisibility(View.VISIBLE);        }    }, 2500);

是没法做到等应用已经启动并加载完成,界面已经显示出来了,然后我们再去做其他的事情。

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

可以用以下二个方法:

  • 1.onwindowfocuschange

  • 2.ViewTreeObserver

具体的代码出下:

public class MainActivity2 extends FragmentActivity {private Handler mHandler = new Handler();private ViewStub viewStub;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.activity_main2);    final SplashFragment splashFragment = new SplashFragment();    final FragmentTransaction transaction = getSupportFragmentManager()            .beginTransaction();    transaction.replace(R.id.frame, splashFragment);    transaction.commit();    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.inflate();                }            });        }    });    // 2.延迟一段时间做动画,然后把splashfragment移除    getWindow().getDecorView().post(new Runnable() {        @Override        public void run() {            mHandler.postDelayed(new DelayRunnable(MainActivity2.this,                    splashFragment), 2500);        }    });    //3.同时异步预加载数据。    //....}static class DelayRunnable implements Runnable {    private WeakReference<Context> contextRef;    private WeakReference<SplashFragment> fragmentRef;    public DelayRunnable(Context context, SplashFragment splashFragment) {        contextRef = new WeakReference<Context>(context);        fragmentRef = new WeakReference<SplashFragment>(splashFragment);    }    @Override    public void run() {        FragmentActivity context = (FragmentActivity) contextRef.get();        if (context != null) {            SplashFragment splashFragment = fragmentRef.get();            if (splashFragment == null)                return;            final FragmentTransaction transaction = context                    .getSupportFragmentManager().beginTransaction();            transaction.remove(splashFragment);            transaction.commit();        }    }}@Overrideprotected void onDestroy() {    super.onDestroy();    mHandler.removeCallbacksAndMessages(null);}}
xml布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.example.applicationstartoptimizedemo.MainActivity" ><ViewStub    android:id="@+id/content_viewstub"    android:layout="@layout/main_viewstub"//这个就是MainActivity的布局    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>
以上就是我对应用启动的优化,通过优化,明显感觉APP启动快很多.
更多文章可以关注[android的那点事]微信公众号:
android的那点事.jpg