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