1. 程式人生 > >Android開發效能優化(記錄、自用)

Android開發效能優化(記錄、自用)

雖然做Android開發已經有一段時間了,但是開發過程中經常是忙著實現功能,卻忽略了效能上的優化。以下都是來自於各位前輩的總結。特此總結記錄一下。

 一、Android效能優化之佈局優化技巧

佈局優化就是用最少的view實現一樣的效果layout。最少的view也就是會減少層級巢狀,從而使渲染的速度加快。

原部落格在佈局優化方面總結了8個點:

1)重用:利用<include/>標籤,可以在一個佈局中引入另外一個佈局。如果有一個佈局是多個頁面重複出現的,可以利用<include/>標籤這樣就可以隨用隨調。便於統一修改使用。

2)合併:減少巢狀,不影響層級深度的情況下,使用LinearLayout而不是RelativeLayout。如果非要巢狀,那麼儘量避免RelativeLayout巢狀RelativeLayout。

在減少巢狀方面還會用到<merge/>標籤。該標籤會幫助你排除把一個佈局插入另一個佈局中產生的多餘的ViewGroup.比如說,你的複用佈局是一個垂直的線性佈局,包含兩個子檢視,當它被插入到另一個垂直的線性佈局時,就是一個垂直的LinearLayout裡面包含一個垂直的LinearLayout。這個巢狀的佈局沒有任何意義,而且會讓UI效能變差,所以為了避免插入冗餘的VeiwGroup的情況,我們可以把複用佈局的根節點換成<merge/>。

3)利用TextView同時顯示圖片和文字。避免巢狀。

4)使用TextView的lineSpacingExta屬性。

5)使用Spannable或Html.fromHtml

6)按需載入ViewStub,ViewStub標籤同<include/>標籤一樣可以用來引入一個外來的佈局,不同的是ViewStub引入的佈局預設不會擴張,即既不會佔用顯示也不會佔用位置,從而在解析layout時節省cpu和記憶體。ViewStub常常用來引入那些預設不會顯示,只在特殊情況下顯示的佈局,比如進度佈局。

7)使用LinearLayout自帶的分割線

8)Space控制元件,它是一個輕量級的控制元件,我們可以利用一個View然後margin但是違背了少用view的初衷。這種情況我們可以使用Space控制元件。

二、Android效能優化之apk瘦身

記得今年年初找工作的時候,答了一套題其中一個就是如何給apk瘦身.....當時答的比較片面,結果是並沒有面試成功,我想一定是他們沒有眼光,一定是這樣。

以下為閱讀總結,摘取自原部落格,詳細內容可參看原部落格

1)概述:為什麼要APK瘦身,因為apk越大,使用者在下載過程中,耗費的流量會多,安裝等待的時間也會長,對於產品本身下載轉化率會越低。所以apk的瘦身優化是很重要的。

2)包體分析:在Android Studio中build--->Analyze APK--->選擇要分析的apk包。

3)使用一套資源。只取一套設計圖,很多大公司也是如此但卻能顯著減少資源佔用的大小。

4)開啟minifyEnabled混淆程式碼,可以大大減少apk大小。

android {
    buildTypes {
        release {
            minifyEnabled true
        }
    }
}
在proguard中,是否保留符號表對app的大小有顯著影響,可酌情不保留,但是建議儘量保留用於除錯。

5)開啟shrinkResources去除無用的資源

android {
    buildTypes {
        release {
            shrinkResources true
        }
    }
}
6)清理無用的資源

在5)中設定好之後。打包發現只是把部分無用的資源或者是更小的東西代替掉。因為shrinkResources true只是能去除沒有任何父函式呼叫的情況的資源,但是我們想要實現的效果是所有廢棄的程式碼無用的圖片都要清理。這時候真正起效果的是AS自帶的"Remove Unused Resources"外掛來實現。Refactor--->Remove Unused Resources

7)刪除無用的語言資源,比如設定只支援中文。

8)使用tinypng壓縮png圖片。

9)使用jpg,在啟動頁或者活動頁採用jpg是明智的,會比png小到一半不止。

10)圖片也可以使用webp格式,一種新的圖片格式,壓縮比比jpg高效果不輸jpg.

11)縮小大圖,如果工程裡還有大圖,可以適當的縮小,對視覺影響是極小的。

12)覆蓋第三方庫裡的大圖。13)精簡so 14)使用微信資源壓縮打包工具  15)使用provided編譯  16)向量圖

17)使用shape背景,可以省去大量的背景圖片。18)使用著色方案可以減少selector這樣的檔案  

19)線上化素材庫   20)避免重複的庫    21)清理第三方的庫和冗餘的程式碼    22)支援外掛化   

23)Facebook的redex優化位元組碼   

具體可以檢視原部落格

三、Android效能優化之App啟動優化

以下摘取自原部落格

應用的啟動方式分為冷啟動和熱啟動,啟動優化指的是優化冷啟動。

首先大概瞭解一下App的啟動過程:


應用的啟動時間指的就是從點選圖示開始創建出一個新的程序直到我們看到了介面的第一幀,這段時間就是應用的啟動時間。

為了減少應用啟動時的耗時可以採取以下策略

1、在Application的構造器方法、attachBaseContext()、onCreate()方法中不要進行耗時操作的初始化,一些資料預取放在非同步執行緒中,可以採取Callable實現。 
2、對於sp的初始化,因為sp的特性在初始化時候會對資料全部讀出來存在記憶體中,所以這個初始化放在主執行緒中不合適,反而會延遲應用的啟動速度,對於這個還是需要放在非同步執行緒中處理。 
3、對於MainActivity,由於在獲取到第一幀前,需要對contentView進行測量佈局繪製操作,儘量減少佈局的層次,考慮StubView的延遲載入策略,當然在onCreate、onStart、onResume方法中避免做耗時操作。

優化啟動時的體驗:

我們可以加入一些配置來提高app啟動時候的使用者體驗。

方案一、為頁面單獨寫一個主題,可以設定一張待顯示的圖片也可以是一種顏色,然後在清單檔案中給MainActivity設定。

<style name="AppTheme.Launcher">
 <item name="android:windowBackground">@drawable/bule</item>
</style>
//...
  <activity
   android:name=".MainActivity"
   android:label="@string/app_name"
   android:theme="@style/AppTheme.Launcher">
   <intent-filter>
    <action android:name="android.intent.action.MAIN" />
    <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
  </activity>
然後在MainActivity中載入佈局之前把AppTheme重新設定給MainActivity.
@Override
 protected void onCreate(Bundle savedInstanceState) {

  setTheme(R.style.AppTheme);
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
} 

方案二、設定AppTheme的style,同樣可以設定一張背景圖片或設定設定一個背景顏色。

1)設定背景圖片,當程式啟動的時候,先顯示圖片,避免了黑屏或者白屏的問題

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:screenOrientation">portrait</item>
        <item name="android:windowBackground">>@mipmap/splash</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
</style>

2)設定背景顏色,把顏色設定成了透明的,這樣就避免了白屏或者黑屏的問題了。
 <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:screenOrientation">portrait</item>
    </style>

四、Android效能優化之渲染優化

五、Android效能優化之Bitmap優化

以下摘取自原博文

Android開發中,Bitmap操作不慎就會造成OOM(記憶體溢位),所以開發中需要注意Bitmap的優化。

為什麼Bitmap操作不慎會導致OOM?

1.每個機型在編譯ROM時都設定了一個應用堆記憶體VM值上限dalvik.vm.heapgrowthlimit,用來限定每個應用可用的最大記憶體,超出這個最大值將會報OOM。這個閥值,一般根據手機螢幕dpi大小遞增,dpi越小的手機,每個應用可用最大記憶體就越低。所以當載入圖片的數量很多時,就很容易超過這個閥值,造成OOM。

2.圖片解析度越高,消耗的記憶體越大,當載入高解析度圖片的時候,將會非常佔用記憶體,一旦處理不當就會OOM。例如,一張解析度為:1920x1080的圖片。如果Bitmap使用 ARGB_8888 32位來平鋪顯示的話,佔用的記憶體是1920x1080x4個位元組,佔用將近8M記憶體,可想而知,如果不對圖片進行處理的話,就會OOM。

3.在使用ListView, GridView等這些大量載入view的元件時,如果沒有合理的處理快取,大量載入Bitmap的時候,也將容易引發OOM.

文中將Bitmap優化的策略總結為以下3種: 
1.對圖片質量進行壓縮
2.對圖片尺寸進行壓縮
3.使用libjpeg.so庫進行壓縮

具體檢視原博文。當然平時開發中我們使用Glide等框架載入圖片,框架已經對圖片做了處理

六、Andorid效能優化之記憶體優化

以下摘自原部落格

再java中記憶體的分配是由程式完成的,而記憶體的釋放是由垃圾收集器(Garbage Collection ,GC)完成的。也就是說釋放記憶體是不需要手動呼叫的,當然隨之就會帶來記憶體洩漏的的問題。Android中常見的記憶體洩漏可以檢視:Android中常見的記憶體洩漏

程式在執行的時候記憶體分配有三種策略,分別是靜態的,棧式的,堆式的,三種儲存策略使用的記憶體空間主要分別是靜態儲存區(方法區)、棧區、堆區。

靜態儲存區(方法區)是指記憶體在程式編譯的時候就已經分配好了,這塊記憶體在程式執行的整個過程都是存在的,主要儲存的是靜態的資料,全域性的static資料,常量。

棧區  是指在執行函式的時候,函式內的區域性變數的儲存單元可以在棧區找到,當函式執行結束後這些儲存單元會自動被釋放。棧記憶體分配運算內置於處理器的指令集中,效率高但是分配的記憶體容量有限。

堆區  也是一個動態分配記憶體區,動態記憶體的生存期可以由我們來決定,程式在執行的時候可以通過malloc或者new 申請任意大小的記憶體,程式設計師自己要負責在適當的時候釋放掉記憶體,如果不釋放的話,程式在最後會釋放掉動態記憶體。所以一個好的編碼習慣是當某動態記憶體不需要的時候就要及時的清理掉。

棧區和堆區的區別:

二者的區別其實從上面的解釋中已經能夠看出,簡單的來說就是棧區分配的記憶體自動釋放,堆區分配的記憶體,系統不是放,哪怕程式退出,記憶體還是在那裡。所以堆記憶體要自己釋放,不然就會出現“記憶體洩漏”的情況。

堆是不連續的記憶體區域,堆的大小是受限於計算機系統中有效的虛擬記憶體,所以堆的空間比較大,比較靈活。

棧是一塊連續的記憶體區域,大小是作業系統預定好的。

對於堆,頻繁的new/delete會造成大量記憶體碎片,使程式效率降低。對於棧,它是先進後出的佇列,進出一一對應,不產生碎片,執行效率高。

這裡寫圖片描述

所以我們可以得出結論: 
1.區域性變數的基本資料型別和引用儲存於棧中,引用的物件實體儲存於堆中。因為它們屬於方法中的變數,生命週期隨方法而結束。

2.成員變數全部儲存與堆中(包括基本資料型別,引用和引用的物件實體),因為它們屬於類,類物件終究是要被new出來使用的。

3.我們所說的記憶體洩露,只針對堆記憶體,他們存放的就是引用指向的物件實體。

記憶體洩漏產生的原因:

在java中記憶體的分配是由程式完成的,而記憶體的釋放是由垃圾收集器(Garbage Collection,GC)完成的。程式設計師不需要呼叫函式來釋放記憶體,並且不再被其他物件引用的那些物件所佔用的空間。
GC為了能夠正確的釋放物件,必須監控每一個物件的執行狀態,包括物件的申請、引用、被引用、賦值,GC都進行監控。監視物件
狀態是為了更加準確的,及時的釋放物件,而釋放物件的根本原則就是該物件不再被引用。
什麼是引用:通過A能操作物件B,那麼就就說A持有B的引用,或者說A是B的引用。B的引用數+1.
(1)比如 Person p1 = new Person();通過P1能操作Person物件,因此P1是Person的引用; 
(2)比如類O中有一個成員變數是I類物件,因此我們可以使用o.i的方式來訪問I類物件的成員,因此o持有一個i物件的引用。

GC過程與物件的引用型別是嚴重相關的,我們來看看Java對引用的分類Strong reference, SoftReference, WeakReference, PhatomReference

這裡寫圖片描述

在Android應用的開發中,為了防止記憶體溢位,在處理一些佔用記憶體大而且宣告週期較長的物件時候,可以儘量應用軟引用和弱引用技術。

軟/弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收器回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。利用這個佇列可以得知被回收的軟/弱引用的物件列表,從而為緩衝器清除已失效的軟/弱引用。

假設我們的應用會用到大量的預設圖片,比如應用中有預設的頭像,預設遊戲圖示等等,這些圖片很多地方會用到。如果每次都去讀取圖片,由於讀取檔案需要硬體操作,速度較慢,會導致效能較低。所以我們考慮將圖片快取起來,需要的時候直接從記憶體中讀取。但是,由於圖片佔用記憶體空間比較大,快取很多圖片需要很多的記憶體,就可能比較容易發生OutOfMemory異常。這時,我們可以考慮使用軟/弱引用技術來避免這個問題發生。以下就是高速緩衝器的雛形:

首先定義一個HashMap,儲存軟引用物件。

1.private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
  • 1
  • 1

再來定義一個方法,儲存Bitmap的軟引用到HashMap

public class CacheSoftRef {

    //首先定義一個HashMap,儲存引用物件
    private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();

    //再來定義一個方法,儲存Bitmap的軟引用到HashMap
    public void addBitmapToCache(String path) {
        //強引用的Bitmap物件
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        //軟引用的Bitmap物件
        SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
        //新增該物件到Map中使其快取
        imageCache.put(path, softBitmap);
    }

    //獲取的時候,可以通過SoftReference的get()方法得到Bitmap物件
    public Bitmap getBitmapByPath(String path) {
        //從快取中取軟引用的Bitmap物件
        SoftReference<Bitmap> softBitmap = imageCache.get(path);
        //判斷是否存在軟引用
        if (softBitmap == null) {
            return null;
        }
        //通過軟引用取出Bitmap物件,如果由於記憶體不足Bitmap被回收,將取得空,如果未被回收,
        //則可重複使用,提高速度。
        Bitmap bitmap = softBitmap.get();
        return bitmap;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

使用軟引用以後,在OutOfMemory異常發生之前,這些快取的圖片資源的記憶體空間可以被釋放掉的,從而避免記憶體達到上限,避免Crash發生。

如果只是想避免OutOfMemory異常的發生,則可以使用軟引用。如果對於應用的效能更在意,想盡快回收一些佔用記憶體比較大的物件,則可以使用弱引用。

另外可以根據物件是否經常使用來判斷選擇軟引用還是弱引用。如果該物件可能會經常使用的,就儘量用軟引用。如果該物件不被使用的可能性更大些,就可以用弱引用。

所以我們得出記憶體洩漏的原因:堆記憶體中的長生命週期的物件持有短生命週期物件的強/軟引用,儘管短生命週期物件已經不再需要,但是因為長生命週期物件持有它的引用而導致不能被回收,這就是Java中記憶體洩露的根本原因。