Android性能優(yōu)化學習筆記

Android性能優(yōu)化

用戶體驗在Android開發(fā)中格外重要甸昏,一款操作卡頓兴枯、耗電量大微王、響應速度慢的軟件判呕,必然會損失大量用戶。 本文的主題就是Android性能優(yōu)化磁浇,幫你實現(xiàn)一款流暢斋陪、穩(wěn)定、耗能少的軟件置吓。

內(nèi)存優(yōu)化并不就是說程序占用的內(nèi)存越少就越好鳍贾,如果因為想要保持更低的內(nèi)存占用,而頻繁觸發(fā)執(zhí)行GC操作交洗,在某種程度上反而會導致應用性能整體有所下降骑科,這里需要綜合考慮做一定的權(quán)衡
Android的內(nèi)存優(yōu)化涉及的知識面還有很多:內(nèi)存管理的細節(jié)构拳,垃圾回收的工作原理咆爽,如何查找內(nèi)存泄漏等等梁棠。

接下來將從OOM避免和布局優(yōu)化,通過減少對象的內(nèi)存占用斗埂、內(nèi)存對象的重復利用符糊、避免對象的內(nèi)存泄漏、內(nèi)存使用策略優(yōu)化等方面來介紹Android內(nèi)存優(yōu)化相關(guān)知識呛凶。

本文內(nèi)容是對胡凱大神Android性能優(yōu)化典范系列文章的整理男娄,感謝大神分享!原文地址:Android性能優(yōu)化典范

Android內(nèi)存優(yōu)化之OOM

內(nèi)存溢出OOM是指應用系統(tǒng)中存在無法回收的內(nèi)存或使用的內(nèi)存過多漾稀,最終使得程序運行要用到的內(nèi)存大于虛擬機能提供的最大內(nèi)存模闲。

引起內(nèi)存溢出的原因有很多種,常見的有以下幾種:

  1. 內(nèi)存中加載的數(shù)據(jù)量過于龐大崭捍,如一次從數(shù)據(jù)庫取出過多數(shù)據(jù)尸折;
  2. 集合類中有對對象的引用,使用完后未清空殷蛇,使得JVM不能回收实夹;
  3. 代碼中存在死循環(huán)或循環(huán)產(chǎn)生過多重復的對象實體;
  4. 使用的第三方軟件中的BUG粒梦;
  5. 啟動參數(shù)內(nèi)存值設定的過辛梁健;

如何避免OOM總結(jié)

前面介紹了一些基礎(chǔ)的內(nèi)存管理機制以及OOM的基礎(chǔ)知識匀们,那么在實踐操作當中塞赂,有哪些指導性的規(guī)則可以參考呢?歸納下來昼蛀,可以從四個方面著手宴猾,首先是減小對象的內(nèi)存占用,其次是內(nèi)存對象的重復利用叼旋,然后是避免對象的內(nèi)存泄露仇哆,最后是內(nèi)存使用策略優(yōu)化

減小對象的內(nèi)存占用

避免OOM的第一步就是要盡量減少新分配出來的對象占用內(nèi)存的大小夫植,盡量使用更加輕量的對象讹剔。

使用更加輕量的數(shù)據(jù)結(jié)構(gòu)
考慮使用ArrayMap/SparseArray而不是HashMap等傳統(tǒng)數(shù)據(jù)結(jié)構(gòu),HashMap相比起Android系統(tǒng)專門為移動操作系統(tǒng)編寫的ArrayMap與SpareArray容器详民,在大多數(shù)情況下延欠,效率相對低下,且更占內(nèi)存沈跨。通常的HashMap的實現(xiàn)方式更加消耗內(nèi)存由捎,因為它需要一個額外的實例對象來記錄Mapping操作。另外饿凛,SparseArray更加高效在于他們避免了對key與value的自動裝箱狞玛,避免了裝箱后的解箱并且內(nèi)部數(shù)據(jù)結(jié)構(gòu)不依賴額外實體對象软驰。

避免在Android里面使用Enum
Enum中的每一個值都是一個Object,它的每個聲明都會占用運行時的部分內(nèi)存以便能夠引用到這個Object心肪。因此Enum的值會比對應的Integer和String所占用的內(nèi)存多锭亏。

定義三個靜態(tài)常量:

public static final int RED =0;
public static final int GREEN =1;
public static final int BLACK =2;

如果使用Enum表示:

public static enum Color {
    RED, GREEN, BLACK
}

反編譯相關(guān)字節(jié)碼文件可以將枚舉操作過程視為如下過程

public final class Color extends java.lang.Enum<Color> {

    public static final Color RED;
    public static final Color GREEN;
    public static final Color BLACK;

    static {
        RED = new Color("RED", 0);
        GREEN = new Color("GREEN", 1);
        BLACK = new Color("BLACK", 2);

        VALUES = new Color[]{RED, GREEN, BLACK};
    }

    public static Color[] values() {
        Color tmp = new Color[VALUES.length];
        system.arraycopy(VALUES, 0, tmp, 0, VALUES.length);
        return tmp;
    }

    public static Color valueOf(String name) {
        return Enum.valueOf(name);
    }
}

在Android 2.2之前的版本中,存在著關(guān)于Enum引起的性能問題硬鞍,這個問題在JIT編譯器中解決了慧瘤。添加一個Enum將會增大最終的DEX文件(Integer常量的13倍大)。并且會引起運行時的過度開銷固该,你的應用也會占用更多的空間锅减。因此過度在Android開發(fā)中使用Enum將會增大DEX大小,并會增大運行時的內(nèi)存分配大小蹬音。如果你的應用是使用了許多的Enum上煤,那么使用Integer或者String常量會更好休玩。

一個枚舉可以為您的應用程序的classes.dex文件添加大約1.0到1.4 KB的大小 著淆。這些添加可以快速累積到復雜系統(tǒng)或共享庫。如果可能拴疤,請考慮使用@IntDef注釋永部,這種類型轉(zhuǎn)換保留了枚舉的所有類型安全優(yōu)勢

關(guān)于枚舉內(nèi)存占用詳細內(nèi)容請參考【內(nèi)存優(yōu)化】避免使用Enum

減小Bitmap對象的內(nèi)存占用
Bitmap是一個極容易消耗內(nèi)存的大胖子呐矾,減小創(chuàng)建出來的Bitmap的內(nèi)存占用是很重要的苔埋,一般來說圖片的內(nèi)存占用與圖片寬高、圖片編碼格式蜒犯、圖片存放目錄及手機屏幕像素密度等因素有關(guān)组橄。常見的解決方案就是對Bitmap進行壓縮,而Bitmap壓縮策略主要分為質(zhì)量壓縮(不改變圖片尺寸罚随,從圖片分辨率角度入手玉工,選用合適的編碼格式)和尺寸壓縮兩種。通常來說有下面2個措施:

  • inSampleSize:縮放比例淘菩,在把圖片載入內(nèi)存之前遵班,我們需要先計算出一個合適的縮放比例,避免不必要的大圖載入潮改。
  • Decode Format:解碼格式狭郑,選擇ARGB-8888、RBG-565汇在、ARGB-4444翰萨、ALPHA-8,存在很大差異糕殉,比如:ARGB-8888格式的圖片缨历,每像素占用 4 Byte以蕴,而 RGB-565則是 2 Byte。

使用更小的圖片
在設計給到資源圖片的時候辛孵,我們需要特別留意這張圖片是否存在可以壓縮的空間丛肮,是否可以使用一張更小的圖片。盡量使用更小的圖片不僅僅可以減少內(nèi)存的使用魄缚,還可以避免出現(xiàn)大量的InflationException宝与。假設有一張很大的圖片被XML文件直接引用,很有可能在初始化視圖的時候就會因為內(nèi)存不足而發(fā)生InflationException冶匹,這個問題的根本原因其實是發(fā)生了OOM习劫。

內(nèi)存對象的重復利用

大多數(shù)對象的復用,最終實施的方案都是利用對象池技術(shù)嚼隘,要么是在編寫代碼的時候顯式的在程序里面去創(chuàng)建對象池诽里,然后處理好復用的實現(xiàn)邏輯,要么就是利用系統(tǒng)框架既有的某些復用特性達到減少對象的重復創(chuàng)建飞蛹,從而減少內(nèi)存的分配與回收谤狡。在Android上面最常用的一個緩存算法是LRU算法。

在Android開發(fā)中卧檐,LruCache是基于LRU算法實現(xiàn)的墓懂。當緩存空間使用完的情況下,最久沒被使用的對象會被清除出緩存霉囚。LruCache是一個內(nèi)存層面的緩存捕仔,如果想要進行本地磁盤緩存,推薦使用DiskLruCache盈罐。
LruCache常用的場景是做圖片內(nèi)存緩存榜跌,電商類APP經(jīng)常會用到圖片,當我們對圖片資源做了內(nèi)存緩存盅粪,不僅可以增強用戶體驗钓葫,而且可以減少圖片網(wǎng)絡請求,減少用戶流量耗費湾揽。

LruCache內(nèi)部又維護了一個雙向鏈表結(jié)構(gòu)LinkeadHashMap瓤逼。HashMap和雙向鏈表合二為一即是LinkedHashMap。所謂LinkedHashMap库物,其落腳點在HashMap霸旗,因此更準確地說,它是一個將所有Entry節(jié)點鏈入一個雙向鏈表的HashMap戚揭。由于LinkedHashMap是HashMap的子類诱告,所以LinkedHashMap自然會擁有HashMap的所有特性。比如民晒,LinkedHashMap的元素存取過程基本與HashMap基本類似精居,只是在細節(jié)實現(xiàn)上稍有不同锄禽。
LinkedHashMap 的存取過程基本與HashMap基本類似,只是在細節(jié)實現(xiàn)上稍有不同靴姿,這是由LinkedHashMap本身的特性所決定的沃但,因為它要額外維護一個雙向鏈表用于保持迭代順序。特別地佛吓,該迭代順序可以是插入順序宵晚,也可以是訪問順序。
LinkedHashMap實現(xiàn)LRU的必要前提是將accessOrder標志位設為true以便開啟按訪問順序排序的模式维雇。我們可以看到淤刃,無論是put方法還是get方法,都會導致目標Entry成為最近訪問的Entry吱型,因此就把該Entry加入到了雙向鏈表的末尾逸贾。

復用系統(tǒng)自帶的資源
Android系統(tǒng)本身內(nèi)置了很多的資源,例如字符串/顏色/圖片/動畫/樣式以及簡單布局等等津滞,這些資源都可以在應用程序中直接引用铝侵。這樣做不僅僅可以減少應用程序的自身負重,減小APK的大小据沈,另外還可以一定程度上減少內(nèi)存的開銷哟沫,復用性更好饺蔑。但是也有必要留意Android系統(tǒng)的版本差異性锌介,對那些不同系統(tǒng)版本上表現(xiàn)存在很大差異,不符合需求的情況猾警,還是需要應用程序自身內(nèi)置進去孔祸。
注意在ListView/GridView等出現(xiàn)大量重復子組件的視圖里面對ConvertView的復用

Bitmap對象的復用

  • 在ListView與GridView等顯示大量圖片的控件里面需要使用LRU的機制來緩存處理好的Bitmap。

避免在onDraw方法里面執(zhí)行對象的創(chuàng)建
類似onDraw等頻繁調(diào)用的方法发皿,一定需要注意避免在這里做創(chuàng)建對象的操作崔慧,因為他會迅速增加內(nèi)存的使用,而且很容易引起頻繁的GC穴墅,甚至是內(nèi)存抖動惶室。

StringBuilder
在有些時候,代碼中會需要使用到大量的字符串拼接的操作玄货,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”皇钞。

避免對象的內(nèi)存泄露

內(nèi)存泄漏表示的是不再用到的對象因為被錯誤引用而無法進行回收

內(nèi)存對象的泄漏松捉,會導致一些不再使用的對象無法及時釋放夹界,這樣一方面占用了寶貴的內(nèi)存空間,很容易導致后續(xù)需要分配內(nèi)存的時候隘世,空閑空間不足而出現(xiàn)OOM可柿。顯然鸠踪,這還使得每級Generation的內(nèi)存區(qū)域可用空間變小,GC就會更容易被觸發(fā)复斥,容易出現(xiàn)內(nèi)存抖動营密,從而引起性能問題。

Android常見內(nèi)存泄漏方式
不止Android程序員目锭,內(nèi)存泄露應該是大部分程序員都遇到過的問題卵贱,可以說大部分的內(nèi)存問題都是內(nèi)存泄露導致的,Android里也有一些很常見的內(nèi)存泄露問題:

  • 單例(主要原因還是因為一般情況下單例都是全局的侣集,有時候會引用一些實際生命周期比較短的變量键俱,導致其無法釋放)
  • 靜態(tài)變量(同樣也是因為生命周期比較長)
  • 非靜態(tài)內(nèi)部類造成的內(nèi)存泄漏
  • Handler內(nèi)存泄露(Handler或Runnable作為非靜態(tài)內(nèi)部類)
  • 匿名內(nèi)部類(匿名內(nèi)部類會引用外部類,導致無法釋放世分,比如各種回調(diào))
  • 資源使用完未關(guān)閉(BroadcastReceiver编振、ContentObserver、File臭埋、Cursor踪央、Stream、Bitmap)

注意Activity的泄漏
通常來說瓢阴,Activity的泄漏是內(nèi)存泄漏里面最嚴重的問題畅蹂,它占用的內(nèi)存多,影響面廣荣恐,我們需要特別注意以下兩種情況導致的Activity泄漏:

  • 內(nèi)部類引用導致Activity的泄漏
    最典型的場景是Handler導致的Activity泄漏液斜,如果Handler中有延遲的任務或者是等待執(zhí)行的任務隊列過長,都有可能因為Handler繼續(xù)執(zhí)行而導致Activity發(fā)生泄漏叠穆。此時的引用關(guān)系鏈是Looper -> MessageQueue -> Message -> Handler -> Activity少漆。為了解決這個問題,可以在UI退出之前硼被,執(zhí)行remove Handler消息隊列中的消息與runnable對象示损。或者是使用Static + WeakReference的方式來達到斷開Handler與Activity之間存在引用關(guān)系的目的嚷硫。

  • Activity Context被傳遞到其他實例中检访,這可能導致自身被引用而發(fā)生泄漏。
    內(nèi)部類引起的泄漏不僅僅會發(fā)生在Activity上仔掸,其他任何內(nèi)部類出現(xiàn)的地方脆贵,都需要特別留意!我們可以考慮盡量使用static類型的內(nèi)部類嘉汰,同時使用WeakReference的機制來避免因為互相引用而出現(xiàn)的泄露丹禀。

考慮使用Application Context而不是Activity Context
對于大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),我們都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經(jīng)意的Activity泄露双泪。

注意臨時Bitmap對象的及時回收
雖然在大多數(shù)情況下持搜,我們會對Bitmap增加緩存機制,但是在某些時候焙矛,部分Bitmap是需要及時回收的葫盼。例如臨時創(chuàng)建的某個相對比較大的bitmap對象,在經(jīng)過變換得到新的bitmap對象之后村斟,應該盡快回收原始的bitmap贫导,這樣能夠更快釋放原始bitmap所占用的空間。

需要特別留意的是Bitmap類里面提供的createBitmap()方法蟆盹,這個函數(shù)返回的bitmap有可能和source bitmap是同一個孩灯,在回收的時候,需要特別檢查source bitmap與return bitmap的引用是否相同逾滥,只有在不等的情況下峰档,才能夠執(zhí)行source bitmap的recycle方法。

注意監(jiān)聽器的注銷
在Android程序里面存在很多需要register與unregister的監(jiān)聽器寨昙,我們需要確保在合適的時候及時unregister那些監(jiān)聽器讥巡。自己手動add的listener,需要記得及時remove這個listener舔哪。

注意緩存容器中的對象泄漏
有時候欢顷,我們?yōu)榱颂岣邔ο蟮膹陀眯园涯承ο蠓诺骄彺嫒萜髦校墒侨绻@些對象沒有及時從容器中清除捉蚤,也是有可能導致內(nèi)存泄漏的抬驴。例如,針對2.3的系統(tǒng)外里,如果把drawable添加到緩存容器怎爵,因為drawable與View的強應用特石,很容易導致activity發(fā)生泄漏盅蝗。而從4.0開始,就不存在這個問題姆蘸。解決這個問題墩莫,需要對2.3系統(tǒng)上的緩存drawable做特殊封裝,處理引用解綁的問題逞敷,避免泄漏的情況狂秦。

注意Cursor對象是否及時關(guān)閉
在程序中我們經(jīng)常會進行查詢數(shù)據(jù)庫的操作,但時常會存在不小心使用Cursor之后沒有及時關(guān)閉的情況推捐。這些Cursor的泄露裂问,反復多次出現(xiàn)的話會對內(nèi)存管理產(chǎn)生很大的負面影響,我們需要謹記對Cursor對象的及時關(guān)閉。

內(nèi)存使用策略優(yōu)化

謹慎使用large heap
正如前面提到的堪簿,Android設備根據(jù)硬件與軟件的設置差異而存在不同大小的內(nèi)存空間痊乾,他們?yōu)閼贸绦蛟O置了不同大小的Heap限制閾值。你可以通過調(diào)用getMemoryClass()來獲取應用的可用Heap大小椭更。在一些特殊的情景下哪审,你可以通過在manifestapplication標簽下添加largeHeap=true的屬性來為應用聲明一個更大的heap空間。然后虑瀑,你可以通過getLargeMemoryClass()來獲取到這個更大的heap size閾值湿滓。然而,聲明得到更大Heap閾值的本意是為了一小部分會消耗大量RAM的應用(例如一個大圖片的編輯應用)舌狗。不要輕易的因為你需要使用更多的內(nèi)存而去請求一個大的Heap Size叽奥。只有當你清楚的知道哪里會使用大量的內(nèi)存并且知道為什么這些內(nèi)存必須被保留時才去使用large heap。因此請謹慎使用large heap屬性痛侍。使用額外的內(nèi)存空間會影響系統(tǒng)整體的用戶體驗而线,并且會使得每次gc的運行時間更長。在任務切換時恋日,系統(tǒng)的性能會大打折扣膀篮。另外, large heap并不一定能夠獲取到更大的heap。在某些有嚴格限制的機器上岂膳,large heap的大小和通常的heap size是一樣的誓竿。因此即使你申請了large heap,你還是應該通過執(zhí)行getMemoryClass()來檢查實際獲取到的heap大小谈截。

綜合考慮設備內(nèi)存閾值與其他因素設計合適的緩存大小
例如筷屡,在設計ListView或者GridView的Bitmap LRU緩存的時候,需要考慮的點有:

  • 應用程序剩下了多少可用的內(nèi)存空間?
  • 有多少圖片會被一次呈現(xiàn)到屏幕上簸喂?有多少圖片需要事先緩存好以便快速滑動時能夠立即顯示到屏幕毙死?
  • 設備的屏幕大小與密度是多少? 一個xhdpi的設備會比hdpi需要一個更大的Cache來hold住同樣數(shù)量的圖片。
  • 不同的頁面針對Bitmap的設計的尺寸與配置是什么喻鳄,大概會花費多少內(nèi)存扼倘?
  • 頁面圖片被訪問的頻率?是否存在其中的一部分比其他的圖片具有更高的訪問頻繁除呵?如果是再菊,也許你想要保存那些最常訪問的到內(nèi)存中,或者為不同組別的位圖(按訪問頻率分組)設置多個LruCache容器颜曾。

onLowMemory()與onTrimMemory()
Android用戶可以隨意在不同的應用之間進行快速切換纠拔。為了讓background的應用能夠迅速的切換到forground,每一個background的應用都會占用一定的內(nèi)存泛豪。Android系統(tǒng)會根據(jù)當前的系統(tǒng)的內(nèi)存使用情況稠诲,決定回收部分background的應用內(nèi)存侦鹏。如果background的應用從暫停狀態(tài)直接被恢復到forground,能夠獲得較快的恢復體驗臀叙,如果background應用是從Kill的狀態(tài)進行恢復种柑,相比之下就顯得稍微有點慢。

  • onLowMemory():Android系統(tǒng)提供了一些回調(diào)來通知當前應用的內(nèi)存使用情況匹耕,通常來說聚请,當所有的background應用都被kill掉的時候,forground應用會收到onLowMemory()的回調(diào)稳其。在這種情況下驶赏,需要盡快釋放當前應用的非必須的內(nèi)存資源,從而確保系統(tǒng)能夠繼續(xù)穩(wěn)定運行既鞠。

  • onTrimMemory(int):Android系統(tǒng)從4.0開始還提供了onTrimMemory()的回調(diào)煤傍,當系統(tǒng)內(nèi)存達到某些條件的時候,所有正在運行的應用都會收到這個回調(diào)嘱蛋,同時在這個回調(diào)里面會傳遞以下的參數(shù)蚯姆,代表不同的內(nèi)存使用情況,收到onTrimMemory()回調(diào)的時候洒敏,需要根據(jù)傳遞的參數(shù)類型進行判斷龄恋,合理的選擇釋放自身的一些內(nèi)存占用,一方面可以提高系統(tǒng)的整體運行流暢度凶伙,另外也可以避免自己被系統(tǒng)判斷為優(yōu)先需要殺掉的應用郭毕。下面介紹各種不同的回調(diào)參數(shù):

  • TRIM_MEMORY_UI_HIDDEN:你的應用程序的所有UI界面被隱藏了,即用戶點擊了Home鍵或者Back鍵退出應用函荣,導致應用的UI界面完全不可見显押。這個時候應該釋放一些不可見的時候非必須的資源

當程序正在前臺運行的時候,可能會接收到從onTrimMemory()中返回的下面的值之一:

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

當應用進程退到后臺正在被Cached的時候,可能會接收到從onTrimMemory()中返回的下面的值之一:

  • TRIM_MEMORY_BACKGROUND: 系統(tǒng)正運行于低內(nèi)存狀態(tài)并且你的進程正處于LRU緩存名單中最不容易殺掉的位置。盡管你的應用進程并不是處于被殺掉的高危險狀態(tài)瓦糕,系統(tǒng)可能已經(jīng)開始殺掉LRU緩存中的其他進程了底洗。你應該釋放那些容易恢復的資源,以便于你的進程可以保留下來咕娄,這樣當用戶回退到你的應用的時候才能夠迅速恢復亥揖。

  • 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名單中最容易被殺掉的位置。你應該釋放任何不影響你的應用恢復狀態(tài)的資源圣贸。

  • 因為onTrimMemory()的回調(diào)是在API 14才被加進來的挚歧,對于老的版本,你可以使用onLowMemory)回調(diào)來進行兼容吁峻。onLowMemory相當與TRIM_MEMORY_COMPLETE滑负。

  • 請注意:當系統(tǒng)開始清除LRU緩存中的進程時,雖然它首先按照LRU的順序來執(zhí)行操作用含,但是它同樣會考慮進程的內(nèi)存使用量以及其他因素矮慕。占用越少的進程越容易被留下來。

資源文件需要選擇合適的文件夾進行存放

我們知道hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設備上會經(jīng)過scale的處理啄骇。例如我們只在hdpi的目錄下放置了一張100100的圖片凡傅,那么根據(jù)換算關(guān)系,xxhdpi的手機去引用那張圖片就會被拉伸到200200肠缔。需要注意到在這種情況下夏跷,內(nèi)存占用是會顯著提高的。對于不希望被拉伸的圖片明未,需要放到assets或者nodpi的目錄下槽华。

Try catch某些大內(nèi)存分配的操作

在某些情況下,我們需要事先評估那些可能發(fā)生OOM的代碼趟妥,對于這些可能發(fā)生OOM的代碼猫态,加入catch機制,可以考慮在catch里面嘗試一次降級的內(nèi)存分配操作披摄。例如decode bitmap的時候亲雪,catch到OOM,可以嘗試把采樣比例再增加一倍之后疚膊,再次嘗試decode义辕。

謹慎使用static對象

因為static的生命周期過長,和應用的進程保持一致寓盗,使用不當很可能導致對象泄漏灌砖,在Android中應該謹慎使用static對象璧函。

特別留意單例對象中不合理的持有

雖然單例模式簡單實用,提供了很多便利性基显,但是因為單例的生命周期和應用保持一致蘸吓,使用不合理很容易出現(xiàn)持有對象的泄漏。

珍惜Services資源

如果你的應用需要在后臺使用Service撩幽,除非它被觸發(fā)并執(zhí)行一個任務库继,否則其他時候Service都應該是停止狀態(tài)。另外需要注意當這個Service完成任務之后因為停止service失敗而引起的內(nèi)存泄漏窜醉。 當你啟動一個Service宪萄,系統(tǒng)會傾向為了保留這個Service而一直保留Service所在的進程。這使得進程的運行代價很高酱虎,因為系統(tǒng)沒有辦法把Service所占用的RAM空間騰出來讓給其他組件雨膨,另外Service還不能被Paged out。這減少了系統(tǒng)能夠存放到LRU緩存當中的進程數(shù)量读串,它會影響應用之間的切換效率聊记,甚至會導致系統(tǒng)內(nèi)存使用不穩(wěn)定,從而無法繼續(xù)保持住所有目前正在運行的Service恢暖。 建議使用IntentService排监,它會在處理完交代給它的任務之后盡快結(jié)束自己。

優(yōu)化布局層次杰捂,減少內(nèi)存消耗

越扁平化的視圖布局舆床,占用的內(nèi)存就越少,效率越高嫁佳。我們需要盡量保證布局足夠扁平化挨队,當使用系統(tǒng)提供的View無法實現(xiàn)足夠扁平的時候考慮使用自定義View來達到目的。

謹慎使用多進程

使用多進程可以把應用中的部分組件運行在單獨的進程當中蒿往,這樣可以擴大應用的內(nèi)存占用范圍盛垦,但是這個技術(shù)必須謹慎使用,絕大多數(shù)應用都不應該貿(mào)然使用多進程瓤漏,一方面是因為使用多進程會使得代碼邏輯更加復雜腾夯,另外如果使用不當,它可能反而會導致顯著增加內(nèi)存蔬充。當你的應用需要運行一個常駐后臺的任務蝶俱,而且這個任務并不輕量,可以考慮使用這個技術(shù)饥漫。

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

Android內(nèi)存優(yōu)化之布局優(yōu)化

主要介紹使用抽象布局標簽(include, viewstub, merge)浅侨、去除不必要的嵌套和View節(jié)點、減少不必要的infalte及其他Layout方面可調(diào)優(yōu)點证膨,順帶提及布局調(diào)優(yōu)相關(guān)工具(hierarchy viewer和lint)如输。

抽象布局標簽

<include>標簽

include標簽常用于將布局中的公共部分提取出來供其他layout共用,以實現(xiàn)布局模塊化央勒,這在布局編寫方便提供了大大的便利不见。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ListView />

    <include layout="@layout/foot.xml" />

</RelativeLayout>

<include>標簽唯一需要的屬性是layout屬性,指定需要包含的布局文件崔步∥人保可以定義android:idandroid:layout_*屬性來覆蓋被引入布局根節(jié)點的對應屬性值。注意重新定義android:id后井濒,子布局的頂結(jié)點就變化了灶似。

<ViewStub>標簽
ViewStub標簽同include標簽一樣可以用來引入一個外部布局,不同的是瑞你,ViewStub引入的布局默認不會擴張酪惭,即既不會占用顯示也不會占用位置,從而在解析layout時節(jié)省CPU和內(nèi)存者甲。

ViewStub常用來引入那些默認不會顯示春感,只在特殊情況下顯示的布局,如進度布局虏缸、網(wǎng)絡失敗顯示的刷新布局鲫懒、信息出錯出現(xiàn)的提示布局等

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <ViewStub
        android:id="@+id/network_error_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout="@layout/network_error" />
</RelativeLayout>

<merge>標簽
在使用了include后可能導致布局嵌套過多刽辙,多余不必要的layout節(jié)點窥岩,從而導致解析變慢

merge標簽可用于兩種典型情況:

  • 布局根節(jié)點是FrameLayout且不需要設置background或padding等屬性扫倡,可以用merge代替谦秧,因為Activity內(nèi)容試圖的parent view就是個FrameLayout,所以可以用merge消除只剩一個撵溃。
  • 某布局作為子布局被其他布局include時巾兆,使用merge作為該公共布局布局的根節(jié)點州弟,這樣在公共布局被引入時根節(jié)點會自動被忽略,而將其子節(jié)點全部合并到主布局中
//將xml中RelativeLayout改為merge
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_above="@+id/text"/>

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_40"
        android:layout_alignParentBottom="true"
        android:text="@string/app_name" />

</merge>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末量瓜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖际歼,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姑蓝,居然都是意外死亡鹅心,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進店門纺荧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旭愧,“玉大人,你說我怎么就攤上這事宙暇∈淇荩” “怎么了?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵占贫,是天一觀的道長桃熄。 經(jīng)常有香客問我,道長型奥,這世上最難降的妖魔是什么瞳收? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮桩引,結(jié)果婚禮上缎讼,老公的妹妹穿的比我還像新娘。我一直安慰自己坑匠,他們只是感情好血崭,可當我...
    茶點故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著厘灼,像睡著了一般夹纫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上设凹,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天舰讹,我揣著相機與錄音,去河邊找鬼闪朱。 笑死月匣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的奋姿。 我是一名探鬼主播锄开,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼称诗!你這毒婦竟也來了萍悴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎癣诱,沒想到半個月后计维,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡撕予,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年鲫惶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嗅蔬。...
    茶點故事閱讀 39,711評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡剑按,死狀恐怖疾就,靈堂內(nèi)的尸體忽然破棺而出澜术,到底是詐尸還是另有隱情,我是刑警寧澤猬腰,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布鸟废,位于F島的核電站,受9級特大地震影響姑荷,放射性物質(zhì)發(fā)生泄漏盒延。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一鼠冕、第九天 我趴在偏房一處隱蔽的房頂上張望添寺。 院中可真熱鬧,春花似錦懈费、人聲如沸计露。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽票罐。三九已至,卻和暖如春泞边,著一層夾襖步出監(jiān)牢的瞬間该押,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工阵谚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蚕礼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓梢什,卻偏偏與公主長得像奠蹬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子绳矩,可洞房花燭夜當晚...
    茶點故事閱讀 44,611評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 操作系統(tǒng)對內(nèi)存的管理 沒有內(nèi)存抽象的年代 在早些的操作系統(tǒng)中罩润,并沒有引入內(nèi)存抽象的概念。程序直接訪問和操作的都是物...
    Mr槑閱讀 16,695評論 3 24
  • 操作系統(tǒng)概論 操作系統(tǒng)的概念 操作系統(tǒng)是指控制和管理計算機的軟硬件資源翼馆,并合理的組織調(diào)度計算機的工作和資源的分配割以,...
    野狗子嗷嗷嗷閱讀 11,928評論 3 34
  • 祭祀在現(xiàn)在實在是不多見了金度。由于科技的發(fā)展和新文化的變革,祭祀總是附帶著一些負面的信息严沥。 而在古代猜极,祭祀則是國家...
    谷乘風閱讀 918評論 0 0
  • 天氣漸漸暖了跟伏。每天下午,窗外的陽光慢慢轉(zhuǎn)成燦爛翩瓜,也給了被焦慮填滿的心情一個放晴的機會受扳。明媚而不刺眼的陽光里,米白的...
    不愛看文獻的兔兔閱讀 658評論 1 1
  • 社會的兩大趨勢兔跌,一個發(fā)生在一萬年前勘高,一個是二十五年前。 一萬年前坟桅,女性在社會中华望,與男性一樣,投入工作仅乓,為家庭創(chuàng)造一...
    winging閱讀 208評論 0 0