五個維度:Android內(nèi)存管理、優(yōu)化

  RAM對于軟件開發(fā)環(huán)境而言是有價值的資源,但它對受限于物理內(nèi)存限制的操作系統(tǒng)具有更大的價值。即使Android Runtime和Dalvik virtual machein執(zhí)行常規(guī)的垃圾回收,但這并不意味著你可以忽略app在何時何地指派和釋放內(nèi)存。你仍然需要去避免產(chǎn)生內(nèi)存泄露。比如長期持有靜態(tài)成員變量常常引起內(nèi)存泄露。我們應該在合適的時間,比如在生命周期回調函數(shù)處釋放一些引用對象,來避免內(nèi)存泄露的發(fā)生。

創(chuàng)新互聯(lián)公司專注于企業(yè)成都營銷網(wǎng)站建設、網(wǎng)站重做改版、羅甸網(wǎng)站定制設計、自適應品牌網(wǎng)站建設、H5場景定制、商城網(wǎng)站開發(fā)、集團公司官網(wǎng)建設、成都外貿(mào)網(wǎng)站建設公司、高端網(wǎng)站制作、響應式網(wǎng)頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為羅甸等各大城市提供網(wǎng)站開發(fā)制作服務。

  這篇文章將闡述怎樣在app中主動的降低內(nèi)存消耗。關于java編程中清理資源的一般實踐,請參照其他關于資源引用管理的書籍或者在線文檔。如果你想分析一個運行中app的內(nèi)存情況,請閱讀文章“Tools for analyzing RAM usage”。如果你想知道AndroidRuntime和Dalvik virtual machine如何管理內(nèi)存,請參閱文章“Overview of Android Memory Management”。

(一)監(jiān)控內(nèi)存的使用情況:

  Androidframework和Android Studio能夠幫助你分析和調整app的內(nèi)存使用情況。Android framework 暴露了幾個在app運行時動態(tài)降低內(nèi)存消耗的API。Android Studio 提供了幾個觀察app內(nèi)存使用情況的工具。


分析內(nèi)存使用的工具:

  想要解決app產(chǎn)生的內(nèi)存問題,我們首先得知道內(nèi)存問題由什么引起。Android Studio提供了如下幾個分析內(nèi)存的工具:

  1.“MemoryMonitor”展示app 在一個單獨回話過程中如何指派內(nèi)存。這個工具會實時繪制包括垃圾回收事件等可用內(nèi)存和已占用內(nèi)存的模擬圖像。在程序運行時,你僅僅能通過初始化一個垃圾回收事件,獲取Java 堆快照。這個Memory Monitor 的輸出圖像能幫你定位到app在什么地方因產(chǎn)生過多的垃圾回收事件導致程序變慢。

關于Memory  Monitor的具體使用方法,請參閱“ViewingHeap Updates”

  2.“AllocationTracker”能夠讓我們詳細的跟蹤到app如何分配內(nèi)存。這個工具能夠記錄app的內(nèi)存分配情況,而且能夠列出所有被分配的對象以及分析快照。用這個工具你能夠追蹤到分配太多對象的代碼。

  關于AllocationTracker的具體使用方法,請參閱“Allocation Tracker Walkthrough”

(二)在合適的時間釋放內(nèi)存     

  Android設備在運行時的可用內(nèi)存會根據(jù)物理內(nèi)存總量以及用戶操作方法不斷變化。當系統(tǒng)內(nèi)存有壓力的情況下會發(fā)送信號來通知程序。app應監(jiān)聽這些通知,并依據(jù)監(jiān)聽結果來調整內(nèi)存使之使用適度。

用APIComponentCallbacks2 來響應app生命周期事件和 設備事件,根據(jù)監(jiān)聽相關信號的結果調整內(nèi)存的使用。方法onTrimMemory()能夠監(jiān)聽app運行在前臺或后臺時候的相關內(nèi)存事件。

在Activity中,實現(xiàn)回調方法onTrimMemory()來監(jiān)聽這些內(nèi)存變化的事件,如下代碼片段:

import android.content.ComponentCallbacks2;
// Other import statements ...
publicclassMainActivityextendsAppCompatActivity
   implementsComponentCallbacks2{

   // Other activity code ...

   /**
     * Release memory when the UI becomes hidden or whensystem resources become low.
     * @param level the memory-related event that wasraised.
     */
   publicvoid onTrimMemory(int level){

       // Determine which lifecycle or systemevent was raised.
       switch(level){

           caseComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:

               /*
                  Release any UI objects that currently hold memory.

                   Theuser interface has moved to the background.
                */

               break;

           caseComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
           caseComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
           caseComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:

               /*
                  Release any memory that your app doesn't need to run.

                   Thedevice is running low on memory while the app is running.
                   Theevent raised indicates the severity of the memory-related event.
                   Ifthe event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
                   beginkilling background processes.
                */

               break;

           caseComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
           caseComponentCallbacks2.TRIM_MEMORY_MODERATE:
           caseComponentCallbacks2.TRIM_MEMORY_COMPLETE:

               /*
                  Release as much memory as the process can.

                   Theapp is on the LRU list and the system is running low on memory.
                   Theevent raised indicates where the app sits within the LRU list.
                   Ifthe event is TRIM_MEMORY_COMPLETE, the process will be one of
                   thefirst to be terminated.
                */

               break;

           default:
               /*
                  Release anynon-critical data structures.

                  The appreceived an unrecognized memory level value
                  from thesystem. Treat this as a generic low-memory message.
                */
               break;
       }
   }

備注:回調方法onTrimMemory()在Android4.0中添加。更早的版本,能用回調方法onLowMemory()作為替代,大約和 TRIM_MEMORY_COMPLETE 事件相同。


(三)檢查你需要用到的內(nèi)存:

  為了支持多進程,android為每一個app設置了一個堆內(nèi)存上限。不同設備為app分配的具體上限值,取決于RAM的總大小。如果程序到達了這個上限值還想占用更多的內(nèi)存,系統(tǒng)將會拋出異常 OutOfMemoryError。

  為了避免內(nèi)存溢出,我們可以查詢當前設備為app指定的內(nèi)存上限值。我們可以通過調用方法getMemoryInfo()來查詢當前系統(tǒng)的這個屬性值。這個方法將會返回一個包含可用內(nèi)存、總內(nèi)存、內(nèi)存臨界值(如果系統(tǒng)可用內(nèi)存低于這個臨界值將會殺掉部分進程)等當前設備狀態(tài)的ActivityManager.MemoryInfo對象。ActivityManager.MemoryInfo也提供了一個boolean類型的字段lowMemory,記錄當前設備是否運行在低內(nèi)存情況下。

下面的代碼片段展示了一個使用geMemoryInfo()方法的例子:

public void doSomethingMemoryIntensive(){


   // Before doing something that requires a lot of memory,
   // check to see whether the device is in a low memory state.
   ActivityManager.MemoryInfo memoryInfo= getAvailableMemory();

   if(!memoryInfo.lowMemory){
       // Do memory intensive work ...
   }
}

// Get a MemoryInfo object for the device's current memory status.
privateActivityManager.MemoryInfo getAvailableMemory(){
   ActivityManager activityManager=(ActivityManager)this.getSystemService(ACTIVITY_SERVICE);
   ActivityManager.MemoryInfo memoryInfo=newActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
   return memoryInfo;
}

(四)使用高效內(nèi)存的代碼結構

    一些android特性、java類、代碼結構趨向于用更多的內(nèi)存。我們可以通過選擇更高效的替代方案來使我們app用最少的內(nèi)存。

 

(1)少用Services:

 在不必要的時候運行一個服務,是app最糟糕的內(nèi)存管理錯誤之一。如果app需要運行一個服務在后臺執(zhí)行工作,除非有必要的任務,否則不要一直運行。當Service完成它的任務后一定要記得停這個Service。否則,可能一不留神就產(chǎn)生一個內(nèi)存泄露。

 開啟一個Service后,系統(tǒng)喜歡為這個service開啟一個進程使其持續(xù)運行。這種情況使Service非常耗費資源,因為其他進程無法使用被service所在進程占用的RAM資源。系統(tǒng)緩存在LRU catche的進程數(shù)量減少后,使app運行效率降低。更有甚者,當內(nèi)存緊張,系統(tǒng)無法維持足夠的進程支持所有service的時候,service可能被銷毀。

 通常情況下我們應該避免長期使用service,因為它們會一直占用內(nèi)存。我們推薦使用可供選擇的類比如JobScheduler 去調度后臺進程。

 關于如何使用JobScheduler調度后臺進程,參閱“BackgroundOptimizations”

(2)使用最優(yōu)化的數(shù)據(jù)容器:

  編程語言提供的部分類對于移動設備不是最優(yōu)化的。例如一般的HashMap實現(xiàn)能使很多內(nèi)存低效率工作,因為它需要為每一個映射生成一個單獨的條目對象。

Androidframework包含幾個最優(yōu)化的數(shù)據(jù)容器,包括SparseArray,SparseBooleanArray,LongSparseArray。例如 AparseArray之所以更高效,是由于它們避免系統(tǒng)的需要,對key(有時候也對value)進行自動裝箱。

如果有必要,我們可以總是選擇原始的數(shù)組作為最精煉的數(shù)據(jù)結構。

(3)謹慎使用抽象代碼:

  開發(fā)者通常僅僅把抽象當成一種好的編程實踐,因為抽象可以提升代碼的靈活性和可維護性。然而,抽象伴隨著巨大的資源耗費而來:通常它們會需要更多的時間和更多的內(nèi)存,需要執(zhí)行相當數(shù)量的代碼,花費更多的時間和內(nèi)存。所以如果抽象不能帶來巨大好處,我們應該盡可能去避免。

例如:枚舉需要消耗的內(nèi)存通常是靜態(tài)變量的兩倍多。我們應該嚴格避免在android上使用枚舉。

(4)使用nanoprotobufs序列化數(shù)據(jù)

    Protocol buffers 是一個谷歌設計的具有語言中立、平臺中立、可擴展、用來序列化結構數(shù)據(jù)的類似于XML的機制,但其比XML快,比XML小,比XML簡單。如果你決定使用protobufs處理數(shù)據(jù),那么我們應該在客戶端代碼中一直使用nano protobufs。普通的protobufs通常生成非常多的代碼,這樣會引起app產(chǎn)生很多問題,比如占用更多內(nèi)存、APK尺寸大幅度增加、運行緩慢。

更多運行,請參閱文章"protobuf readme"中"Nanoversion"的段落。

 

(5)避免內(nèi)存抖動

  就像上文提醒到的,GC事件通常不會影響app的性能。然而一些發(fā)生在短時間內(nèi)的GC事件能迅速耗盡你的幀像時間。系統(tǒng)花費在GC上的時間越多,那么它處理其他諸如渲染或者音頻流的事件。

  通常情況下下,內(nèi)存抖動能引起大量的的GC事件。在實踐中,內(nèi)存抖動描繪的是,在一個給定時間內(nèi),分配臨時對象的數(shù)目。

  例如,你可能通過for循環(huán)分配多個臨時對象?;蛘咴谝粋€View的onDraw方法中創(chuàng)建一個Paint或者Bitmap對象。如上兩個例子,app迅速地創(chuàng)建了大量的大體積對象。年青一代的這些行為將會迅速的消耗所有的可用內(nèi)存,強制一個垃圾回收事件產(chǎn)生。

  當然,在你修復內(nèi)存抖動之前,需要先發(fā)現(xiàn)導致內(nèi)存抖動發(fā)生的代碼所在地方。請用在文章"Analyze your RAM usage"中討論的工具。

  一旦你識別代碼出現(xiàn)問題的區(qū)域,試著降低處于性能臨界區(qū)域的配置數(shù)目??紤]移除內(nèi)循環(huán)的東西,或者將它們移到基于分配結構的 Factory(參閱文章:)。

 

(五)降低內(nèi)存占有量——累贅的資源和庫


  一些資源和庫在你的代碼能在你不知道的情況下貪婪的消耗內(nèi)存。apk的大小、包括第三方庫以及嵌入的資源、能夠影響app消耗的內(nèi)存量。我們能通過移除代碼中多余的、不必要的、臃腫的組件、資源或者庫的方式來改善app的內(nèi)存消耗。


(1)降低apk大小

  我們能通過降低apk大小的方式顯著的降低app的內(nèi)存使用情況。bitmap尺寸、資源、動畫的幀、第三方庫都能導致apk變大。android sdk 和 android studio 提供了多種工具來幫助我們降低資源和外部依賴的尺寸。

  關于降低apk大小的更多信息,參閱"ReduceAPK Size"。

(2)如果需要依賴注入,建議使用Dagger

  依賴注入框架能夠簡化你寫的代碼以及為測試和測試改變提供一個適配的環(huán)境。如果你打算在app中用一個依賴注入框架,考慮使用Dagger2(參閱https://google.github.io/dagger/).

Dagger不會用反射掃描app代碼。它是靜態(tài)的、編譯時實現(xiàn)的框架,這意味著在運行時沒有不必要的運行時花費或者內(nèi)存使用。

  其他的那些依賴注入框架通過掃描代碼和注解,運用反射去初始化進程。這個產(chǎn)生的進程會需要大量的CPU周期以及內(nèi)存,能在app啟動時引起一個顯而易見的延遲。

 

(3)謹慎使用依賴庫:

  外部庫的代碼通常不是為手機環(huán)境所寫,使其在手機客戶端上工作時可能是很低效的。當我們決定用一個依賴庫,可能需要針對移動設備進行優(yōu)化。在決定真正使用這個依賴庫之前,應預先做好針對移動設備優(yōu)化的計劃,依據(jù)代碼尺寸和內(nèi)存大小進行分析。

  即使一些移動優(yōu)化的庫也能由于不同的實現(xiàn)引起問題。例如,當一個庫使用了micro protobufs而一個庫使用了nano protobufs,這樣app中有兩個不同的 protobuf實現(xiàn)。當問題發(fā)生時,原因可能是logging,analytics,p_w_picpath loading frameworks,caching或者一些其他我們無法預料的事情的不同實現(xiàn)導致的。

  盡管ProGuard(參閱:https://developer.android.com/studio/build/shrink-code.html)能根據(jù)正確的標記移除APIs和資源,但是它不能移除一個庫大型的依賴。你在庫中想要的特性可能需要低版本的依賴....

  翻譯整理,更多內(nèi)容,敬請期待....

         

文章標題:五個維度:Android內(nèi)存管理、優(yōu)化
標題來源:http://muchs.cn/article22/jpecjc.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站建設建站公司、網(wǎng)站設計公司服務器托管、關鍵詞優(yōu)化App開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站網(wǎng)頁設計