Android布局性能优化—从源码角度看ViewStub延迟加载技术

在项目中,难免会遇到这种需求,在程序运行时需要动态根据条件来决定显示哪个View或某个布局,最通常的想法就是把需要动态显示的View都先写在布局中,然后把它们的可见性设为View.GONE,最后在代码中通过控制View.VISIABLE动态的更改它的可见性。这样的做法的优点是逻辑简单而且控制起来比较灵活。但是它的缺点就是,耗费资源,虽然把View的初始可见View.GONE但是在Inflate布局的时候View仍然会被Inflate,也就是说仍然会创建对象,会被实例化,会被设置属性。也就是说,会耗费内存等资源。

推荐的做法是使用android.view.ViewStub,ViewStub是一个轻量级的View,使用非常简单:

mViewStub = (ViewStub) this.findViewById(R.id.viewstub);

mViewStub.inflate();

它一个不可见的,不占布局位置,占用资源非常小的控件,相当于一个“占位控件”。使用时可以为ViewStub指定一个布局,在Inflate布局的时候,只有ViewStub会被初始化,然后当ViewStub被设置为可见的时或调用了ViewStub.inflate()的时候,ViewStub所指向的布局就会被inflate实例化,且此布局文件直接将当前ViewStub替换掉,然后ViewStub的布局属性(layout_margin***、layout_width等)都会传给它所指向的布局。这样,就可以使用ViewStub在运行时动态显示布局,节约内存资源。

下面我们从ViewStub源码来看下inflate()方法的实现原理:

[java]  view plain  copy
 print ?
  1. public View inflate() {  
  2.        final ViewParent viewParent = getParent();  
  3.   
  4.        if (viewParent != null && viewParent instanceof ViewGroup) {  
  5.            if (mLayoutResource != 0) {  
  6.                final ViewGroup parent = (ViewGroup) viewParent;  
  7.                final LayoutInflater factory;  
  8.                if (mInflater != null) {  
  9.                    factory = mInflater;  
  10.                } else {  
  11.                    factory = LayoutInflater.from(mContext);  
  12.                }  
  13.                final View view = factory.inflate(mLayoutResource, parent,  
  14.                        false);  
  15.   
  16.                if (mInflatedId != NO_ID) {  
  17.                    view.setId(mInflatedId);  
  18.                }  
  19.   
  20.                final int index = parent.indexOfChild(this);  
  21.                parent.removeViewInLayout(this);  
  22.   
  23.                final ViewGroup.LayoutParams layoutParams = getLayoutParams();  
  24.                if (layoutParams != null) {  
  25.                    parent.addView(view, index, layoutParams);  
  26.                } else {  
  27.                    parent.addView(view, index);  
  28.                }  
  29.   
  30.                mInflatedViewRef = new WeakReference<View>(view);  
  31.   
  32.                if (mInflateListener != null) {  
  33.                    mInflateListener.onInflate(this, view);  
  34.                }  
  35.   
  36.                return view;  
  37.            } else {  
  38.                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");  
  39.            }  
  40.        } else {  
  41.            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");  
  42.        }  
  43.    }  
我们先从方法的入口开始看:

1、在第2行,首先是得到ViewStub它的父视图对象。

2、然后在第4行一开始肯定是能进入判断的,mLayoutResource就是需要inflate的布局资源,然后在第13行填充这个布局资源。

3、然后在第21行,重要的来了,parent.removeViewInLayout(this);这段代码是什么意思呢?看方法名字就知道了,this是代表ViewStub对象,意思就是把当前ViewStub对象从父视图中移除了。

4、然后第23~28行,就是得到ViewStub的LayoutParams布局参数对象,如果存在就把它赋给被inflate的布局对象,然后把inflate的布局对象添加到父视图中。
5、最后返回inflate的布局对象。

好了,源码解析完毕!!!

从上述可知,当我们第二次调用ViewStub.inflate()方法的时候,因为已经移除了ViewStub对象,在第2、4行,得到的viewParent就为null,此时判断时候就会走else抛出一个IllegalStateException异常:ViewStub must have a non-null ViewGroup viewParent。


需要注意的几点:

1.ViewStub之所以常称之为“延迟化加载”,是因为在教多数情况下,程序无需显示ViewStub所指向的布局文件,只有在特定的某些较少条件下,此时ViewStub所指向的布局文件才需要被inflate,且此布局文件直接将当前ViewStub替换掉,具体是通过viewStub.infalte()或viewStub.setVisibility(View.VISIBLE)来完成。

2.正确把握住ViewStub的应用场景非常重要,因为使用ViewStub可以优化布局,一般应用在当前布局或控件在用户使用较少情况下,这样可以提高性能,节约内存,加快界面渲染。

3.对ViewStub的inflate操作只能进行一次,因为inflate的时候是将它指向的布局实例化并替换掉当前ViewStub本身(由此体现出了ViewStub“占位”性质),一旦替换后,此时原来的布局文件中就没有ViewStub控件了,因此,如果多次对ViewStub进行infalte,会出现错误信息:ViewStub must have a non-null ViewGroup viewParent。

4.3中所讲到的ViewStub指向的布局文件解析inflate并替换掉当前ViewStub本身,并不是完全意义上的替换(与include标签不太一样),替换时,布局文件的layout params是以ViewStub为准,其他布局属性是以布局文件自身为准。

5. ViewStub本身是不可见的,对ViewStub.setVisibility(int visibility)与其他View控件不一样,我们可以从源码角度来看一下ViewStub.setVisibility()方法的作用:


这个方法意思就是ViewStub的setVisibility()设置成View.VISIBLE或INVISIBLE如果是首次使用,都会自动inflate其指向的布局文件,并替换ViewStub本身,再次使用则是相当于对其指向的布局文件设置可见性

好了,原理讲了那么多,来看看代码怎么实现吧:

首先看看效果图:


使用了ViewStub的activity_main.xml:

[html]  view plain  copy
 print ?
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     tools:context="com.example.viewstub.MainActivity" >  
  7.   
  8.     <Switch  
  9.         android:id="@+id/switch1"  
  10.         android:layout_width="wrap_content"  
  11.         android:layout_height="wrap_content" />  
  12.   
  13.     <ViewStub  
  14.         android:id="@+id/viewstub"  
  15.         android:layout_width="match_parent"  
  16.         android:layout_height="match_parent"  
  17.         android:layout_marginBottom="10dp"  
  18.         android:layout_marginLeft="10dp"  
  19.         android:layout_marginRight="10dp"  
  20.         android:layout_marginTop="10dp"  
  21.         android:layout="@layout/hide_layout" />  
  22.   
  23. </LinearLayout>  
hide_layout.xml

[html]  view plain  copy
 print ?
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:background="#00ffff"  
  6.     android:orientation="vertical" >  
  7.     <Button  
  8.         android:id="@+id/hide_layout_btn"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content"  
  11.         android:layout_gravity="center"  
  12.         android:text="Click me" />  
  13. </LinearLayout>  
代码文件:

[java]  view plain  copy
 print ?
  1. public class MainActivity extends ActionBarActivity {  
  2.     private ViewStub mViewStub;  
  3.     private Switch mSwitch;  
  4.     private boolean flag = false;  
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.         mViewStub = (ViewStub) this.findViewById(R.id.viewstub);//实例化ViewStub  
  10.         mSwitch = (Switch) findViewById(R.id.switch1);  
  11.         mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {  
  12.             @Override  
  13.             public void onCheckedChanged(CompoundButton buttonView,  
  14.                     boolean isChecked) {  
  15.                 if (isChecked) {  
  16.                     if(!flag){  
  17.                         mViewStub.inflate();//ViewStub只能被inflate一次,会返回一个填充资源的View对象  
  18.                         //mViewStub.setVisibility(View.VISIBLE);)  
  19.                         flag = true;  
  20.                     }else{  
  21.                         mViewStub.setVisibility(View.VISIBLE);  
  22.                     }  
  23.                     Button mBtn = (Button) findViewById(R.id.hide_layout_btn);//ViewStub被替换的布局内的控件  
  24.                     mBtn.setOnClickListener(new OnClickListener() {  
  25.                         @Override  
  26.                         public void onClick(View v) {  
  27.                             Toast.makeText(getApplicationContext(), "Click me!",  
  28.                                     Toast.LENGTH_SHORT).show();  
  29.                         }  
  30.                     });  
  31.                 } else {  
  32.                     mViewStub.setVisibility(View.GONE);  
  33.                 }  
  34.             }  
  35.         });  
  36.     }  
  37. }  

注:使用ViewStub被替换的布局中的控件,直接findViewById即可。


最后扩展一下在布局优化时候常用的其它几个标签:

1、布局重用<include />可以通过这个标签直接加载外部的xml到当前结构中,是复用UI资源的常用标签

<include />标签能够重用布局文件,简单的使用如下:

[html]  view plain  copy
 print ?
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical"  
  6.     tools:context="com.example.viewstub.MainActivity" >  
  7.     <Switch  
  8.         android:id="@+id/switch1"  
  9.         android:layout_width="wrap_content"  
  10.         android:layout_height="wrap_content" />  
  11.   
  12.     <include layout="@layout/include_layout" />  
  13.   
  14. </LinearLayout>  
使用include标签中布局文件中的控件,直接findViewById即可。

2、减少视图层级<merge />

<merge />标签在UI的结构优化中起着非常重要的作用,它可以删减多余的层级,优化UI。<merge />多用于替换FrameLayout(因为所有的Activity视图的根结点都是FrameLayout,如果当前的布局根结点是Framelayout,那么可以用merge替代,减少多余的层级)或者当一个布局包含另一个时,<merge />标签消除视图层次结构中多余的视图组。例如你的主布局文件是垂直布局,又include引入了一个垂直布局,这是如果include布局使用的LinearLayout就没意义了,使用的话反而减慢你的UI渲染。这时可以使用<merge />标签进行优化。

[java]  view plain  copy
 print ?
  1. <merge xmlns:android="http://schemas.android.com/apk/res/android">  
  2.     <Button  
  3.         android:layout_width="wrap_content"  
  4.         android:layout_height="wrap_content"  
  5.         android:text=""/>  
  6.     <TextView  
  7.         android:layout_width="wrap_content"  
  8.         android:layout_height="wrap_content"  
  9.         android:text=""/>  
  10. </merge>  


转自:http://blog.csdn.net/u010687392

猜你喜欢

转载自blog.csdn.net/WalterZhoukick/article/details/52463635