Android性能優化之-UI優化篇

日常開發中,我們經常會碰到比較復雜的布局,在這種情況下,最簡單的方案就是采用多層嵌套實現效果,但是最簡單的方法就是最優的方案嗎?我認為在不影響效果的情況下應盡可能減少布局的層級、減少嵌套,這樣做的好處就是可以讓整個布局達到結構清晰,渲染速度快的效果。

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

< include/> 重用:

< include>標簽的作用是在當前布局中引入另外一個布局,作為當前布局的子布局。可以節省大量代碼,同時便于統一使用及維護。

以app中常見的標題欄為例:

<?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="標題"
        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>

運行效果如下:

你可以在其他xml中調用它,比如:

<?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>標簽當中重新設置寬高等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的行間距:

效果圖:

看到這個效果,我們第一反應是不是四個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="交易狀態:已支付\n快遞狀態:北京通州1號倉庫已出庫\n配送時間:1月4日 \n快遞費用:包郵"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

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

ViewStub:

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

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

基于以上,我們可以看出 ViewStub的優點在于實現View的延遲加載,避免資源的浪費,減少渲染時間,在需要的時候才加載View。

實例:

<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文件時,可以調用 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自帶的分割線:

效果圖:

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

<?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="設置"
        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>

其實實現分割線的就是這兩行:

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方法進行了一個空的實現。Space控件在布局中只占位置,而不去繪制渲染。比如組件中的間隙用Space控件填充比用其它控件填充可以提高效率。

實例:

<Space    
    android:layout_width="match_parent"    
    android:layout_height="15dp" />

減少嵌套:

首先在我們日常開發中,我們心中要有一個大原則:盡量保持布局層級的扁平化。在這個大原則下我們要知道:

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

布局優化之-防止過度繪制

以下部分圖片內容引用來自 開源中國

Android渲染機制

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

為什么是16ms?

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

圖片來源:開源中國

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

圖片來源:開源中國

理解VSYNC(垂直同步)

從Android 4.1開始,谷歌致力于解決Android系統中最飽受詬病的一個問題,滑動不如iOS流暢。因此谷歌在4.1版本引入了一個重大的改進—Project Butter,也就是黃油計劃。

Project Butter對Android Display系統進行了重構,引入了三個核心元素,即VSYNC、Triple Buffer和Choreographer。

為了理解App是如何進行渲染的,我們必須了解手機硬件是如何工作,首先我們需要了解兩個相關的概念:

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

用戶容易在UI執行動畫或者滑動ListView的時候感知到不流暢,是因為這里的操作相對復雜,容易發生丟幀的現象,從而感覺卡頓。有很多原 因可以導致丟幀,也許是因為你的layout太過復雜,無法在16ms內完成渲染,有可能是因為你的UI上有層疊太多的繪制單元,還有可能是因為動畫執行 的次數過多。這些都會導致CPU或者GPU負載過重。

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

我們可以使用手機設置里 面的開發者選項,打開Show GPU Overdraw等選項進行觀察。

設置 > 開發者選項 > 調試GPU過度繪制 > 顯示GPU過度繪制區域

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

圖片來源:開源中國

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

嗯,那我們來看個實例:

看到這個頁面,基本上在深紅色區塊中,首先看下這個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>

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

再來看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肯定也是多余的,順便優化了其他的一些小地方,這里就不說了,修改了這幾處,我們運行后再來看一下:

是不是好一點了呢?還有可優化的余地,這里只是舉例說明一下。

總結:

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

 

來自:http://www.jianshu.com/p/61604eeebee0

 

 本文由用戶 mozeliang 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!