Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android布局性能優化—從源碼角度看ViewStub延遲加載技術

Android布局性能優化—從源碼角度看ViewStub延遲加載技術

編輯:關於Android編程

在項目中,難免會遇到這種需求,在程序運行時需要動態根據條件來決定顯示哪個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()方法的實現原理:

 

 public View inflate() {
        final ViewParent viewParent = getParent();

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final LayoutInflater factory;
                if (mInflater != null) {
                    factory = mInflater;
                } else {
                    factory = LayoutInflater.from(mContext);
                }
                final View view = factory.inflate(mLayoutResource, parent,
                        false);

                if (mInflatedId != NO_ID) {
                    view.setId(mInflatedId);
                }

                final int index = parent.indexOfChild(this);
                parent.removeViewInLayout(this);

                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
                if (layoutParams != null) {
                    parent.addView(view, index, layoutParams);
                } else {
                    parent.addView(view, index);
                }

                mInflatedViewRef = new WeakReference(view);

                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
            } else {
                throw new IllegalArgumentException(ViewStub must have a valid layoutResource);
            }
        } else {
            throw new IllegalStateException(ViewStub must have a non-null ViewGroup viewParent);
        }
    }
我們先從方法的入口開始看:

 

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:

 



    

    

hide_layout.xml

 

 


代碼文件:

 

 

public class MainActivity extends ActionBarActivity {
	private ViewStub mViewStub;
	private Switch mSwitch;
	private boolean flag = false;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mViewStub = (ViewStub) this.findViewById(R.id.viewstub);//實例化ViewStub
		mSwitch = (Switch) findViewById(R.id.switch1);
		mSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
			@Override
			public void onCheckedChanged(CompoundButton buttonView,
					boolean isChecked) {
				if (isChecked) {
					if(!flag){
						mViewStub.inflate();//ViewStub只能被inflate一次,會返回一個填充資源的View對象
						//mViewStub.setVisibility(View.VISIBLE);)
						flag = true;
					}else{
						mViewStub.setVisibility(View.VISIBLE);
					}
					Button mBtn = (Button) findViewById(R.id.hide_layout_btn);//ViewStub被替換的布局內的控件
					mBtn.setOnClickListener(new OnClickListener() {
						@Override
						public void onClick(View v) {
							Toast.makeText(getApplicationContext(), Click me!,
									Toast.LENGTH_SHORT).show();
						}
					});
				} else {
					mViewStub.setVisibility(View.GONE);
				}
			}
		});
	}
}

 

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


最後擴展一下在布局優化時候常用的其它幾個標簽:

 

1、布局重用可以通過這個標簽直接加載外部的xml到當前結構中,是復用UI資源的常用標簽

標簽能夠重用布局文件,簡單的使用如下:

 


    

    

使用include標簽中布局文件中的控件,直接findViewById即可。

 

2、減少視圖層級

標簽在UI的結構優化中起著非常重要的作用,它可以刪減多余的層級,優化UI。多用於替換FrameLayout(因為所有的Activity視圖的根結點都是FrameLayout,如果當前的布局根結點是Framelayout,那麼可以用merge替代,減少多余的層級)或者當一個布局包含另一個時,標簽消除視圖層次結構中多余的視圖組。例如你的主布局文件是垂直布局,又include引入了一個垂直布局,這是如果include布局使用的LinearLayout就沒意義了,使用的話反而減慢你的UI渲染。這時可以使用標簽進行優化。

 

 




 





  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved