1. 程式人生 > >Android效能優化篇(一)——佈局優化

Android效能優化篇(一)——佈局優化

                                                 Android效能優化篇

題記:

不知道別人是怎麼學習的,我總是覺得我學習的效率很低,昨天發生了不愉快的事情後,我痛定思痛,反思了一下自己,還是總結不夠,也是工作中接觸的東西太少的緣故,但是缺乏思考和總結,是我自己的問題,怪不了別人,也不怨天尤人。這段時間我把自己叫做廢物,什麼時候覺醒了,什麼時候再改名吧。

本章主要圍繞Android的效能優化來講解,主要包含佈局優化、繪製優化、記憶體洩露、響應速度、ListView優化、Bitap優化、執行緒優化等優化方式和一些目前常見到的優化工具的使用和說明,在講解中會結合我在工作中遇到的相關問題。具體參照任玉剛-Android開發藝術探索。

一、基礎

1、ANR

2、OOM

(2)標籤

(4)標籤

一、基礎

1、ANR

ANR,全名Application not response,應用無響應。

產生ANR的原因:通常在UI執行緒執行耗時任務可能會導致ANR,一般Activity中5s內無法響應按鍵或觸控事件,那麼就會導致ANR,同樣的BroadCast中10s內沒有完成操作也會出現anr,service為20s。

如何分析:一般發生ANR後,系統會在data/anr目錄下建立一個traces.txt檔案,通過這個檔案我們可以分析ANR的原因。

2、OOM

OOM,全稱Out of Memory,記憶體溢位

產生記憶體溢位的原因:常見的比如有單例持有外部例項,Bimap沒有及時釋放等

如何分析:後面會講到詳細的工具和分析

3、為什麼要進行效能優化

(1)Android裝置,通常由於體積的限制,它的cpu和記憶體是有限制的,因此無法和pc的大記憶體高cpu相比

(2)程式無限制使用cpu,會導致當前應用cpu過高,可能會導致系統卡頓甚至程式無響應(cpu都被某一塊佔用了,其他程式無法獲得對應的cpu時間片,因此自然會出現卡頓)

(3)無限制使用記憶體,會導致程式使用記憶體過多,從而引發OOM,發生OOM後,系統的MemoryKiller會殺死一些程序,這樣會導致該程式或者其他程式被殺死,從而出現閃退的現象。

二、佈局優化

Android中佈局優化主要包含以下三個方面:佈局層級和測量次數、佈局過度繪製、繪製過程

1、佈局層級與測量次數

佈局層級越多,繪製耗時就會相應增加。考慮使用佈局層級比較少的方案.

(1)合理選擇父容器

在佈局層數相同時,我們優先選擇測量次數較少的父容器

通常我們選取的優先順序為:FrameLayout、不帶Layou_wight的LinearLayuut、RelativeLayout。因為帶有Layot_weight的LinearLayout和RelativeLayout會測量兩次。

總結來看,首先優先佈局層級少的方案,在佈局層級相同時,採用測量次數少的。

那麼如何分析佈局層級呢?

(1)Android Device Monitor

Android studio3.0開始,Google不建議使用它, 因此我們需要手動在sdk目錄下的tools中找到它,之後執行你的apk並且選擇Hierarchy View,就可以檢視對應的層級關係,如下:

(2)Component Tree

在Android studio.3.0之後,我們可以使用Component Tree,它同樣提供了檢視元件層級的功能,具體如下:

選擇並檢視我們的xm佈局,點選左下角的design,則可以看到左側層級關係。

(2)標籤

除了上面提到的方式外,我們還可以通過使用標籤來減少層級和複用元件

(1)include標籤

include標籤的作用就是可以直接引用已有的佈局,而不需要重新寫佈局。而通常會將include和merge標籤相結合使用,下面會介紹merge標籤。

例如:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/activity_main"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:paddingBottom="@dimen/activity_vertical_margin"

android:paddingLeft="@dimen/activity_horizontal_margin"

android:paddingRight="@dimen/activity_horizontal_margin"

android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.jared.layoutoptimise.MainActivity">

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

</RelativeLayout>

這裡include引用的是一個LinearLayout的佈局,雖然我們不需要重複寫這個佈局,但是卻增加了層級關係。

(2)merge標籤

merge標籤通常是作為include標籤的輔助擴充套件,就是為了解決引入include後導致佈局層級增加問題,使用merge標籤後,引入的佈局中的View就會作為父佈局的子View。

如下:

<?xml version="1.0" encoding="utf-8"?>

<RelativeLyout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent">

<ImageView

android:id="@+id/iv_image"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="10dp"

android:src="@mipmap/ic_launcher" />

<TextView

android:id="@+id/tv_title"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_marginLeft="10dp"

android:layout_marginTop="16dp"

android:layout_toRightOf="@+id/iv_image"

android:text="這個是MergeLayout"

android:textSize="16sp" />

<TextView

android:id="@+id/tv_content"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_below="@+id/tv_title"

android:layout_marginLeft="10dp"

android:layout_marginTop="10dp"

android:layout_toRightOf="@+id/iv_image"

android:text="這個是MergeLayout,這個是MergeLayout"

android:textSize="12sp" />

</RelativeLyout>

使用include標籤引入後,層級關係如下:

現在我們把標籤改為merge,層級關係如下:

可以明顯看到中間少了一層。

(3)ViewStub標籤

ViewStub繼承於View,它是一個輕量級且寬高為0的元件,本身不參與佈局和繪製。因此使用它可以做到在不需要的時候不載入,在需要的時候再載入,從而提高效能。

那麼如何做到需要的時候顯示呢?

可以通過以下兩種方式:

setVisiable(View.Visiable)或者findViewById().inflate()

(3)使用ConstaintLayout

ConstaintLayout允許在不使用任何巢狀的情況下建立複雜佈局,與RelativeLayout相似,可以依賴兄弟容器和父控制元件之間的相對關係。常見屬性:

app:layout_constraintLeft_toLeftOf="parent"

app:layout_constraintTop_toTopOf="parent"

app:layout_constraintLeft_toRightOf="@+id/iv_image"

例如:

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout

xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:id="@+id/lay_root"

android:layout_width="match_parent"

android:layout_height="match_parent">

<ImageView

android:id="@+id/iv_image"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_margin="10dp"

android:src="@mipmap/ic_launcher"

android:layout_marginStart="16dp"

app:layout_constraintLeft_toLeftOf="parent"

android:layout_marginLeft="16dp"

android:layout_marginTop="16dp"

app:layout_constraintTop_toTopOf="parent" />

<TextView

android:id="@+id/tv_title"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_toRightOf="@+id/iv_image"

android:text="這個是ConstraintLayout"

android:textSize="16sp"

app:layout_constraintLeft_toRightOf="@+id/iv_image"

android:layout_marginStart="20dp"

android:layout_marginLeft="20dp"

android:layout_marginTop="20dp"

app:layout_constraintTop_toTopOf="parent" />

<TextView

android:id="@+id/tv_content"

android:layout_width="0dp"

android:layout_height="wrap_content"

android:layout_below="@+id/tv_title"

android:layout_toRightOf="@+id/iv_image"

android:text="這個是ConstraintLayout,這個是RelativeLayout"

android:textSize="12sp"

app:layout_constraintTop_toBottomOf="@+id/tv_title"

android:layout_marginTop="16dp"

app:layout_constraintLeft_toLeftOf="@+id/tv_title" />

</android.support.constraint.ConstraintLayout>

2、過度繪製

過度繪製其實指的是螢幕內某個畫素在同一幀的時間內被繪製了多次。

而在多層次重疊的UI結構裡,如果不可見的UI也在繪製的話,就會導致某些畫素區域被繪製多次,從而浪費大量的cpu和gpu資源。

目前提供兩種方式來檢測過度繪製:

  • 手機自帶檢測工具

手機的開發者模式中會有一項為除錯GPU過度繪製>顯示GPU過度繪製,設定後,開啟任何一個app,就可以看到介面上出現藍、綠、粉、紅四種顏色中的一種或者多種。

藍色:1次過度繪製

綠色:2次過度繪製

粉絲:3次過度繪製

紅色:4次過度繪製

例如:

  • Android device monotor

開啟Hierarchy Viewer(/'haɪərɑːkɪ/),執行模擬器,開啟對應的Activity介面,就可以看到如下

其中每一個View中,下面三個點依次表示測量、佈局、繪製的時間,紅點和黃點表示速度慢,而藍綠則相對好一些。

在瞭解瞭如何分析過度繪製後,我們如何去處理過度繪製?

(1)去掉Windwo的預設背景

一般來說我們使用的Activity都會有一些預設的主題,通常這個主題會有一個對應的背景,被DecoreView持有,我們自定義佈局時如果又添加了一個背景圖或者設定背景色,就會產生一個overdraw,因此可以考慮去掉預設的背景。

我們可以在onCreate()的setContentView之前呼叫

getWindow().setBackGroundDrawable(null)

或者是在theme中加入windowbackground=“null”

(2)去掉其他不必要的背景

有時候我們為layout設定一個背景,而它的子View也有背景,此時就會造成重疊。針對這種情況我們首先考慮新增背景是否需要,如果需要我們可以考慮通過selector普通狀態下設定背景為透明,點選狀態下設定背景來減少重繪。

(3)優化onDraw方法

  • 避免在onDraw()方法中分配物件,因為onDraw方法可能被多次呼叫,這樣的話可能會產生很多不必要的物件
  • 使用ClipRect制定個繪製區域

在使用自定義View時,我們可以利用此方法來指定一塊可見區域,用於繪製,其他區域則會被忽略,即只繪製clipRect指定的區域。

例如在圖片層疊時,我們將重疊部分不再繪製,只繪製不重疊部分。直接繪製如下:

可以看到重疊部分出現了過度繪製,而實際上重疊部分我們不需要繪製底層部分,因為我們只能看到上面的圖層,因此我們可以利用clipRect()來指定區域。如下:

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.save();

int bits = mBitmaps.length;

for (int i = 0; i < bits; i++) {

Bitmap bitmap = mBitmaps[i];

int bitW = bitmap.getWidth();

int bitH = bitmap.getHeight();

if (i != 0) {

canvas.translate(bitW / 2, 0);//每繪製完圖片時,畫布向前平移width/2的長度

}

canvas.save();

if (i != bits - 1) {

canvas.clipRect(0, 0, bitW / 2, bitH);//選擇繪製區域,每次只繪製圖片的一半

}

canvas.drawBitmap(bitmap, 0, 0, null);

canvas.restore();

}

canvas.restore();

}

(4)標籤

也就是前面提到的include、merge、viewStub標籤