Android性能優(yōu)化之-UI優(yōu)化篇

日常開發(fā)中,我們經(jīng)常會碰到比較復(fù)雜的布局,在這種情況下,最簡單的方案就是采用多層嵌套實(shí)現(xiàn)效果,但是最簡單的方法就是最優(yōu)的方案嗎?我認(rèn)為在不影響效果的情況下應(yīng)盡可能減少布局的層級、減少嵌套,這樣做的好處就是可以讓整個布局達(dá)到結(jié)構(gòu)清晰,渲染速度快的效果。

一些需要我們掌握的小技巧

< include/> 重用:

< include>標(biāo)簽的作用是在當(dāng)前布局中引入另外一個布局,作為當(dāng)前布局的子布局??梢怨?jié)省大量代碼,同時便于統(tǒng)一使用及維護(hù)。

以app中常見的標(biāo)題欄為例:

<?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="48dp"
    android:background="@color/background">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:src="@mipmap/ic_launcher"/>

    <TextView
        tools:text="標(biāo)題"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:textSize="16sp"
        android:textColor="@color/black" />

    <TextView
        tools:text="確定"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:gravity="center"
        android:layout_alignParentRight="true"
        android:paddingRight="15dp"
        android:textSize="16sp"
        android:textColor="@color/black" />

</RelativeLayout>

運(yùn)行效果如下:


你可以在其他xml中調(diào)用它,比如:

<?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">

    <include
        layout="@layout/title_bar"/>

</RelativeLayout>

效果和上面是一樣的,你也可以在< include>標(biāo)簽當(dāng)中重新設(shè)置寬高等layout屬性。

用TextView同時顯示圖片和文字:

這個不用多說吧,使用TextView的drawableLeft(Right,Top,Bottom),舉個栗子:


這種效果如果使用兩個ImageView加上一TextView是非常不明智的,我們可以這樣寫:

<?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:background="@color/background">

    <TextView
        android:drawableLeft="@mipmap/ic_launcher"
        android:drawableRight="@mipmap/enter"
        android:drawablePadding="15dp"
        android:paddingLeft="15dp"
        android:paddingRight="15dp"
        android:textSize="16sp"
        android:text="朋友圈"
        android:background="@color/white"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</LinearLayout>

這樣寫是不是簡潔多了呢

使用TextView的行間距:

效果圖:


看到這個效果,我們第一反應(yīng)是不是四個Textview呢? 看下代碼吧:

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

    <ImageView
        android:padding="25dp"
        android:src="@mipmap/aa"
        android:layout_width="100dp"
        android:layout_height="match_parent"/>

    <TextView
        android:textSize="14dp"
        android:lineSpacingExtra="10dp"
        android:gravity="center_vertical"
        android:text="交易狀態(tài):已支付\n快遞狀態(tài):北京通州1號倉庫已出庫\n配送時間:1月4日 \n快遞費(fèi)用:包郵"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

可以看到我們僅僅利用Android:lineSpacingExtra="10dp",這一行代碼就省去了3個TextView,在行數(shù)更多的情況下是不是方便多了呢?

ViewStub:

ViewStub是一個非常輕量級的View,與其他的控件一樣,有著自己的屬性及特定的方法。它是一個看不見的,不占布局位置,占用資源非常小的控件??梢詾閂iewStub指定一個布局,ViewStub與其他控件相比,主要區(qū)別在以下:

  • 當(dāng)布局文件inflate時,ViewStub控件雖然也占據(jù)內(nèi)存,但是相相比于其他控件,ViewStub所占內(nèi)存很小。
  • ViewStub只能用來Inflate一個布局文件,而不是某個具體的View,當(dāng)然也可以把View寫在某個布局文件中。

基于以上,我們可以看出 ViewStub的優(yōu)點(diǎn)在于實(shí)現(xiàn)View的延遲加載,避免資源的浪費(fèi),減少渲染時間,在需要的時候才加載View。

實(shí)例:

<ViewStub 
    android:id="@+id/stub_import"
    android:inflatedId="@+id/panel_import"
    android:layout="@layout/progress_overlay"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom" />

用ViewStub加載layout文件時,可以調(diào)用setVisibility(View.VISIBLE)或者inflate()

((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE);
View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();

注意:如果ViewStub可見或是被inflate了,ViewStub就不存在了,取而代之的是被inflate的Layout。所以它也被稱做惰性控件。

用LinearLayout自帶的分割線:

效果圖:


上圖中分割線大家是怎么實(shí)現(xiàn)的呢,是不是用一個view設(shè)置高度和背景?其實(shí)我們可以使用LinearLayout自帶的分割線實(shí)現(xiàn),看代碼

<?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"
    android:divider="@drawable/divider"
    android:showDividers="middle">

    <TextView
        android:drawableLeft="@mipmap/ic_launcher"
        android:drawableRight="@mipmap/enter"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="設(shè)置"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <TextView
        android:drawableLeft="@mipmap/ic_launcher"
        android:drawableRight="@mipmap/enter"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="檔案"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

    <TextView
        android:drawableLeft="@mipmap/ic_launcher"
        android:drawableRight="@mipmap/enter"
        android:drawablePadding="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textSize="16sp"
        android:text="下載"
        android:gravity="center_vertical"
        android:layout_width="match_parent"
        android:layout_height="50dp" />

</LinearLayout>

其實(shí)實(shí)現(xiàn)分割線的就是這兩行:

  android:divider="@drawable/divider"
  android:showDividers="middle"

其中divider.xml是分隔線樣式。就是一個shape,showDividers 是分隔線的顯示位置,beginning、middle、end分別代表顯示在開始位置,中間,以及末尾位置。
還有一個dividerPadding的屬性沒有用到,意思是給divider添加padding。

Space控件:

Space控件是Android4.0中新提供的兩個控件之一,另一個是GridLayout,Space控件源碼非常簡單,先來看看

public class Space extends View {

    public Space(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        if (getVisibility() == VISIBLE) {
            setVisibility(INVISIBLE);
        }
    }

    public Space(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public Space(Context context) {
        this(context, null);
    }

    /**
     * Draw nothing.
     *
     * @param canvas an unused parameter.
     */
    @Override
    public void draw(Canvas canvas) {
    }

    /**
     * Compare to: {@link View#getDefaultSize(int, int)}
     * If mode is AT_MOST, return the child size instead of the parent size
     * (unless it is too big).
     */
    private static int getDefaultSize2(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.min(size, specSize);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
        }
        return result;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
                getDefaultSize2(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize2(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
}

該組件直接繼承View,相比其他的View組件都有onDraw方法用來繪制自身,而Space控件onDraw方法進(jìn)行了一個空的實(shí)現(xiàn)。Space控件在布局中只占位置,而不去繪制渲染。比如組件中的間隙用Space控件填充比用其它控件填充可以提高效率。

實(shí)例:

<Space    
    android:layout_width="match_parent"    
    android:layout_height="15dp" />
減少嵌套:

首先在我們?nèi)粘i_發(fā)中,我們心中要有一個大原則:盡量保持布局層級的扁平化。在這個大原則下我們要知道:

  • 在不影響層級深度的情況下,使用LinearLayout而不是RelativeLayout。因?yàn)镽elativeLayout會讓子View調(diào)用2次onMeasure,LinearLayout 在有weight時,才會讓子View調(diào)用2次onMeasure。Measure的耗時越長那么繪制效率就低。
  • 如果非要是嵌套,那么盡量避免RelativeLayout嵌套RelativeLayout,惡性循環(huán)。

布局優(yōu)化之-防止過度繪制

以下部分圖片內(nèi)容引用來自開源中國

Android渲染機(jī)制

用戶在使用我們app過程中感知到的卡頓等性能問題的最主要根源都是因?yàn)殇秩拘阅?。從設(shè)計(jì)師的角度,他們希望App能夠有更多的動畫,圖片等時尚元素來實(shí)現(xiàn)流暢的用 戶體驗(yàn)。但是Android系統(tǒng)很有可能無法及時完成那些復(fù)雜的界面渲染操作。Android系統(tǒng)每隔16ms發(fā)出VSYNC信號,觸發(fā)對UI進(jìn)行渲染, 如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫面,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。通俗的說:你只有16ms的時間來處理所有的任務(wù)。

為什么是16ms?

16ms是怎么計(jì)算出來的呢?就是1000/60hz,LCD手機(jī)屏一般都是60HZ,電腦屏幕亦是如此,即1秒鐘時間內(nèi)整個畫面刷新60次,對于人眼而言60HZ已經(jīng)達(dá)到很流暢的程度,1秒=1000ms,這樣算下來每一幀都要在16ms內(nèi)完成就很好解釋了。


圖片來源:開源中國

如果你的某個操作花費(fèi)時間是24ms,系統(tǒng)在得到VSYNC信號的時候就無法進(jìn)行正常渲染,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在32ms內(nèi)看到的就會是同一幀畫面。


圖片來源:開源中國
理解VSYNC(垂直同步)

從Android 4.1開始,谷歌致力于解決Android系統(tǒng)中最飽受詬病的一個問題,滑動不如iOS流暢。因此谷歌在4.1版本引入了一個重大的改進(jìn)—Project Butter,也就是黃油計(jì)劃。
Project Butter對Android Display系統(tǒng)進(jìn)行了重構(gòu),引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer。
為了理解App是如何進(jìn)行渲染的,我們必須了解手機(jī)硬件是如何工作,首先我們需要了解兩個相關(guān)的概念:

  • Refresh Rate:代表了屏幕在一秒內(nèi)刷新屏幕的次數(shù),這取決于硬件的固定參數(shù),例如60Hz。
  • Frame Rate:代表了GPU在一秒內(nèi)繪制操作的幀數(shù),例如30fps,60fps。
    GPU會獲取圖形數(shù)據(jù)進(jìn)行渲染,然后硬件負(fù)責(zé)把渲染后的內(nèi)容呈現(xiàn)到屏幕上,他們兩者不停的進(jìn)行協(xié)作。
    不幸的是,刷新頻率和幀率并不是總能夠保持相同的節(jié)奏。如果發(fā)生幀率與刷新頻率不一致的情況,就會容易出現(xiàn)Tearing的現(xiàn)象(畫面上下兩部分顯示內(nèi)容發(fā)生斷裂,來自不同的兩幀數(shù)據(jù)發(fā)生重疊)。
    通常來說,幀率超過刷新頻率只是一種理想的狀況,在超過60fps的情況下,GPU所產(chǎn)生的幀數(shù)據(jù)會因?yàn)榈却齎SYNC的刷新信息而被Hold住,這樣能夠保持每次刷新都有實(shí)際的新的數(shù)據(jù)可以顯示。但是我們遇到更多的情況是幀率小于刷新頻率。
    在這種情況下,某些幀顯示的畫面內(nèi)容就會與上一幀的畫面相同。糟糕的事情是,幀率從超過60fps突然掉到60fps以下,這樣就會發(fā)生LAG,JANK,HITCHING等卡頓掉幀的不順滑的情況。這也是用戶感受不好的原因所在。

用戶容易在UI執(zhí)行動畫或者滑動ListView的時候感知到不流暢,是因?yàn)檫@里的操作相對復(fù)雜,容易發(fā)生丟幀的現(xiàn)象,從而感覺卡頓。有很多原 因可以導(dǎo)致丟幀,也許是因?yàn)槟愕膌ayout太過復(fù)雜,無法在16ms內(nèi)完成渲染,有可能是因?yàn)槟愕腢I上有層疊太多的繪制單元,還有可能是因?yàn)閯赢媹?zhí)行 的次數(shù)過多。這些都會導(dǎo)致CPU或者GPU負(fù)載過重。

怎樣檢測Overdraw(過度繪制)?

我們可以使用手機(jī)設(shè)置里 面的開發(fā)者選項(xiàng),打開Show GPU Overdraw等選項(xiàng)進(jìn)行觀察。

設(shè)置 > 開發(fā)者選項(xiàng) > 調(diào)試GPU過度繪制 > 顯示GPU過度繪制區(qū)域

打開以后可以切換到需要檢測的程序,你可以發(fā)現(xiàn)屏幕上有很多色塊,對于各個色塊的解釋,我們來看一張Overdraw的參考圖:

圖片來源:開源中國

淡紫,淡綠,淡紅,深紅代表了4種不同程度的Overdraw情況,我們的目標(biāo)就是盡量減少紅色Overdraw,看到更多的淡紫色區(qū)域。

嗯,那我們來看個實(shí)例:


看到這個頁面,基本上在深紅色區(qū)塊中,首先看下這個ListView的代碼:

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

            <com.view.swipelistview.MySwipeMenuListView
              android:id="@+id/lv_courses"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginBottom="40dp"
              android:background="@color/app_background"
              android:divider="@drawable/course_list_divider"
              android:dividerHeight="@dimen/divider"
              android:footerDividersEnabled="false"
              android:headerDividersEnabled="false"
              android:listSelector="@android:color/transparent" />

</LinearLayout>

去除了一些不相關(guān)代碼,方便觀看首先我們可以看到,頂層布局的已經(jīng)設(shè)置了背景,ListView也設(shè)置了相同的背景,所以這里的背景就可以去掉了。

再來看item的布局:

<?xml version="1.0" encoding="utf-8" ?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/ll_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:background="@drawable/list_item_bg"
    android:paddingLeft="@dimen/cf_official_layout_paddingleft"
    android:paddingRight="@dimen/cf_official_layout_paddingright">

</LinearLayout>

這里的頂層布局的background是一個selector,代碼如下:

<?xml version="1.0" encoding="utf-8"?>
<selector
      xmlns:android="http://schemas.android.com/apk/res/android>
      <item android:drawable="@color/white2" android:state_pressed="true" />
      <item android:drawable="@color/white2"  android:state_pressed="false"/>
</selector>

上面ListView的android:listSelector="@android:color/transparent",所以這里的selector肯定也是多余的,順便優(yōu)化了其他的一些小地方,這里就不說了,修改了這幾處,我們運(yùn)行后再來看一下:


是不是好一點(diǎn)了呢?還有可優(yōu)化的余地,這里只是舉例說明一下。

總結(jié):

巧用這些技巧可以讓我們寫出更高效,更優(yōu)雅的代碼,具體的情景使用還是要看項(xiàng)目的具體情況,不能為了優(yōu)化而優(yōu)化。優(yōu)化也是不斷積累的過程,不要指望立竿見影。

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

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

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