基本的優(yōu)化總結(jié)(四)

導(dǎo)言

這節(jié)主要是講一下布局方面關(guān)于UI的優(yōu)化手段,屬于編碼中的一些細(xì)節(jié)處理

UI流暢性優(yōu)化

先看Systrace中的某一幀


Systrace中的某一幀

從Alert提示中我們也可以知道反饋的是測量和布局時(shí)間過久,所以說后續(xù)要做的就是優(yōu)化測量和布局的時(shí)間

ViewStub

先看一下ViewStub里面的一些關(guān)鍵代碼

    public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context);

        final TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.ViewStub, defStyleAttr, defStyleRes);
        mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
        mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
        mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
        a.recycle();
        //可以看到,默認(rèn)是不顯示的
        setVisibility(GONE);
        setWillNotDraw(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //默認(rèn)的大小也是0,并且不會(huì)有任何測量操作
        setMeasuredDimension(0, 0);
    }

    @Override
    public void draw(Canvas canvas) {
        //本身并不會(huì)繪制任何東西  
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
      //同樣的也不會(huì)要求別人繪制任何東西
    }

從上面我們可以看出,實(shí)際上ViewStub就是一個(gè)占位容器,本身不會(huì)做任何操作,并且測量的時(shí)候默認(rèn)為GONE的視圖并不會(huì)參與測量
再看setVisibility方法

    @Override
    @android.view.RemotableViewMethod(asyncImpl = "setVisibilityAsync")
    public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            //如果內(nèi)部布局已經(jīng)inflate過,那么直接顯示或者隱藏
            View view = mInflatedViewRef.get();
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            //否則顯示自己,然后再進(jìn)行inflate操作
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }

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

        if (viewParent != null && viewParent instanceof ViewGroup) {
            if (mLayoutResource != 0) {
                final ViewGroup parent = (ViewGroup) viewParent;
                final View view = inflateViewNoAdd(parent);
                replaceSelfWithView(view, parent);

                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");
        }
    }

    private View inflateViewNoAdd(ViewGroup parent) {
        final LayoutInflater factory;
        if (mInflater != null) {
            factory = mInflater;
        } else {
            factory = LayoutInflater.from(mContext);
        }
        //這里才進(jìn)行ViewStub內(nèi)的布局的inflate操作
        final View view = factory.inflate(mLayoutResource, parent, false);

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

    private void replaceSelfWithView(View view, ViewGroup parent) {
        final int index = parent.indexOfChild(this);
        //將當(dāng)前ViewStub從父布局中移除
        parent.removeViewInLayout(this);
        //然后將ViewStub內(nèi)的布局直接添加到父布局中
        final ViewGroup.LayoutParams layoutParams = getLayoutParams();
        if (layoutParams != null) {
            parent.addView(view, index, layoutParams);
        } else {
            parent.addView(view, index);
        }
    }

看完之后我們就清楚了,相比于直接把視圖放入xml中,ViewStub一開始并不會(huì)直接進(jìn)行inflate操作,而是等到手動(dòng)調(diào)用inflate或者setVisibility(View.VISIBLE),也就是說在ViewStub中的layout的inflate被延遲處理了

結(jié)論:ViewStub的使用場景就是一個(gè)視圖需要滿足一定條件的情況下才顯示,此時(shí)該視圖就應(yīng)該通過ViewStub修飾,通過延遲加載的方式來優(yōu)化原始原來布局的測量等速度,因?yàn)檫@個(gè)布局不一定可見

布局優(yōu)化

我們知道Android的測量是從頂層布局開始然后一直向下分發(fā),所謂布局優(yōu)化,實(shí)際上就是降低inflate和測量/布局的耗時(shí),比方說對比于RelativeLayout和LinearLayout,先看一個(gè)布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text1"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text2"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text3"
            />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text4"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text5"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text6"
            />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text7"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text8"
            />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="text9"
            />

    </LinearLayout>

</LinearLayout>
inflate的情況(不包括系統(tǒng)布局)
測量和布局的耗時(shí)

然后看一下直接使用RelativeLayout的效果

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tv_text1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text1"
        />

    <TextView
        android:id="@+id/tv_text2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text2"
        android:layout_toRightOf="@+id/tv_text1"
        />

    <TextView
        android:id="@+id/tv_text3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text3"
        android:layout_toRightOf="@+id/tv_text2"
        />

    <TextView
        android:id="@+id/tv_text4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text4"
        android:layout_below="@+id/tv_text1"
        />

    <TextView
        android:id="@+id/tv_text5"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text5"
        android:layout_toRightOf="@+id/tv_text4"
        android:layout_below="@+id/tv_text1"
        />

    <TextView
        android:id="@+id/tv_text6"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text6"
        android:layout_toRightOf="@+id/tv_text5"
        android:layout_below="@+id/tv_text1"
        />

    <TextView
        android:id="@+id/tv_text7"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text7"
        android:layout_below="@+id/tv_text4"
        />

    <TextView
        android:id="@+id/tv_text8"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text8"
        android:layout_toRightOf="@+id/tv_text7"
        android:layout_below="@+id/tv_text4"
        />

    <TextView
        android:id="@+id/tv_text9"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text9"
        android:layout_toRightOf="@+id/tv_text8"
        android:layout_below="@+id/tv_text4"
        />

</RelativeLayout>
inflate的情況(不包括系統(tǒng)布局)

測量和布局的耗時(shí)

大致分析一下差別,其實(shí)可以看出,如果在同樣的實(shí)現(xiàn)效果下,使用RelativeLayout可以減少視圖的個(gè)數(shù),從而優(yōu)化inflate的效率,相對于測量和布局來說,這個(gè)比較明顯
當(dāng)然例子中的布局層級比較簡單,所以說沒有看出measure/layout上面的差別,當(dāng)布局特別復(fù)雜的時(shí)候,這個(gè)也是要考慮的部分

結(jié)論:
優(yōu)先應(yīng)該考慮降低布局的層級,實(shí)際上也就是降低視圖的數(shù)量,這樣可以相對有效的降低inflate的耗時(shí)

merge

上面提到了要降低布局層級,實(shí)際開發(fā)中會(huì)出現(xiàn)一種情況,比方說setContentView,實(shí)際上父布局就是一個(gè)FrameLayout,如果此時(shí)我們的xml中父布局是FrameLayout

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/iv_image2"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:src="@mipmap/ic_launcher"
        android:layout_gravity="center"
        />

    <ImageView
        android:id="@+id/iv_image3"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:src="@mipmap/ic_launcher"
        android:layout_gravity="right"
        />

</FrameLayout>

此時(shí)想到于FrameLayout中嵌套FrameLayout,很明顯這個(gè)是多余,所幸的是Android提供了方式來處理,可以通過merge標(biāo)簽來合并

<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android">

    <ImageView
        android:id="@+id/iv_image2"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:src="@mipmap/ic_launcher"
        android:layout_gravity="center"
        />

    <ImageView
        android:id="@+id/iv_image3"
        android:layout_width="36dp"
        android:layout_height="36dp"
        android:src="@mipmap/ic_launcher"
        android:layout_gravity="right"
        />

</merge>

看一下LayoutInflate的具體處理

    ...
    if (TAG_MERGE.equals(name)) {
        if (root == null || !attachToRoot) {
            throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
            }

            rInflate(parser, root, inflaterContext, attrs, false);
            } else {
    ...

可以看到這里直接進(jìn)入rInflate,實(shí)際上就是繼續(xù)解析一下標(biāo)簽,也就是說inflate遇到merge之后就直接pass,然后把內(nèi)部的視圖添加到merge外部的視圖當(dāng)中

測量優(yōu)化

我們知道視圖在使用的時(shí)候必須要進(jìn)行測量,具體的意義就是要確定子視圖的大小,那么這一塊的耗時(shí)也會(huì)影響到一幀的流暢性,也應(yīng)該是優(yōu)化考慮的一部分
比方說LinearLayout和RelativeLayout,最明顯的區(qū)別就是LinearLayout對內(nèi)部的子視圖只測量一次,而RelativeLayout要對內(nèi)部的子視圖測量兩次,所以說測量的速度上面來說LinearLayout會(huì)優(yōu)于RelativeLayout,布局的話因?yàn)镽elativeLayout在測量的時(shí)候就計(jì)算好了子視圖的位置,所以相對LinearLayout來說可能會(huì)快一點(diǎn)。

結(jié)論:
從使用的角度來說,如果可以單一的使用LinearLayout或者RelativeLayout的話確實(shí)是比較好的選擇,否則可以考慮通過自定義視圖的方式來實(shí)現(xiàn)測量和布局,自己實(shí)現(xiàn)可以避免了大量的無意義的計(jì)算操作,效率會(huì)更好

總結(jié)

UI這塊的優(yōu)化相對比較復(fù)雜,具體的情況還是要做深入的分析,個(gè)人認(rèn)為原則上就是盡量降低視圖層級,能自己寫一個(gè)ViewGroup直接擺好的那就最好,如果時(shí)間充裕的話,也可以考慮通過Systrace來進(jìn)行對比,選擇合理方案,這一節(jié)講了視圖層級優(yōu)化,下一節(jié)看看具體的工具

文章系列:
基本的優(yōu)化總結(jié)(一)
基本的優(yōu)化總結(jié)(二)
基本的優(yōu)化總結(jié)(三)
基本的優(yōu)化總結(jié)(四)
基本的優(yōu)化總結(jié)(五)
基本的優(yōu)化總結(jié)(六)
基本的優(yōu)化總結(jié)(七)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容