今天周四了,这周又过去了,感觉时间过的真快啊,今天把之前项目中遇到的一个问题,怎么优化的,写下,感觉这也是很重要的一方面,就是关于app启动优化,在我原来的app中,点击后要过好几秒才看到界面,特别是在一些盒子或者电视性能很差的更为明显.
我们知道app启动分二种,一种是冷启动,一种是热启动,所谓冷启动就是系统没有给这个app分配进程,比如你apk第一次安装启动,或者是你再后台把这个app杀死了,然后杀死了,这都是冷启动,热启动是系统已经给这个app分配了进程,只是你把这个app置为后台了,比如你按home键或者app退出了,这个时候是不会走Application的onCreate()方法的,我这篇博客也有点:http://blog.youkuaiyun.com/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业务很多时,这个时候就更明显了.好了就写到了这里了,饿了,要吃夜宵.