天天看點

Android 性能優化:記憶體優化、布局、電量、流量、啟動

一、布局優化

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,就可以檢視對應的層級關系,如下:

Android 性能優化:記憶體優化、布局、電量、流量、啟動

2)Component Tree

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

Android 性能優化:記憶體優化、布局、電量、流量、啟動

選擇并檢視我們的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标簽引入後,層級關系如下:

Android 性能優化:記憶體優化、布局、電量、流量、啟動

現在我們把标簽改為merge,層級關系如下:

Android 性能優化:記憶體優化、布局、電量、流量、啟動

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

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資源。

關于繪制的原理可以參考https://blog.csdn.net/xsf50717/article/details/78444047,這裡不再贅述

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

手機自帶檢測工具

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

藍色:1次過度繪制

綠色:2次過度繪制

粉絲:3次過度繪制

紅色:4次過度繪制

例如:

Android 性能優化:記憶體優化、布局、電量、流量、啟動
  • Android device monotor

    打開Hierarchy Viewer(/'haɪərɑːkɪ/),運作模拟器,打開對應的Activity界面,就可以看到如下:

    Android 性能優化:記憶體優化、布局、電量、流量、啟動
    其中每一個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指定的區域。

    例如在圖檔層疊時,我們将重疊部分不再繪制,隻繪制不重疊部分。直接繪制如下:

    Android 性能優化:記憶體優化、布局、電量、流量、啟動
    可以看到重疊部分出現了過度繪制,而實際上重疊部分我們不需要繪制底層部分,因為我們隻能看到上面的圖層,是以我們可以利用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标簽

二、記憶體優化

1、防止記憶體洩漏,還有些代碼時要注意防止記憶體洩漏,還可以使用Leakcancary記憶體檢測工具。

2、合理使用如數組、連結清單、隊列、棧、樹、哈希表等資料結構。在這說一下,推薦兩個安卓常用的SparseArray和ArrayMap,

他們相比hashmap比較節省記憶體,在1000以下,性能上的差異可以忽略。

3、用int或者字元串常量代替枚舉,枚舉太占記憶體,大概是int的2倍。

記憶體優化之圖檔優化

1、把圖檔素材放在合适的目錄下

2、bitmap優化,就是加載圖檔時可以調用BitmapFactory.Options來按照一定采樣率來加載所需的圖檔大小。

3、用Glide、picisoo等三方架構加載圖檔。
           

三、電量優化

耗電的原因:1、大資料的傳輸 2、解析大量的文本資料 3、不停的在網絡間切換

解決方案:

1、先檢視是否處于網絡連接配接狀态,如果沒有連接配接成功,就不要執行響應的程式。

    2、使用高效的資料格式和解析方法,如json

    3、在進行大資料請求時,使用GZip壓縮,它會大大減少文本檔案的體積,進而使資料的傳輸效率變高

    在開發中,也要靈活的判斷目前電量,如果電量低的話,就減少一些更新的操作,如果充電時或者電量充足時

    就加快App更新速度
           

四、流量優化

1、同步資料盡量在wifi下

2、接口能合一則合,産品設計簡單極緻

3、做好本地資料緩存,以及和服務端的同步機制;例如get請求和cache-control

4、webview做好本地緩存,加載時優先加載本地資源,當然也需要服務端支援

修改http伺服器中的配置,使其支援text/cache-manifest,我使用的是apache伺服器,是windows版本的,在apache的conf檔案夾中找到mime.types檔案,打開後在檔案的最後加上“text/cache-manifest mf manifest”,重新開機伺服器

5、壓縮H5的圖檔大小,以及改變圖檔格式為webp,或者把背景圖改為jpg,将純色圖用xml來寫。

五、啟動優化

1、在Application的構造器方法、attachBaseContext()、onCreate()方法中不要進行耗時操作的初始化,一些資料預取放在異步線程中,可以采取Callable實作。

2、對于sp的初始化,因為sp的特性在初始化時候會對資料全部讀出來存在記憶體中,是以這個初始化放在主線程中不合适,反而會延遲應用的啟動速度,對于這個還是需要放在異步線程中處理。

3、對于MainActivity,由于在擷取到第一幀前,需要對contentView進行測量布局繪制操作,盡量減少布局的層次,考慮StubView的延遲加載政策,當然在onCreate、onStart、onResume方法中避免做耗時操作。

遵循上面三種政策可明顯提高app啟動速度。

繼續閱讀