应用启动速度在日常开发中也是影响用户体验最重要的一环,市场上很多的应用启动的时候可能都会花上3,4秒,其实这是很影响用户体验的,用户等不了那么长的时间去等待界面加载,用户就会难免会卸载该应用,造成损失。
1)冷启动:当直接从桌面上直接启动,同时后台没有该进程的缓存,这个时候系统就需要重新创建一个新的进程并且分配各种资源。 2)热启动:该app后台有该进程的缓存,这时候启动的进程就属于热启动。
使用命令行来启动app,同时进行时间测量。单位:毫秒
adb shell am start -W [PackageName]/[PackageName.MainActivity] adb shell am start -W com.gzsll.hupu/.ui.splash.SplashActivity adb shell am start -W com.example.applicationstartoptimizedemo/com.example.applicationstartoptimizedemo.SplashActivity adb shell am start -W com.kiven.splashoptimize/com.kiven.splashoptimize.MainActivity ThisTime: 165 指当前指定的MainActivity的启动时间 TotalTime: 165 整个应用的启动时间,Application+Activity的使用的时间。 WaitTime: 175 包括系统的影响时间---比较上面大。Application从构造方法开始—>attachBaseContext()—>onCreate()—>Activity构造方法—>onCreate()—>设置显示界面布局,设置主题、背景等等属性—>onStart()—>onResume()—>显示里面的view(测量、布局、绘制,显示到界面上)
1)、不要在Application的构造方法、attachBaseContext()、onCreate()里面进行初始化耗时操作。 2)、MainActivity,由于用户只关心最后的显示的这一帧,对我们的布局的层次要求要减少,自定义控件的话测量、布局、绘制的时间,不要在onCreate、onStart、onResume当中做耗时操作。 3)、对于SharedPreference的初始化,因为他初始化的时候是需要将数据全部读取出来放到内存当中。 优化1:可以尽可能减少sp文件数量(IO需要时间); 优化2:像这样的初始化最好放到线程里面; 优化3:大的数据缓存到数据库里面。 总的来说,app启动的耗时主要是在:Application初始化 + MainActivity的界面加载绘制时间。由于MainActivity的业务和布局复杂度非常高,甚至该界面必须要有一些初始化的数据才能显示。 那么这个时候MainActivity就可能半天都出不来,这就给用户感觉app太卡了。我们要做的就是给用户赶紧利落的体验。点击app就立马弹出我们的界面。
SplasActivity就是一个简单的欢迎页,相信所有App都会有,一般就是只显示一张图片,但是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的启动加载资源太耗时了)。那该如何去设计延迟加载呢?这个延迟时间如何控制?第一时间想到的就是在onCreate里面调用handler.postDelayed()方法,时间的控制成了问题,因为不同的机器启动速度不一样,这个时间就不好设置。我们需要的是当应用启动加载完成后,界面就显示出来,提到这个相信很多人就有了些想法,没错,我们只需要知道界面是什么时候加载完成就行了,具体细节看如下实现: fragment_splash.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" > <FrameLayout android:id="@+id/frame" android:background="@drawable/splash12" android:layout_width="match_parent" android:layout_height="match_parent" > </FrameLayout> </RelativeLayout>SplashFragment.java
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); } }activity_main.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/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>activity_main_viewstub.java
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/content" android:layout_width="match_parent" android:layout_height="match_parent" > <ProgressBar android:id="@+id/progressBar1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" /> <ImageView android:id="@+id/iv" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitStart" android:src="@drawable/content" /> </FrameLayout>MainActivity.java:
public class MainActivity extends FragmentActivity { 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(); 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); //若splash没有动画 //mHandler.post(new DelayRunnable()); } }); //3.同时进行异步加载数据 } @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); } static class DelayRunnable implements Runnable{ private WeakReference<Context> contextRef; private WeakReference<SplashFragment> fragmentRef; public DelayRunnable(Context context, SplashFragment f) { contextRef = new WeakReference<Context>(context); fragmentRef = new WeakReference<SplashFragment>(f); } @Override public void run() { // 移除fragment if(contextRef!=null){ SplashFragment splashFragment = fragmentRef.get(); if(splashFragment==null){ return; } FragmentActivity activity = (FragmentActivity) contextRef.get(); FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction(); transaction.remove(splashFragment); transaction.commit(); } } } }总的来说,Splash的设计还是很有讲究的,好的加载方式可以让应用更加的受人青睐,上面的这种方式能解决大部分的应用启动缓慢的问题,如果有不对的地方,欢迎指正。