Android 性能優化以內存優化

前言

Android App優化這個問題,我相信是Android開發者一個永恆的話題。本篇文章也不例外,也是來說解一下Android內存優化。那麼本篇文章有什麼不一樣呢? 本篇文章主要是從最基礎的Android系統內存管理方面出發再到App優化方法,讓你能更加清楚地理解、處理Android內存優化問題,下面進入正題。android

Android內存的管理方式

Android系統分配和回收方式

一般狀況下,一個APP就是一個進程或者說是一個虛擬機。也就是說咱們一個APP運行的時候那麼就有一個單獨的進程在運行。可是也有不少的大公司在Mainfest指定process進程名字,因此會看到一個APP對應多個進程的狀況。算法

咱們用實際的代碼來演示看一下: 這是我運行的一個App的包名:數組

咱們在Windows上看一下他的進程:緩存

UID表示:用戶 PID表示:進程ID PPID表示:進程父ID CMD表示:名字 能夠看一下咱們的App運行的進程在上面能夠找到

進程ID:12768 進程父ID:1509 經過父ID咱們能夠找到:

咱們經過這張圖能夠清楚的看到一個咱們熟悉的名字:zygote 這個是什麼呢?

zygote進程是由init進程啓動起來,在Android中,zygote是整個系統建立新進程的核心進程,換句話說就是zygote進程是android的孵化進程也就是父進程。bash

經過命令 dumpsys meminfo + 進程名字,能夠獲取具體信息:網絡

簡單介紹下咱們須要知道:

Pss Total : 當前使用物理內存的大小數據結構

Heap Size : 堆空間app

Heap Alloc : 分配多少堆空間框架

Heap Free :空閒堆空間ide

通常來講:Heap Size = Heap Alloc + Heap Free

Native Heap:指的JIN開發所佔的堆空間 Dalvik Heap : 虛擬機的堆空間 Dalvik Other : 虛擬機其餘所佔空間 stack : 堆棧佔多少

其餘還有不少的有用信息,就不一一解釋了,感興趣的能夠多去了解這方面的知識,我這裏就主要說一下咱們常常內存泄漏主要在:Pss Total 中的TOTAL不斷的變大就能夠看出內存泄漏

GC就是垃圾收集器,只有在Heap剩餘空間不夠的時候纔會觸發垃圾回收。

Java的垃圾回收機制就是你在開發的時候不用去關注內存是否去釋放,這個是一個優勢,可是也有缺點就是當前的變量不使用了,放在一邊,只有當內存不夠的時候纔會觸發GC去回收這些不使用的內存。爲何說是個缺點呢?

**由於在GC觸發垃圾回收的時候,全部的線程都會被暫停,此時就會咱們常常出現的卡頓現象。

APP內存限制機制

首先咱們要知道一個理論:每一個APP分配的內存最大限制,是隨着設備的不一樣而改變的。所以,咱們才須要咱們去管理咱們的內存,有一點要明白的就是系統分配的內存,通常狀況下是確定夠使用的,若是出現OOM這種狀況,那麼一定是你的APP優化的不夠好。

最常說的吃內存的: 高清圖片,如今的手機拍照動不動就是以M爲單位。可是就咱們目前的開發來講,大多數人使用的是Glide、Picasso的框架,其實都是框架給咱們處理了管理圖片的問題。

爲何要限制內存?

假如咱們每一個App都不限制內存的大小,那麼各自的APP都無論理,讓內存一直增大,Android系統是容許多個APP同時運行的,總的空間是固定的,最終致使結果必然有些APP沒有內存能夠分配。

切換後臺是APP清理機制

APP切換的時候採用的是LRU Cache這種算法。

什麼是LRU Cache算法?

LRU Cache是一個Cache置換算法,含義是「最近最少使用」,當Cache滿(沒有空閒的cache塊)時,把知足「最近最少使用」的數據從Cache中置換出去,而且保證Cache中第一個數據是最近剛剛訪問的。由「局部性原理」,這樣的數據更有可能被接下來的程序訪問。 切換到實際場景就是,咱們APP切換的時候會把剛剛訪問的放在第一個。當咱們內存不足的時候咱們就會置換出最近最少使用、或者最久未使用的。

而最近使用的APP,最不容易被清理掉。

當咱們的應用要被清理掉的時候,或者是咱們的內存出現不夠的時候,咱們的APP中會回調一個方法

@Override
public void onTrimMemory(int level) {
    super.onTrimMemory(level);
}
複製代碼

咱們解釋一下Level這參數的意義:

1.當你的app在後臺時:

TRIM_MEMORY_COMPLETE :當前進程在LRU列表的尾部,若是沒有足夠的內存,它將很快被殺死。這時候你應該釋聽任何不影響app運行的資源。

TRIM_MEMORY_MODERATE :當前進程在LRU列表的中部,若是系統進一步須要內存,你的進程可能會被殺死。

TRIM_MEMORY_BACKGROUND:當前進程在LRU列表的頭部,雖然你的進程不會被高優殺死,可是系統已經開始準備殺死LRU列表中的其餘進程了, 所以你應該儘可能的釋放可以快速回復的資源,以保證當用戶返回你的app時能夠快速恢復。 。

2.當你的app的可見性改變時:

TRIM_MEMORY_UI_HIDDEN:當前進程的界面已經不可見,這時是釋放UI相關的資源的好時機。

3.當你的app正在運行時:

TRIM_MEMORY_RUNNING_CRITICAL:雖然你的進程不會被殺死,可是系統已經開始準備殺死其餘的後臺進程了,這時候你應該釋放無用資源以防止性能降低。下一個階段就是調用」onLowMemory()」來報告開始殺死後臺進程了,特別是情況已經開始影響到用戶。

TRIM_MEMORY_RUNNING_LOW:雖然你的進程不會被殺死,可是系統已經開始準備殺死其餘的後臺進程了,你應該釋放沒必要要的資源來提供系統性能,不然會 影響用戶體驗。

TRIM_MEMORY_RUNNING_MODERATE:系統已經進入了低內存的狀態,你的進程正在運行可是不會被殺死。

咱們能夠用過這個Level的參數來判斷當前APP的狀況,來優化內存。

監控內存的幾種演示方法

1.在咱們的代碼中動態打印出咱們的內

private void printMemorySize() {
       ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
       //以M爲單位  當前APP最大限制內存
       int memoryClass = activityManager.getMemoryClass();
       //經過在Manifest <application>標籤中largeHeap屬性的值爲"true"  爲應用分配的最大的內存
       int largeMemoryClass = activityManager.getLargeMemoryClass();
       StringBuilder stringBuilder = new StringBuilder();
       stringBuilder.append("memoryClass===" + memoryClass+"\n")
               .append("largeMemoryClass===" + largeMemoryClass);

       Logger.e(stringBuilder.toString());


       //以M爲單位輸出當前可用總的Memory大小
       float totalMemory = Runtime.getRuntime().totalMemory() * 1.0f / (1014 * 1024);
       // 以M爲單位輸出當前空閒的Memory大小
       float freeMemory = Runtime.getRuntime().freeMemory() * 1.0f / (1024 * 1024);
       // 以M爲單位輸出虛擬機限制的最大內存
       float maxMemory = Runtime.getRuntime().maxMemory() * 1.0f / (1024 * 1024);

       StringBuilder builder = new StringBuilder();
       builder.append("totalMemory==" + totalMemory + "\n")
               .append("freeMemory==" + freeMemory + "\n")
               .append("maxMemory==" + maxMemory + "\n");

       Logger.e(builder.toString());
   }
複製代碼

經過上面的代碼和圖咱們就能夠清楚的知道咱們的應用的內存的狀況。 這種方法也是最複雜的方法,須要代碼去打印,下面兩種是咱們的Android studio提供的工具。

2.Android studio 3.1 工具 Android profiler

這裏寫圖片描述

這種方式是方便查看的,直接在下方點擊 Android profiler就能夠了,方便快捷

3.DDMS

打開DDMS

這也是看咱們的APP的內存使用狀況,標記了的假如你的APP在運行的時候 data object和 class object 不斷的變化,那就說明你的應用可能有內存泄露了,這個時候你就須要檢查一下。

Android內存優化

數據結構的優化

1.字符串拼接

咱們都知道咱們的若是使用string 的 「+」方式來拼接字符串,會產生字符串中間內存塊,這些內存塊是無用的,形成內存浪費,這種方式低效、並且耗時。咱們就是實際看看:

int length = 20;
       int rawLength = 300;
       int[][] intMatrix = new int[length][rawLength];
       for (int i = 0; i < length; i++) {
           for (int j = 0; j < rawLength; j++) {
               intMatrix[i][j] = ran.nextInt();
           }
       }
複製代碼

初始化一個兩維的矩陣,獲得隨機數。

/**
    * 用StringBuilder鏈接起來
    */
   private void strBuild() {
       StringBuilder builder = null;
       Log.e("test", "builder start:");
       for (int i = 0; i < length; i++) {
           for (int j = 0; j < rawLength; j++) {
               builder.append(intMatrix[i][j]+"").append(",");
           }
           Log.e("test", "builder:" + i);

       }
       Log.e("test", "add finish:" + builder.toString().length());
   }
複製代碼
/**
    * 字符串用 「+」 鏈接起來
    */
   private void strAdd() {
       String str = null;

       Log.e("test", "add start:");
       for (int i = 0; i < length; i++) {
           for (int j = 0; j < rawLength; j++) {
               str = str + intMatrix[i][j];
               str = str + ",";
           }
           Log.e("test", "add:" + i);

       }
       Log.e("test", "add finish:" + str.length());
   }
複製代碼

獲得的用 「+」連接的結果:

耗時2.6s

用stringBuilder的結果:

耗時0.06s

這就能夠看出咱們的String和StringBuilder的使用效率的對比了。

2.替換HashMap

還有值得一提的就是JAVA裏面的HashMap,這個使用的效率是不高的,咱們要用ArrayMap、SparseArray替換。

3.內存抖動

內存都用的主要緣由是咱們內存變量的使用不當形成的

/**
    * 試驗內存抖動
    */
   private void doChurn() {
       Log.e("test", "doChurn start: ");
       int len = 10;
       int rawLen = 450000;
       for (int i = 0; i < rawLen; i++) {
           String[] strings = new String[len];
           for (int j = 0; j < len; j++) {
               strings[j] = String.valueOf(ran.nextInt());
           }
           Log.e("test", "doChurn : " + i);
       }
       Log.e("test", "doChurn end: ");
   }
複製代碼

重點就是在建立string數組那裏,是放在第一個for循環裏面,rawLen=450000,所以會建立450000個對象。

這一塊就是咱們的內存抖動的狀況。

分析一下緣由:

咱們在for循環裏面建立了45000個string對象,而後再裏面添加了數據以後就沒有使用了,當建立的對象達到內存限制的時候就會觸發GC回收,接下來又建立,又回收,這樣就致使了內存抖動的狀況。

內存的複用

  • 複用系統自帶的資源

  • ListView/GridView中的ConvertView的複用,固然咱們如今ListView和GridView使用已經不多了,都被RecyclerView給取代了

  • 咱們在自定義View的要避免在onDraw中去建立對象,由於onDraw方法會常常執行

內存泄露

內存泄露已是老生常談了,可是咱們仍是要舉一些簡單的例子讓你們知道怎樣會形成內存泄露。

什麼是內存泄露?

內存泄露:因爲你代碼的問題,致使某一塊內存雖然已經不使用了,可是依然被其餘的東西(對象或者其餘)引用着,使得GC沒發對它回收。 因此內存泄露會致使APP剩餘可用的Heap愈來愈少,頻繁觸發GC。

1.內部內形成的內存泄露

/**
    * 建立一個線程
    */
   private class MyThread extends Thread {

       @Override
       public void run() {
           try {
               //休眠5分鐘
               Thread.sleep(1000 * 60 * 5);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
   }
複製代碼

上面這個是一個Activity的內部內,每次啓動這個activity都會開啓這個線程。點擊按鈕開啓這個Activity 觸發線程休眠 5min,而後按返回鍵,再點擊按鈕開啓這個Activity觸發線程休眠5min,就這樣依次反覆操做屢次。咱們5min中內能夠重複這樣的N次操做,咱們的操做會頻繁的觸發GC回收,可是因爲咱們的線程還在運行,這個內部類是默認持有外部類對象,所以這個Activity就不會被回收,就形成了內存泄露。

**內部內又分不少種,靜態內部類、非靜態內部類、匿名內部類,這些內部類咱們都應該注意不要長時間引用Activity。

2.單例形成的內存泄露

建立一個單例

Activity獲取單例對象,並將Activity傳入單例中:

咱們假設這樣一個場景,咱們打開應用,而後點手機返回,等待一段時間假設10s,這樣就會形成內存泄露。

爲何會形成內存泄露呢?

AppManager appManager=AppManager.getInstance(this) 這句傳入的是Activity的Context,咱們都知道,Activty是間接繼承於Context的,當這Activity退出時,Activity應該被回收, 可是單例中又持有它的引用,致使Activity回收失敗,形成內存泄漏。 像這種狀況咱們應該怎麼避免呢? 咱們將傳入的this改爲getApplicationContext(),由於咱們Application的生命週期是和APP的生命週期一直的因此就不存在內存泄露的問題。

Android 圖片優化之OOM

如今咱們的APP基本上都會有圖片顯示,那麼有圖片顯示必然就會出現圖片的優化問題,若是處理不得當就會出現OOM。

1.什麼是OOM?

咱們程序申請須要10485776byte太大了,虛擬機沒法知足咱們,羞愧的shutdown自殺了

2.爲何會有OOM?

由於android系統的app的每一個進程或者每一個虛擬機有個最大內存限制,若是申請的內存資源超過這個限制,系統就會拋出OOM錯誤。跟整個設備的剩餘內存沒太大關係。好比比較早的android系統的一個虛擬機最多16M內存,當一個app啓動後,虛擬機不停的申請內存資源來裝載圖片,當超過內存上限時就出現OOM。 這一小節說的圖片優化OOM,爲何說圖片會形成OOM呢?由於咱們在網絡請求加載圖片的時候,咱們要申請內存來裝載圖片,而後咱們的一張圖片本來1M,可是下載下來以後轉換成Bitmap顯示到咱們的控件的話,那麼咱們的Bitmap此時的大小估計是好幾M,會翻好幾倍。當你下載多了,不注意回收這些Bitmap的話,就會形成OOM。

總結有一下三種狀況:

  • 直接加載 超大尺寸 圖片;
  • 圖片加載後 未及時釋放;
  • 在頁面中,同時加載 很是多 的圖片;

解決加載圖片出現OOM有幾種方法:

  • 對圖片進行裁剪以後再加載圖片。
  • 採用LruCache來緩存圖片
  • 對圖片進行適當的縮小以後再加載顯示

爲何這塊咱們沒有細講,主要是由於咱們如今的圖片加載主要都是使用這框架Glide、Picasso、Fresco 來加載圖片,咱們如今就像是傻瓜似的操做,直接傳入個Url就行了,圖片的優化問題框架已經給我作的很好了,無需咱們考慮那麼多。若是說有必要的話,我以後能夠來一篇框架的加載圖片原理,源碼解析,若有須要的能夠在後臺留言。

原創不易,若是以爲寫得好,掃碼關注一下點個贊,是我最大的動力。

關注我,必定會有意想不到的東西等你: 天天專一分享Android、JAVA乾貨

備註:程序圈LT

相關文章
相關標籤/搜索