鍍金池/ 教程/ Android/ 管理應(yīng)用的內(nèi)存
檢測常用的手勢
優(yōu)化layout的層級
用戶輸入
管理應(yīng)用的內(nèi)存
聯(lián)系人信息
開發(fā)輔助程序
Android多媒體
添加語音功能
顯示位置地址
提供向下與橫向?qū)Ш?/span>
支持游戲控制器
訪問可穿戴數(shù)據(jù)層
處理多點觸控手勢
全屏沉浸式應(yīng)用
為多線程創(chuàng)建管理器
數(shù)據(jù)保存
Intent的發(fā)送
更新Notification
優(yōu)化下載以高效地訪問網(wǎng)絡(luò)
打印
打包可穿戴應(yīng)用
接收從其他App傳送來的數(shù)據(jù)
發(fā)送與接收消息
建立靈活動態(tài)的UI
處理鍵盤輸入
Building a Work Policy Controller
建立測試環(huán)境
創(chuàng)建表盤
分享文件
顯示Notification進度
實現(xiàn)自適應(yīng)UI流(Flows)
使用設(shè)備管理策略增強安全性
使用能感知版本的組件
執(zhí)行網(wǎng)絡(luò)操作
建立文件分享
添加移動
更新你的Security Provider來對抗SSL漏洞利用
支持鍵盤導(dǎo)航
創(chuàng)建和監(jiān)視地理圍欄
發(fā)送并同步數(shù)據(jù)
使用BigView樣式
無線連接設(shè)備
提供向上導(dǎo)航與歷史導(dǎo)航
最小化定期更新造成的影響
實現(xiàn)向下的導(dǎo)航
支持不同的屏幕大小
Android 可穿戴應(yīng)用
添加動畫
顯示聯(lián)系人頭像
使用OpenGL ES顯示圖像
處理輸入法可見性
分享文件
保持設(shè)備喚醒
淡化系統(tǒng)Bar
使用NFC分享文件
保存到Preference
Android聯(lián)系人信息與位置信息
創(chuàng)建標準的網(wǎng)絡(luò)請求
使用Drawables
管理Bitmap的內(nèi)存使用
管理Activity的生命周期
按需加載視圖
傳輸資源
為可穿戴設(shè)備創(chuàng)建自定義UI
在一個線程中執(zhí)行一段特定的代碼
性能優(yōu)化
隱藏導(dǎo)航欄
創(chuàng)建目錄瀏覽器
為多種大小的屏幕進行規(guī)劃
View間漸變
使用觸摸手勢
高效加載大圖
使用CursorLoader在后臺加載數(shù)據(jù)
創(chuàng)建抽屜式導(dǎo)航(navigation drawer)
管理音頻焦點
創(chuàng)建后臺服務(wù)
創(chuàng)建功能測試
創(chuàng)建使用Material Design的應(yīng)用
停止與重啟Activity
添加一個簡便的分享功能
啟動Activity時保留導(dǎo)航
TV應(yīng)用清單
創(chuàng)建向后兼容的UI
?# 優(yōu)化自定義View
創(chuàng)建單元測試
在UI上顯示Bitmap
建立OpenGL ES的環(huán)境
構(gòu)建表盤服務(wù)
JNI Tips
建立搜索界面
實現(xiàn)自定義View的繪制
使用HTTPS與SSL
按需操控BroadcastReceiver
分享簡單的數(shù)據(jù)
繪制形狀
Android位置信息
創(chuàng)建并運行可穿戴應(yīng)用
執(zhí)行 Sync Adpater
獲取最后可知位置
創(chuàng)建 Android 項目
實現(xiàn)高效的導(dǎo)航
退出全屏的Activity
創(chuàng)建Card
兼容音頻輸出設(shè)備
同步數(shù)據(jù)單元
傳輸數(shù)據(jù)時避免消耗大量電量
保存到文件
緩存Bitmap
提供配置 Activity
調(diào)度重復(fù)的鬧鐘
實現(xiàn)輔助功能
重復(fù)的下載是冗余的
隱藏狀態(tài)欄
實現(xiàn)自定義的網(wǎng)絡(luò)請求
規(guī)劃界面和他們之間的關(guān)系
使用Sync Adapter傳輸數(shù)據(jù)
TV應(yīng)用內(nèi)搜索
響應(yīng)觸摸事件
使用Google Cloud Messaging(已廢棄)
控制相機
Android網(wǎng)絡(luò)連接與云服務(wù)
請求分享一個文件
處理TV硬件
響應(yīng)UI可見性的變化
使用網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)
指定輸入法類型
優(yōu)化電池壽命
創(chuàng)建TV應(yīng)用
獲取聯(lián)系人列表
拖拽與縮放
啟動與停止線程池中的線程
創(chuàng)建 Sync Adpater
使用 WiFi P2P 服務(wù)發(fā)現(xiàn)
開始使用Material Design
代理至新的APIs
使用include標簽重用layouts
使得View可交互
高效顯示Bitmap
創(chuàng)建企業(yè)級應(yīng)用
Fragments之間的交互
創(chuàng)建與執(zhí)行測試用例
綜合:設(shè)計我們的樣例 App
繪制表盤
建立簡單的用戶界面
自定義動畫
開發(fā)輔助服務(wù)
避免出現(xiàn)程序無響應(yīng)ANR(Keeping Your App Responsive)
使用ViewPager實現(xiàn)屏幕滑動
設(shè)計高效的導(dǎo)航
Android分享操作(Building Apps with Content Sharing)
提供向后的導(dǎo)航
保持向下兼容
創(chuàng)建TV播放應(yīng)用
縮放View
使用 WiFi 建立 P2P 連接
Android后臺任務(wù)
連接到網(wǎng)絡(luò)
為 Notification 添加頁面
使TV應(yīng)用是可被搜索的
添加Action Bar
使用Material的主題
啟動另一個Activity
顯示正在播放卡片
適配不同的系統(tǒng)版本
輕松錄制視頻
創(chuàng)建可穿戴的應(yīng)用
創(chuàng)建自定義的布局
重新創(chuàng)建Activity
使用CursorLoader執(zhí)行查詢?nèi)蝿?wù)
使用舊的APIs實現(xiàn)新API的效果
使用備份API
安全要點
Android入門基礎(chǔ):從這里開始
保存并搜索數(shù)據(jù)
根據(jù)網(wǎng)絡(luò)連接類型來調(diào)整下載模式
使用Tabs創(chuàng)建Swipe視圖
SMP(Symmetric Multi-Processor) Primer for Android
解析 XML 數(shù)據(jù)
使用 Volley 傳輸網(wǎng)絡(luò)數(shù)據(jù)
建立ActionBar
Android交互設(shè)計
使用Intent修改聯(lián)系人信息
增加搜索功能
輕松拍攝照片
定義形狀
測試你的Activity
在 Notifcation 中接收語音輸入
與其他應(yīng)用的交互
管理系統(tǒng)UI
追蹤手勢移動
Android界面設(shè)計
執(zhí)行 Android 程序
顯示確認界面
創(chuàng)建Lists與Cards
打印HTML文檔
創(chuàng)建TV應(yīng)用
為多屏幕設(shè)計
定義Shadows與Clipping視圖
使用Fragment建立動態(tài)UI
接收Activity返回的結(jié)果
布局變更動畫
定位常見的問題
自定義ActionBar的風(fēng)格
定義Layouts
發(fā)送簡單的網(wǎng)絡(luò)請求
啟動與銷毀Activity
與UI線程通信
非UI線程處理Bitmap
創(chuàng)建TV布局
提升Layout的性能
報告任務(wù)執(zhí)行狀態(tài)
判斷并監(jiān)測網(wǎng)絡(luò)連接狀態(tài)
兼容不同的設(shè)備
處理按鍵動作
優(yōu)化性能和電池使用時間
給其他App發(fā)送簡單的數(shù)據(jù)
Implementing App Restrictions
向后臺服務(wù)發(fā)送任務(wù)請求
展示Card翻轉(zhuǎn)動畫
管理ViewGroup中的觸摸事件
兼容不同的屏幕密度
通過藍牙進行調(diào)試
為可穿戴設(shè)備創(chuàng)建Notification
控制音量與音頻播放
獲取聯(lián)系人詳情
在表盤上顯示信息
提供向上的導(dǎo)航
滾動手勢動畫
幫助用戶在TV上找到內(nèi)容
創(chuàng)建TV導(dǎo)航
為索引指定App內(nèi)容
ActionBar的覆蓋疊加
Android Wear 上的位置檢測
保護安全與隱私的最佳策略
Ensuring Compatibility with Managed Profiles
解決云同步的保存沖突
獲取位置更新
創(chuàng)建List
測試程序
管理網(wǎng)絡(luò)的使用情況
為App內(nèi)容開啟深度鏈接
推薦TV內(nèi)容
建立一個Notification
管理音頻播放
設(shè)計表盤
拍照
處理控制器輸入動作
判斷并監(jiān)測設(shè)備的底座狀態(tài)與類型
處理查詢的結(jié)果
保存到數(shù)據(jù)庫
支持多個游戲控制器
創(chuàng)建 Stub Content Provider
使得ListView滑動順暢
處理數(shù)據(jù)層的事件
創(chuàng)建TV應(yīng)用的第一步
使得你的App內(nèi)容可被Google搜索
將 Notification 放成一疊
創(chuàng)建 Stub 授權(quán)器
暫停與恢復(fù)Activity
管理設(shè)備的喚醒狀態(tài)
Android圖像與動畫
打印照片
云同步
創(chuàng)建TV直播應(yīng)用
為Notification賦加可穿戴特性
提供一個Card視圖
建立請求隊列(RequestQueue)
適配不同的語言
創(chuàng)建詳情頁
測試UI組件
接收其他設(shè)備的文件
創(chuàng)建自定義View
建立第一個App
創(chuàng)建2D Picker
監(jiān)測電池的電量與充電狀態(tài)
打印自定義文檔
抽象出新的APIs
通知提示用戶
獲取文件信息
運用投影與相機視角
在IntentService中執(zhí)行后臺任務(wù)
多線程操作
創(chuàng)建一個Fragment
添加Action按鈕
在不同的 Android 系統(tǒng)版本支持控制器
維護兼容性
發(fā)送文件給其他設(shè)備
創(chuàng)建TV游戲應(yīng)用
創(chuàng)建自定義的View類
代碼性能優(yōu)化建議
Intent過濾
適配不同的屏幕

管理應(yīng)用的內(nèi)存

編寫:kesenhoo - 原文:http://developer.android.com/training/articles/memory.html

Random Access Memory(RAM)在任何軟件開發(fā)環(huán)境中都是一個很寶貴的資源。這一點在物理內(nèi)存通常很有限的移動操作系統(tǒng)上,顯得尤為突出。盡管Android的Dalvik虛擬機扮演了常規(guī)的垃圾回收的角色,但這并不意味著你可以忽視app的內(nèi)存分配與釋放的時機與地點。

為了GC能夠從app中及時回收內(nèi)存,我們需要注意避免內(nèi)存泄露(通常由于在全局成員變量中持有對象引用而導(dǎo)致)并且在適當?shù)臅r機(下面會講到的lifecycle callbacks)來釋放引用對象。對于大多數(shù)app來說,Dalvik的GC會自動把離開活動線程的對象進行回收。

這篇文章會解釋Android是如何管理app的進程與內(nèi)存分配,以及在開發(fā)Android應(yīng)用的時候如何主動的減少內(nèi)存的使用。關(guān)于Java的資源管理機制,請參考其它書籍或者線上材料。如果你正在尋找如何分析你的內(nèi)存使用情況的文章,請參考這里Investigating Your RAM Usage。

第1部分: Android是如何管理內(nèi)存的

Android并沒有為內(nèi)存提供交換區(qū)(Swap space),但是它有使用pagingmemory-mapping(mmapping)的機制來管理內(nèi)存。這意味著任何你修改的內(nèi)存(無論是通過分配新的對象還是去訪問mmaped pages中的內(nèi)容)都會貯存在RAM中,而且不能被paged out。因此唯一完整釋放內(nèi)存的方法是釋放那些你可能hold住的對象的引用,當這個對象沒有被任何其他對象所引用的時候,它就能夠被GC回收了。只有一種例外是:如果系統(tǒng)想要在其他地方重用這個對象。

1) 共享內(nèi)存

Android通過下面幾個方式在不同的進程中來實現(xiàn)共享RAM:

  • 每一個app的進程都是從一個被叫做Zygote的進程中fork出來的。Zygote進程在系統(tǒng)啟動并且載入通用的framework的代碼與資源之后開始啟動。為了啟動一個新的程序進程,系統(tǒng)會fork Zygote進程生成一個新的進程,然后在新的進程中加載并運行app的代碼。這使得大多數(shù)的RAM pages被用來分配給framework的代碼,同時使得RAM資源能夠在應(yīng)用的所有進程中進行共享。

  • 大多數(shù)static的數(shù)據(jù)被mmapped到一個進程中。這不僅僅使得同樣的數(shù)據(jù)能夠在進程間進行共享,而且使得它能夠在需要的時候被paged out。例如下面幾種static的數(shù)據(jù):
    • Dalvik 代碼 (放在一個預(yù)鏈接好的 .odex 文件中以便直接mapping)
    • App resources (通過把資源表結(jié)構(gòu)設(shè)計成便于mmapping的數(shù)據(jù)結(jié)構(gòu),另外還可以通過把APK中的文件做aligning的操作來優(yōu)化)
    • 傳統(tǒng)項目元素,比如 .so 文件中的本地代碼.
  • 在很多情況下,Android通過顯式的分配共享內(nèi)存區(qū)域(例如ashmem或者gralloc)來實現(xiàn)一些動態(tài)RAM區(qū)域能夠在不同進程間進行共享。例如,window surfaces在app與screen compositor之間使用共享的內(nèi)存,cursor buffers在content provider與client之間使用共享的內(nèi)存。

關(guān)于如何查看app所使用的共享內(nèi)存,請查看Investigating Your RAM Usage

2) 分配與回收內(nèi)存

這里有下面幾點關(guān)于Android如何分配與回收內(nèi)存的事實:

  • 每一個進程的Dalvik heap都有一個受限的虛擬內(nèi)存范圍。這就是邏輯上講的heap size,它可以隨著需要進行增長,但是會有一個系統(tǒng)為它所定義的上限。
  • 邏輯上講的heap size和實際物理上使用的內(nèi)存數(shù)量是不等的,Android會計算一個叫做Proportional Set Size(PSS)的值,它記錄了那些和其他進程進行共享的內(nèi)存大小。(假設(shè)共享內(nèi)存大小是10M,一共有20個Process在共享使用,根據(jù)權(quán)重,可能認為其中有0.3M才能真正算是你的進程所使用的)
  • Dalvik heap與邏輯上的heap size不吻合,這意味著Android并不會去做heap中的碎片整理用來關(guān)閉空閑區(qū)域。Android僅僅會在heap的尾端出現(xiàn)不使用的空間時才會做收縮邏輯heap size大小的動作。但是這并不是意味著被heap所使用的物理內(nèi)存大小不能被收縮。在垃圾回收之后,Dalvik會遍歷heap并找出不使用的pages,然后使用madvise(系統(tǒng)調(diào)用)把那些pages返回給kernal。因此,成對的allocations與deallocations大塊的數(shù)據(jù)可以使得物理內(nèi)存能夠被正常的回收。然而,回收碎片化的內(nèi)存則會使得效率低下很多,因為那些碎片化的分配頁面也許會被其他地方所共享到。

3) 限制應(yīng)用的內(nèi)存

為了維持多任務(wù)的功能環(huán)境,Android為每一個app都設(shè)置了一個硬性的heap size限制。準確的heap size限制會因為不同設(shè)備的不同RAM大小而各有差異。如果你的app已經(jīng)到了heap的限制大小并且再嘗試分配內(nèi)存的話,會引起OutOfMemoryError的錯誤。

在一些情況下,你也許想要查詢當前設(shè)備的heap size限制大小是多少,然后決定cache的大小??梢酝ㄟ^getMemoryClass()來查詢。這個方法會返回一個整數(shù),表明你的應(yīng)用的heap size限制是多少Mb(megabates)。

4) 切換應(yīng)用

Android并不會在用戶切換不同應(yīng)用時候做交換內(nèi)存的操作。Android會把那些不包含foreground組件的進程放到LRU cache中。例如,當用戶剛開始啟動了一個應(yīng)用,系統(tǒng)會為它創(chuàng)建了一個進程,但是當用戶離開這個應(yīng)用,此進程并不會立即被銷毀。系統(tǒng)會把這個進程放到cache中,如果用戶后來再回到這個應(yīng)用,此進程就能夠被完整恢復(fù),從而實現(xiàn)應(yīng)用的快速切換。

如果你的應(yīng)用中有一個被緩存的進程,這個進程會占用暫時不需要使用到的內(nèi)存,這個暫時不需要使用的進程,它被保留在內(nèi)存中,這會對系統(tǒng)的整體性能有影響。因此當系統(tǒng)開始進入低內(nèi)存狀態(tài)時,它會由系統(tǒng)根據(jù)LRU的規(guī)則與其他因素選擇綜合考慮之后決定殺掉某些進程,為了保持你的進程能夠盡可能長久的被緩存,請參考下面的章節(jié)學(xué)習(xí)何時釋放你的引用。

對于那些不在foreground的進程,Android是如何決定kill掉哪一類進程的問題,請參考Processes and Threads.

第2部分: 你的應(yīng)用該如何管理內(nèi)存

你應(yīng)該在開發(fā)過程的每一個階段都考慮到RAM的有限性,甚至包括在開始編寫代碼之前的設(shè)計階段就應(yīng)該考慮到RAM的限制性。我們可以使用多種設(shè)計與實現(xiàn)方式,他們有著不同的效率,即使這些方式只是相同技術(shù)的不斷組合與演變。

為了使得你的應(yīng)用性能效率更高,你應(yīng)該在設(shè)計與實現(xiàn)代碼時,遵循下面的技術(shù)要點。

1) 珍惜Services資源

如果你的應(yīng)用需要在后臺使用service,除非它被觸發(fā)并執(zhí)行一個任務(wù),否則其他時候service都應(yīng)該是停止狀態(tài)。另外需要注意當這個service完成任務(wù)之后因為停止service失敗而引起的內(nèi)存泄漏。

當你啟動一個service,系統(tǒng)會傾向為了保留這個service而一直保留service所在的進程。這使得進程的運行代價很高,因為系統(tǒng)沒有辦法把service所占用的RAM空間騰出來讓給其他組件,另外service還不能被paged out。這減少了系統(tǒng)能夠存放到LRU緩存當中的進程數(shù)量,它會影響app之間的切換效率。它甚至?xí)?dǎo)致系統(tǒng)內(nèi)存使用不穩(wěn)定,從而無法繼續(xù)保持住所有目前正在運行的service。

限制你的service的最好辦法是使用IntentService, 它會在處理完交代給它的intent任務(wù)之后盡快結(jié)束自己。更多信息,請閱讀Running in a Background Service.

當一個Service已經(jīng)不再需要的時候還繼續(xù)保留它,這對Android應(yīng)用的內(nèi)存管理來說是最糟糕的錯誤之一。因此千萬不要貪婪的使得一個Service持續(xù)保留。不僅僅是因為它會使得你的應(yīng)用因為RAM空間的不足而性能糟糕,還會使得用戶發(fā)現(xiàn)那些有著常駐后臺行為的應(yīng)用并且可能卸載它。

2) 當UI隱藏時釋放內(nèi)存

當用戶切換到其它應(yīng)用并且你的應(yīng)用 UI不再可見時,你應(yīng)該釋放你的應(yīng)用UI上所占用的所有內(nèi)存資源。在這個時候釋放UI資源可以顯著的增加系統(tǒng)緩存進程的能力,它會對用戶體驗有著很直接的影響。

為了能夠接收到用戶離開你的UI時的通知,你需要實現(xiàn)Activtiy類里面的onTrimMemory()回調(diào)方法。你應(yīng)該使用這個方法來監(jiān)聽到TRIM_MEMORY_UI_HIDDEN級別的回調(diào),此時意味著你的UI已經(jīng)隱藏,你應(yīng)該釋放那些僅僅被你的UI使用的資源。

請注意:你的應(yīng)用僅僅會在所有UI組件的被隱藏的時候接收到onTrimMemory()的回調(diào)并帶有參數(shù)TRIM_MEMORY_UI_HIDDEN。這與onStop()的回調(diào)是不同的,onStop會在activity的實例隱藏時會執(zhí)行,例如當用戶從你的app的某個activity跳轉(zhuǎn)到另外一個activity時前面activity的onStop()會被執(zhí)行。因此你應(yīng)該實現(xiàn)onStop回調(diào),并且在此回調(diào)里面釋放activity的資源,例如釋放網(wǎng)絡(luò)連接,注銷監(jiān)聽廣播接收者。除非接收到onTrimMemory(TRIM_MEMORY_UI_HIDDEN)的回調(diào),否者你不應(yīng)該釋放你的UI資源。這確保了用戶從其他activity切回來時,你的UI資源仍然可用,并且可以迅速恢復(fù)activity。

3) 當內(nèi)存緊張時釋放部分內(nèi)存

在你的app生命周期的任何階段,onTrimMemory的回調(diào)方法同樣可以告訴你整個設(shè)備的內(nèi)存資源已經(jīng)開始緊張。你應(yīng)該根據(jù)onTrimMemory回調(diào)中的內(nèi)存級別來進一步?jīng)Q定釋放哪些資源。

  • TRIM_MEMORY_RUNNING_MODERATE:你的app正在運行并且不會被列為可殺死的。但是設(shè)備此時正運行于低內(nèi)存狀態(tài)下,系統(tǒng)開始觸發(fā)殺死LRU Cache中的Process的機制。
  • TRIM_MEMORY_RUNNING_LOW:你的app正在運行且沒有被列為可殺死的。但是設(shè)備正運行于更低內(nèi)存的狀態(tài)下,你應(yīng)該釋放不用的資源用來提升系統(tǒng)性能(但是這也會直接影響到你的app的性能)。
  • TRIM_MEMORY_RUNNING_CRITICAL:你的app仍在運行,但是系統(tǒng)已經(jīng)把LRU Cache中的大多數(shù)進程都已經(jīng)殺死,因此你應(yīng)該立即釋放所有非必須的資源。如果系統(tǒng)不能回收到足夠的RAM數(shù)量,系統(tǒng)將會清除所有的LRU緩存中的進程,并且開始殺死那些之前被認為不應(yīng)該殺死的進程,例如那個包含了一個運行態(tài)Service的進程。

同樣,當你的app進程正在被cached時,你可能會接受到從onTrimMemory()中返回的下面的值之一:

  • TRIM_MEMORY_BACKGROUND: 系統(tǒng)正運行于低內(nèi)存狀態(tài)并且你的進程正處于LRU緩存名單中最不容易殺掉的位置。盡管你的app進程并不是處于被殺掉的高危險狀態(tài),系統(tǒng)可能已經(jīng)開始殺掉LRU緩存中的其他進程了。你應(yīng)該釋放那些容易恢復(fù)的資源,以便于你的進程可以保留下來,這樣當用戶回退到你的app的時候才能夠迅速恢復(fù)。
  • TRIM_MEMORY_MODERATE: 系統(tǒng)正運行于低內(nèi)存狀態(tài)并且你的進程已經(jīng)已經(jīng)接近LRU名單的中部位置。如果系統(tǒng)開始變得更加內(nèi)存緊張,你的進程是有可能被殺死的。
  • TRIM_MEMORY_COMPLETE: 系統(tǒng)正運行與低內(nèi)存的狀態(tài)并且你的進程正處于LRU名單中最容易被殺掉的位置。你應(yīng)該釋放任何不影響你的app恢復(fù)狀態(tài)的資源。

因為onTrimMemory()的回調(diào)是在API 14才被加進來的,對于老的版本,你可以使用[onLowMemory](http://developer.android.com/reference/android/content/ComponentCallbacks.html#onLowMemory())回調(diào)來進行兼容。onLowMemory相當與`TRIM_MEMORY_COMPLETE`。

Note: 當系統(tǒng)開始清除LRU緩存中的進程時,盡管它首先按照LRU的順序來操作,但是它同樣會考慮進程的內(nèi)存使用量。因此消耗越少的進程則越容易被留下來。

4) 檢查你應(yīng)該使用多少的內(nèi)存

正如前面提到的,每一個Android設(shè)備都會有不同的RAM總大小與可用空間,因此不同設(shè)備為app提供了不同大小的heap限制。你可以通過調(diào)用[getMemoryClass()](http://developer.android.com/reference/android/app/ActivityManager.html#getMemoryClass())來獲取你的app的可用heap大小。如果你的app嘗試申請更多的內(nèi)存,會出現(xiàn)`OutOfMemory`的錯誤。

在一些特殊的情景下,你可以通過在manifest的application標簽下添加largeHeap=true的屬性來聲明一個更大的heap空間。如果你這樣做,你可以通過[getLargeMemoryClass()](http://developer.android.com/reference/android/app/ActivityManager.html#getLargeMemoryClass())來獲取到一個更大的heap size。

然而,能夠獲取更大heap的設(shè)計本意是為了一小部分會消耗大量RAM的應(yīng)用(例如一個大圖片的編輯應(yīng)用)。不要輕易的因為你需要使用大量的內(nèi)存而去請求一個大的heap size。只有當你清楚的知道哪里會使用大量的內(nèi)存并且為什么這些內(nèi)存必須被保留時才去使用large heap. 因此請盡量少使用large heap。使用額外的內(nèi)存會影響系統(tǒng)整體的用戶體驗,并且會使得GC的每次運行時間更長。在任務(wù)切換時,系統(tǒng)的性能會變得大打折扣。

另外, large heap并不一定能夠獲取到更大的heap。在某些有嚴格限制的機器上,large heap的大小和通常的heap size是一樣的。因此即使你申請了large heap,你還是應(yīng)該通過執(zhí)行g(shù)etMemoryClass()來檢查實際獲取到的heap大小。

5) 避免bitmaps的浪費

當你加載一個bitmap時,僅僅需要保留適配當前屏幕設(shè)備分辨率的數(shù)據(jù)即可,如果原圖高于你的設(shè)備分辨率,需要做縮小的動作。請記住,增加bitmap的尺寸會對內(nèi)存呈現(xiàn)出2次方的增加,因為X與Y都在增加。

Note:在Android 2.3.x (API level 10)及其以下, bitmap對象的pixel data是存放在native內(nèi)存中的,它不便于調(diào)試。然而,從Android 3.0(API level 11)開始,bitmap pixel data是分配在你的app的Dalvik heap中, 這提升了GC的工作效率并且更加容易Debug。因此如果你的app使用bitmap并在舊的機器上引發(fā)了一些內(nèi)存問題,切換到3.0以上的機器上進行Debug。

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

利用Android Framework里面優(yōu)化過的容器類,例如SparseArray, SparseBooleanArray, 與 LongSparseArray。 通常的HashMap的實現(xiàn)方式更加消耗內(nèi)存,因為它需要一個額外的實例對象來記錄Mapping操作。另外,SparseArray更加高效在于他們避免了對key與value的autobox自動裝箱,并且避免了裝箱后的解箱。

7) 請注意內(nèi)存開銷

對你所使用的語言與庫的成本與開銷有所了解,從開始到結(jié)束,在設(shè)計你的app時謹記這些信息。通常,表面上看起來無關(guān)痛癢(innocuous)的事情也許實際上會導(dǎo)致大量的開銷。例如:

  • Enums的內(nèi)存消耗通常是static constants的2倍。你應(yīng)該盡量避免在Android上使用enums。
  • 在Java中的每一個類(包括匿名內(nèi)部類)都會使用大概500 bytes。
  • 每一個類的實例花銷是12-16 bytes。
  • 往HashMap添加一個entry需要額一個額外占用的32 bytes的entry對象。

8) 請注意代碼“抽象”

通常,開發(fā)者使用抽象作為"好的編程實踐",因為抽象能夠提升代碼的靈活性與可維護性。然而,抽象會導(dǎo)致一個顯著的開銷:通常他們需要同等量的代碼用于可執(zhí)行。那些代碼會被map到內(nèi)存中。因此如果你的抽象沒有顯著的提升效率,應(yīng)該盡量避免他們。

9) 為序列化的數(shù)據(jù)使用nano protobufs

Protocol buffers是由Google為序列化結(jié)構(gòu)數(shù)據(jù)而設(shè)計的,一種語言無關(guān),平臺無關(guān),具有良好擴展性的協(xié)議。類似XML,卻比XML更加輕量,快速,簡單。如果你需要為你的數(shù)據(jù)實現(xiàn)協(xié)議化,你應(yīng)該在客戶端的代碼中總是使用nano protobufs。通常的協(xié)議化操作會生成大量繁瑣的代碼,這容易給你的app帶來許多問題:增加RAM的使用量,顯著增加APK的大小,更慢的執(zhí)行速度,更容易達到DEX的字符限制。

關(guān)于更多細節(jié),請參考protobuf readme的"Nano version"章節(jié)。

10) 避免使用依賴注入框架

使用類似Guice或者RoboGuice等framework injection包是很有效的,因為他們能夠簡化你的代碼。

Notes:RoboGuice 2 通過依賴注入改變代碼風(fēng)格,讓Android開發(fā)時的體驗更好。你在調(diào)用 getIntent().getExtras() 時經(jīng)常忘記檢查 null 嗎?RoboGuice 2 可以幫你做。你認為將 findViewById() 的返回值強制轉(zhuǎn)換成 TextView 是本不必要的工作嗎? RoboGuice 2 可以幫你。RoboGuice 把這些需要猜測性的工作移到Android開發(fā)以外去了。RoboGuice 2 會負責(zé)注入你的 View, Resource, System Service或者其他對象等等類似的細節(jié)。

然而,那些框架會通過掃描你的代碼執(zhí)行許多初始化的操作,這會導(dǎo)致你的代碼需要大量的RAM來mapping代碼,而且mapped pages會長時間的被保留在RAM中。

11) 謹慎使用第三方libraries

很多開源的library代碼都不是為移動網(wǎng)絡(luò)環(huán)境而編寫的,如果運用在移動設(shè)備上,,這樣的效率并不高。當你決定使用一個第三方library的時候,你應(yīng)該針對移動網(wǎng)絡(luò)做繁瑣的遷移與維護的工作。

即使是針對Android而設(shè)計的library,也可能是很危險的,因為每一個library所做的事情都是不一樣的。例如,其中一個lib使用的是nano protobufs, 而另外一個使用的是micro protobufs。那么這樣,在你的app里面就有2種protobuf的實現(xiàn)方式。這樣的沖突同樣可能發(fā)生在輸出日志,加載圖片,緩存等等模塊里面。

同樣不要陷入為了1個或者2個功能而導(dǎo)入整個library的陷阱。如果沒有一個合適的庫與你的需求相吻合,你應(yīng)該考慮自己去實現(xiàn),而不是導(dǎo)入一個大而全的解決方案。

12) 優(yōu)化整體性能

官方有列出許多優(yōu)化整個app性能的文章:Best Practices for Performance。這篇文章就是其中之一。有些文章是講解如何優(yōu)化app的CPU使用效率,有些是如何優(yōu)化app的內(nèi)存使用效率。

你還應(yīng)該閱讀optimizing your UI來為layout進行優(yōu)化。同樣還應(yīng)該關(guān)注lint工具所提出的建議,進行優(yōu)化。

13) 使用ProGuard來剔除不需要的代碼

ProGuard能夠通過移除不需要的代碼,重命名類,域與方法等方對代碼進行壓縮,優(yōu)化與混淆。使用ProGuard可以使得你的代碼更加緊湊,這樣能夠使用更少mapped代碼所需要的RAM。

14) 對最終的APK使用zipalign

在編寫完所有代碼,并通過編譯系統(tǒng)生成APK之后,你需要使用zipalign對APK進行重新校準。如果你不做這個步驟,會導(dǎo)致你的APK需要更多的RAM,因為一些類似圖片資源的東西不能被mapped。

Notes: Google Play不接受沒有經(jīng)過zipalign的APK。

15) 分析你的RAM使用情況

一旦你獲取到一個相對穩(wěn)定的版本后,需要分析你的app整個生命周期內(nèi)使用的內(nèi)存情況,并進行優(yōu)化,更多細節(jié)請參考Investigating Your RAM Usage.

16) 使用多進程

如果合適的話,有一個更高級的技術(shù)可以幫助你的app管理內(nèi)存使用:通過把你的app組件切分成多個組件,運行在不同的進程中。這個技術(shù)必須謹慎使用,大多數(shù)app都不應(yīng)該運行在多個進程中。因為如果使用不當,它會顯著增加內(nèi)存的使用,而不是減少。當你的app需要在后臺運行與前臺一樣的大量的任務(wù)的時候,可以考慮使用這個技術(shù)。

一個典型的例子是創(chuàng)建一個可以長時間后臺播放的Music Player。如果整個app運行在一個進程中,當后臺播放的時候,前臺的那些UI資源也沒有辦法得到釋放。類似這樣的app可以切分成2個進程:一個用來操作UI,另外一個用來后臺的Service.

你可以通過在manifest文件中聲明'android:process'屬性來實現(xiàn)某個組件運行在另外一個進程的操作。

<service android:name=".PlaybackService"
         android:process=":background" />

更多關(guān)于使用這個技術(shù)的細節(jié),請參考原文,鏈接如下。 http://developer.android.com/training/articles/memory.html