android 提升app启动速度以及Splash界面设计

今天周四了,这周又过去了,感觉时间过的真快啊,今天把之前项目中遇到的一个问题,怎么优化的,写下,感觉这也是很重要的一方面,就是关于app启动优化,在我原来的app中,点击后要过好几秒才看到界面,特别是在一些盒子或者电视性能很差的更为明显.


我们知道app启动分二种,一种是冷启动,一种是热启动,所谓冷启动就是系统没有给这个app分配进程,比如你apk第一次安装启动,或者是你再后台把这个app杀死了,然后杀死了,这都是冷启动,热启动是系统已经给这个app分配了进程,只是你把这个app置为后台了,比如你按home键或者app退出了,这个时候是不会走Application的onCreate()方法的,我这篇博客也有点:http://blog.csdn.net/coderinchina/article/details/77840477


如何测量一个app的启动时间呢?

我们可以通过adb命令的方式来启动app,同时测量时间,单位为毫秒,

adb shell am start -W com.test/com.test.MainActivity

说明com.test是包名,后面的activity是全路径,现在用这个命令分别热启动一次,冷启动一次,因为我app第一个activity就是MainActivity,

冷启动:


热启动


你会明显的发现热启动比冷启动要快很多,当然了你也可以写一个自己的Application去测试下热启动不会走Application的,我之前也知道这个,所以不再写了,

thisTime:是指当前你activity的启动时间

totalTime:是指整个应用的启动时间,包括Applicatin+activity的时间,上面的thisTime=TotalTime是因为我没写Application在demo中.

waitTime:系统的影响时间

app的启动的入口是在ActivityThread中的main()方法开始的,里面有创建Application以及创建Activity,以及Activity的生命周期方法,然后View的测量 布局,绘制到我们的界面上,大概经过这一系列的问题,还有就是你写的BaseActivity很多没用的东西也写在这里,会导致每个子Acivity启动也会去浪费加载无关的资源等,还有就是在主线程中做一些耗时的操作,比如IO操作等,可能在性能差的手机上会卡主线程,


减少app的启动时间

1:不要Applicatin的onCreate()方法做一些耗时的操作,这主要是影响app的冷启动

2:UI布局不要嵌套太深,因为我们知道xml文件最终也是通过xml解析然后创建一个个对象的

3:View最好用能复用的,之前我快搜项目主界面有很多view,而且是不能复用,而且还是三层结构,导致在一些性能差的电视上特别的卡,后面是用recyclerview来做的,,这只是一方面的优化.

4:耗时的操作放在线程中去做.


我们的对Application一般没啥优化的,这里面主要做一些三方框架的初始化工作,主要是集中在MainActivity上,一般MainActivity是整个项目中界面算是比较复杂的一个,如果里面的业务很多,刚进去可能会造成白屏或者黑屏啥的,为什么一般的app都要一个Splash界面呢?,第一是展示下app有关的图片什么的,还有一个重要的地方在于如果你一下子就启动一个重的Activity会导致用户一时看不到界面,这个效果肯定是不好的,一般Splash界面就是显示图片,而且是静态图片,我现在写了一个SplashActivity做我第一个启动的activity,发现有白屏情况,解决办法如下,在styles下定义:

<style name="SplashTheme" parent="AppTheme">
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowNoTitle">true</item>
</style>

然后作用于这个SplashActivity上就把白屏这个bug解决了,

比如在Splash过3秒跳转到MainActivity,也就是说还是要到MainActivity,所以要优化就优化这个地方了,之前项目是在SplashActivity做了一些MainActivity的预加载操作,比如请求接口啊,然后把数据通过Bundle传递到MainActivity中,这样你到MainActivity界面就一眼看到数据了,而不是出现了一直在加载状态,那还能不能优化呢?现在的问题是Application的启动时间以及Splash的启动时间以及资源加载时间,以及预加载数据的时间,如果要优化就是要让这二个时间并发执行,splash执行流程如下:


现在的优化方案是把这二个Activity合并成一个Activity,app一启动还是显示MainActivity,我把SplashActivity变成一个Fragment,我这个SplashFragment就显示一张图片,当SplashFragment显示完毕以后再remove掉,节省内存,然后再显示MainActivity的东西,比如显示进度条去加载数据,这中间我们可以在SplashFragment中去请求MainActivity需要的数据,这样当数据请求成功后,MainActivity显示就有数据了,如果没有请求到的话,就要重新去请求数据了,这其中有个问题就是SplashFragment和MainActivity的setContentView()加载在一起了,可能会影响到app的启动时间,android在给我们UI优化的时间提供了一个ViewStub,使用这个延迟加载MainActivity上的view来减少app启动的时间,


现在又有问题出现了,ViewStub延迟加载也花时间,因为我们使用ViewStub的设计是为了防止MainActivity的启动加载资源太耗时,延迟加载就不会影响app的启动时间,这样用户看到界面就不会很难看了,现在要解决的问题就是如何设计延迟加载的问题了,

说到延迟我们就想到了handler.post()了,那么这个延迟时间怎么设定呢?因为手机的性能不一样,不可能把这个延迟的时间设置死了,我们要的是app启动并加载完成,界面已经出来了,然后再去做其他的事,那么android有什么app启动加载你完成的监听么?,监听窗体加载完毕后再把mainActivity的布局加载进来,动手写一个

SplashFragment就是显示一张图片

public class SplashFragment extends Fragment {
   @Override
   @Nullable
   public View onCreateView(LayoutInflater inflater,
         @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
      return inflater.inflate(R.layout.fragment_splash, container,false);
   }
}

fragment_splash布局中就显示了一个大图,

关键是MainActivity,先看下其布局文件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
     >
    <!--main真正显示的布局内容-->
    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="main界面"
             />
    </FrameLayout>
    <FrameLayout
        android:id="@+id/view_splash"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>
</RelativeLayout>

MainActivity类

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler();
    private ProgressBar mProgressBar;
    private TextView tv_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_content = (TextView) findViewById(R.id.tv_content);
        mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
        final SplashFragment splashFragment = new SplashFragment();
        final FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();
        transaction.replace(R.id.view_splash, splashFragment);
        transaction.commit();
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                mHandler.postDelayed(new DelayLoadDataRunnable(MainActivity.this, splashFragment,
                        mProgressBar,tv_content), 2500);
            }
        });

    }
    static class DelayLoadDataRunnable implements Runnable {
        private WeakReference<Context> contextRef;
        private WeakReference<SplashFragment> fragmentRef;
        private WeakReference<ProgressBar> progressBarRef;
        private WeakReference<TextView> tvRef;
        public DelayLoadDataRunnable(Context context, SplashFragment splashFragment,
                             ProgressBar progressBar,TextView textView) {
            contextRef = new WeakReference<>(context);
            fragmentRef = new WeakReference<>(splashFragment);
            progressBarRef = new WeakReference<>(progressBar);
            tvRef =  new WeakReference<>(textView);
        }
        @Override
        public void run() {
            final  ProgressBar progressBar = progressBarRef.get();
            final TextView tvContent = tvRef.get();
            if (progressBar != null){
                progressBar.postDelayed(new Runnable() {//模拟网络请求
                    @Override
                    public void run() {
                        progressBar.setVisibility(View.GONE);
                        tvContent.setVisibility(View.VISIBLE);
                    }
                },2000);
            }
            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();
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

现在用命令再来看下热启动和冷启动时间

冷启动:


热启动


这个时间跟我们MainActivity没啥业务有关,我们只是讲优化的方法而已,这样做总比你启动splash界面卡然后进入到MainActivity出现什么白屏黑屏的什么的强,现在进入到主界面就是进行数据加载的,UI效果肯定比之前的好, 那么还有优化的空间没有,答案是有的,你看我们的MainActivity的布局:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
     >
    <!--main真正显示的布局内容-->
    <FrameLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center" />
        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="main界面"
            android:visibility="gone"
            android:gravity="center"
            android:textSize="36sp"
             />
    </FrameLayout>
    <FrameLayout
        android:id="@+id/view_splash"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>
</RelativeLayout>

其实我们只要下面的Framelayout,上面的FrameLayout是等我们把SplashFragment移除后再显示的,但是你一进来就加载,系统要给他进行测量,绘制,除非你设置了android:visibility为Gone,那么我就想进来不让他加载上面的FrameLayout,那么我们就用ViewStub延迟加载的方式,首先就要去改他布局了,

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
     >
    <!--main真正显示的布局内容-->
    <ViewStub
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/main_viewstub"
        />
    <FrameLayout
        android:id="@+id/view_splash"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </FrameLayout>
</RelativeLayout>

主界面的布局就是main_viewstub了,这个就不贴了,现在看下MainActivity怎么去使用ViewStub,现在看下MainActivity

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private Handler mHandler = new Handler();
    private ProgressBar mProgressBar;
    private TextView tv_content;
    private ViewStub view_stub_content;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        view_stub_content = (ViewStub) findViewById(R.id.view_stub_content);
        final SplashFragment splashFragment = new SplashFragment();
        final FragmentTransaction transaction = getSupportFragmentManager()
                .beginTransaction();
        transaction.replace(R.id.view_splash, splashFragment);
        transaction.commit();
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        view_stub_content.inflate();//真正去加载主界面布局
                        loadUI();
                    }
                });
            }
        });
        getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                mHandler.post(new DelayLoadDataRunnable(MainActivity.this, splashFragment));
            }
        });
    }

    private void loadUI() {
        tv_content = (TextView)findViewById(R.id.tv_content);
        mProgressBar = (ProgressBar)findViewById(R.id.progressBar);
        mProgressBar.postDelayed(new Runnable() {
            @Override
            public void run() {
                mProgressBar.setVisibility(View.GONE);
                tv_content.setVisibility(View.VISIBLE);
            }
        },2000);//模拟网络请求
    }

    static class DelayLoadDataRunnable implements Runnable {
        private WeakReference<Context> contextRef;
        private WeakReference<SplashFragment> fragmentRef;
        public DelayLoadDataRunnable(Context context, SplashFragment splashFragment) {
            contextRef = new WeakReference<>(context);
            fragmentRef = new WeakReference<>(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();
            }
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

现在看下热启动和冷启动的时间和刚才之前的对比下:

冷启动:


热启动:


你会发现冷启动好像没少什么时间,那是因为我们的MainActivity很简单,看下热启动就少了几十毫秒,如果是MainActivity业务很多时,这个时候就更明显了.好了就写到了这里了,饿了,要吃夜宵.

猜你喜欢

转载自blog.csdn.net/coderinchina/article/details/78744448