一.介紹
Android機(jī)器中,內(nèi)存使用問題一直是個(gè)十分重要,引人注目的問題,當(dāng)我們代碼編寫不當(dāng),或者邏輯沒處理好,就會(huì)導(dǎo)致機(jī)器運(yùn)行緩慢,有時(shí)候甚至死機(jī)。
對(duì)于程序員來說,這很致命,所以要去理解內(nèi)存的使用,去避免內(nèi)存的泄露,不斷優(yōu)化內(nèi)存,而當(dāng)出現(xiàn)內(nèi)存泄露導(dǎo)致的問題,我們能夠分析log,并且會(huì)用工具M(jìn)AT。
二.什么場景會(huì)導(dǎo)致內(nèi)存泄露
內(nèi)存泄露其實(shí)就是占用內(nèi)存的對(duì)象使用后沒有被回收。在這種現(xiàn)象下,當(dāng)java程序運(yùn)行一段時(shí)間,占用的內(nèi)存越來越大,導(dǎo)致該進(jìn)程的內(nèi)存占用達(dá)到Android為進(jìn)程分配的內(nèi)存使用上限,程序就死了。
- ListView、GridView等在使用適配器時(shí),沒有使用ConvertView緩存
- Bitmap使用后沒有釋放
- Context泄露。Context的引用超過了本身的生命周期。比如一個(gè)長時(shí)間在跑的異步任務(wù)或者長時(shí)間的對(duì)象,擁有著Activity(Context類型)的引用,這時(shí)Activity被銷毀了,但是內(nèi)存依然存在,Context無法被回收。所以這種情況下, 可以使用getApplicationContext比較好,或者弱引用Context
- 數(shù)據(jù)庫游標(biāo)或者文件流緩存等使用后未關(guān)閉
- 線程使用不當(dāng)。在線程中的run函數(shù)處理著耗時(shí)的工作,當(dāng)設(shè)備橫豎屏切換,重新創(chuàng)建Activity,由于run函數(shù)未處理完,導(dǎo)致引用的Activity也不會(huì)被銷毀。再說AsyncTask,由于運(yùn)行機(jī)制ThreadPoolExcutor,生命周期更加不可控,更容易出現(xiàn)問題了(具體解決方法,可看下面講解)。
- Rxjava訂閱與反訂閱
- BraodcastReceiver等在生命周期結(jié)束后一定要記得unregidter
三.內(nèi)存優(yōu)化注意點(diǎn)
1.圖片優(yōu)化
在Android中顯示Bitmap圖片,會(huì)造成一定的內(nèi)存消耗,甚至?xí)?dǎo)致OOM異常爆發(fā),所以對(duì)于圖片的顯示,要做一定的處理。
- 大圖片顯示要進(jìn)行壓縮才能加載。一張圖片,不要認(rèn)為表面的小而不以為然,比如一張150kb的圖片,當(dāng)讀到內(nèi)存時(shí),若該圖片像素為20481024,使用屬性為ARGB_8888(默認(rèn)),也就是一個(gè)像素4byte,那么總內(nèi)存就是42048*1024byte,8M多。可見,大圖片壓縮加載的必要性。具體壓縮詳細(xì)方法可見 Android高效加載大圖,防止OOM,以及多圖解決方案
- 多圖顯示時(shí)要活用內(nèi)存緩存技術(shù)。在一個(gè)ListView(或GridView)中,不斷加載圖片,不可能一直把圖片都到內(nèi)存中,因?yàn)閮?nèi)存是有上限值的,也要為其他操作分配內(nèi)存。所以當(dāng)在可見區(qū)域里,要將移除屏幕的部分內(nèi)存進(jìn)行回收處理??扇粢瞥牟糠衷谙聜€(gè)操作中又要馬上使用,這時(shí)若被移除回收,性能效率馬上就下去了,所以可以使用LRUCache緩存技術(shù)。具體可見以上那篇博文。
- ListView中的快速滑動(dòng)加載圖片,在不影響使用體驗(yàn)的情況下,應(yīng)判斷滑動(dòng)狀態(tài)進(jìn)行加載操作。在快速滑動(dòng)時(shí),由于滑動(dòng)過程中加載的資源是不會(huì)被使用的,反而影響了用戶所要查看資源的加載,所以在快速滑動(dòng)(SCROLL_STATE_TOUCH_SCROLL)列表時(shí),就不再去獲取加載資源了,在靜止(SCROLL_STATE_IDLE)以及觸摸屏幕(SCROLL_STATE_TOUCH_SCROLL)時(shí)才去加載,我們需要注冊(cè)一個(gè)滾動(dòng)監(jiān)聽器OnScrollListener 。
- 由于圖片占用內(nèi)存的緣故,所以一些內(nèi)置資源的圖片可以經(jīng)過tinyPng處理,再放到app里加載。通常的壓縮率可以達(dá)到50%以上,意思是150kb的圖片處理后至少能減少到75kb以下,而且不失真。降低了內(nèi)存的占用,同時(shí)達(dá)到APK瘦身的效果。TinyPng入口地址
2.線程、異步任務(wù)優(yōu)化
因?yàn)榫€程、異步任務(wù)等生命周期的不可控性,成為了內(nèi)存泄露的另一個(gè)源頭。平常中,因?yàn)閷?duì)它的頻繁使用,所以,我們應(yīng)慎重對(duì)待它。
- 在Activity結(jié)束時(shí),應(yīng)及時(shí)銷毀所創(chuàng)建的線程。不然,當(dāng)線程持有該所在Activity的引用時(shí),實(shí)際上以為退出去的Activity,其實(shí)由于線程未完成,所引用的老Activity是不會(huì)被銷毀的,就出現(xiàn)了內(nèi)存泄露,所以可使用Thread.interrupt()中斷線程,雖然并不是真正意義上的中斷!具體詳細(xì)可見 Thread的中斷機(jī)制(interrupt),這個(gè)機(jī)制到底做了些什么呢?原來Thread.interrupt()方法不會(huì)中斷一個(gè)正在運(yùn)行的線程。這一方法實(shí)際上完成的是,設(shè)置線程的中斷標(biāo)示位,在線程受到阻塞的地方(如調(diào)用sleep、wait、join等地方)拋出一個(gè)異常InterruptedException,并且中斷狀態(tài)也將被清除,這樣線程就得以退出阻塞的狀態(tài)。
-
AsyncTask異步任務(wù)的生命周期不可控性。一定得注意一件事,因?yàn)橐郧昂芟矚g在Activity中創(chuàng)建AsyncTask作為內(nèi)部類,完成一些耗時(shí)且Ui交互的操作,十分方便,但是其實(shí)這個(gè)是具有很大風(fēng)險(xiǎn)的,因?yàn)楹苋菀壮霈F(xiàn)內(nèi)存泄露。異步任務(wù)內(nèi)部是以ThreadPoolExcutor作為實(shí)現(xiàn)機(jī)制的,這樣出來的線程對(duì)象生命周期是不確定的!!
解決方案:在線程內(nèi)部采用弱引用保存Context引用。即如下代碼所示:
public abstract class WeakAsyncTask < Params,Progress,Result,WeakTarget >
extends AsyncTask < Params,Progress,Result > {
protected WeakReference < WeakTarget > mTarget;
public WeakAsyncTask(WeakTarget target) {
mTarget = new WeakReference < WeakTarget > (target);
}
/** {@inheritDoc} */
@Override protected final void onPreExecute() {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPreExecute(target);
}
}
/** {@inheritDoc} */
@Override protected final Result doInBackground(Params...params) {
final WeakTarget target = mTarget.get();
if (target != null) {
return this.doInBackground(target, params);
} else {
return null;
}
}
/** {@inheritDoc} */
@Override protected final void onPostExecute(Result result) {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPostExecute(target, result);
}
}
protected void onPreExecute(WeakTarget target) {
// No default action
}
protected abstract Result doInBackground(WeakTarget target, Params...params);
protected void onPostExecute(WeakTarget target, Result result) {
// No default action
}
}
所以內(nèi)存優(yōu)化方面,還有很多方面需要補(bǔ)足,還需努力。
四.內(nèi)存分析
不管是對(duì)內(nèi)存使用情況的分析,還是排查內(nèi)存泄露與溢出,我都推薦去好好看看以下大神的文章,相信看過后,會(huì)有很大的收獲,我就是如此=。=
android 中如何分析內(nèi)存泄露
Android最佳性能實(shí)踐(二)——分析內(nèi)存的使用情況
小小的總結(jié)與推薦閱讀,希望可以幫助到大家~
Ps:以前博客遷移到簡書,如果你覺得文章還可以,麻煩底下點(diǎn)個(gè)“喜歡”!