在项目中,我们忙于赶着功能的实现,整个app的性能很差。每个页面加载都卡到不行。暑假马上就要到了,项目组专门安排了一个提升性能的版本。版本还没开始做,我提前在这里记录一下性能调优的一些方法和尝试。
今天学习并尝试的是ViewStub。在app的UI中,我们时常会有一些View是“隐形的”即,我会在xml中给它设置将它的visibility设为gone。只有在特殊情况下,我才会将它显现出来。比如悬浮的Button会在我滑动内容时消失,部分按钮只有有权限的用户才看见等等情况。用View设置visibility的方式,最大的好处就是灵活。我可以动态地在程序中,根据一些条件判断,随时显现或者隐藏这些控件,使整个交互充满灵动。
但除了以上情况,还有另一种情况。比如,在列表为空时,我会加载一幅特殊的画,用以提示用户列表没有内容,或者在用户第一次登录时会显示每个按钮,之后就不会再看到了。这些情况归纳起来就是,这些控件大部分情况下处于隐形状态。只有少数情况它需要被显示出来。我如果依然把它写在xml里面,只是设置一下visibility = gone,显得非常不划算。因为不论visibility设置成什么,系统都已经把它加载出来了,加载的时间已经花了,内存也占用了,我却有很大的可能根本不会使用它。因此,我们可以使用ViewStub。ViewStub是一个轻量级的view,我们只有一次给它绘制的机会。它在不绘制时,占用的内存是非常小的,且加载时间也很短。当我们需要它时,我们再绘制它,整体花的时间与View的直接加载相差不大。所以,如果这个View显示的机会不多,我们更推荐使用ViewStub进行显示。
代码如下:
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> <Button android:id="@+id/test_btn2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:text="test2" android:onClick="testclick2"/> <ViewStub android:id="@+id/test_view2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:inflatedId="@+id/test_image_view" android:layout_above="@id/test_btn2" android:layout="@layout/test_image_view" /> </RelativeLayout> </LinearLayout>
test_image_view.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/test_img_src" /> </LinearLayout>
MainActivity.java:
package com.dream.fishbonelsy.viewstubdemo; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewStub; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends ActionBarActivity { private ViewStub view2; @Override protected void onCreate(Bundle savedInstanceState) { long startTime = System.nanoTime(); //开始时间 super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); view2 = (ViewStub) findViewById(R.id.test_view2); long consumingTime = System.nanoTime() - startTime; //消耗时间 Log.d("tag" , "布局时间:"+ consumingTime/1000000+"毫秒"); } public void testclick2(View view){ long startTime = System.nanoTime(); //开始时间 view2.inflate(); long consumingTime = System.nanoTime() - startTime; //消耗时间 Log.d("tag" , "绘制时间:"+consumingTime/1000000+"毫秒"); } }
消耗时间如图:
相同的内容我经过了多次测试,发现ViewStub的布局时间非常短,之后加载也不会太慢。对于同样的内容,我采用了ImageView设置visibility尝试了一次。
代码如下:
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1"> <Button android:id="@+id/test_btn1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:text="test1" android:onClick="testclick1"/> <ImageView android:id="@+id/test_view1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/test_img_src" android:visibility="gone" android:layout_above="@id/test_btn1" /> </RelativeLayout> </LinearLayout>
MainActivity.java:
package com.dream.fishbonelsy.viewstubdemo; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewStub; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends ActionBarActivity { private ImageView view1; private ViewStub view2; @Override protected void onCreate(Bundle savedInstanceState) { long startTime = System.nanoTime(); //開始時間 // super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); view1 = (ImageView) findViewById(R.id.test_view1); //view2 = (ViewStub) findViewById(R.id.test_view2); long consumingTime = System.nanoTime() - startTime; //消耗時間 Log.d("tag" , "布局时间:"+ consumingTime/1000000+"毫秒"); } public void testclick1(View view){ long startTime = System.nanoTime(); //開始時間 view1.setVisibility(View.VISIBLE); long consumingTime = System.nanoTime() - startTime; //消耗時間 Log.d("tag", "绘制时间:" + consumingTime / 1000 + "微秒"); } }
消耗时间如图:
分析上面两份时间数据我们可以观察出这样的结果:
一、两种方案在要显示这个图时,所花的时间是相当的。此时用View更好,因为View更灵活,我可以反反复复地隐藏和显现,可以设置点击事件,甚至可以重绘等等。
二、两种方案在不显示这张图时,ViewStub的加载时间会大大降低,同样的,消耗内存也会少很多。
因此,可以暂时总结为:当我需要一个灵活的控件,它需要变化、监听、隐藏、移动时,我应该选择View。由于,ViewStub只能绘制一次,不能监听,不能重绘,不能移动,不能隐藏,所以当我需要一个不常显示的备用View时,使用ViewStub可以大大节省我们的系统资源。(比如,当列表内容为空,显示某图;当网络情况不好,显示某图;当用户第一次登录,显示某图)。我们需要预估这个View出现的频率,来最终决定方案。
暂时Done。
发现性能优化真是需要丰富的经验,然后一点点扣出来的性能。学海无涯啊~