Android 性能優化——內存篇

歡迎轉載,轉載請標明出處【Hoolay Team】:
http://www.cnblogs.com/hoolay/p/6278229.html
Author : Hoolay Android Team Chiclaimhtml

1、android官方一些內存方面的內存tips

一、避免建立沒必要要的對象。android

  • 如儘可能避免字符串的加號拼接,可使用StringBuilder來拼接。
  • 若是須要TextView設置多個字符串片斷,可使用textView.append方法,不要直接用加號拼起來。

二、儘可能使用for-each循環,對於ArrayList,請使用普通的for,如:數據庫

int len = list.size();
for (int i = 0; i < len; ++i) {
    //todo somgthing
}

三、使用系統庫函數。api

  • 使用系統庫函數,而且還有彙編級別的優化,他們一般比帶有JIT的Java編譯出來的代碼更高效 如:System.arraycopy(); String.indexOf()等。
  • 若是系統函數可以解決的,不要加入第三方庫,可能第三方庫使用起來比較簡單(jsoup,htmlparser等)。如解析HTML可使用系統的XmlPullParser

2、使用 ArrayMap、SparseArray代替HashMap

ArrayMap 和 HashMap的在內存的使用上,更加高效。數組

ArrayMap實現上有兩個數組,一個數組是保存key hash,另外一個數組保存value,ArrayMap經過二分法(binary search)來進行查找的。緩存

HashMap經過一個數組來實現的,key hash做爲數組的索引,這樣就須要更大的內存來減小key hash的衝突,key hash就是數組的索引,因此查找效率很高。網絡

使用建議:多線程

  • 當數據量比較小的時候(小於1000),優先使用ArrayMap,不然使用HashMap。app

  • map裏嵌套map。less

使用方法:

  • ArrayMap和HashMap的使用方法都是同樣的,ArrayMap也實現了Map接口。

  • 另外,ArrayMap能夠經過keyAt(index)方法來獲取第index位置的key,keyValue(int index)同理。可是HashMap是不能夠的。

arrayMap.keyAt(0);
  arrayMap.valueAt(0);

SparseArray和ArrayMap很是像,它們都是經過兩種緊密包裝的數組,而不是一個大的哈希散列,從而減小了整個內存的覆蓋區。可是查詢的速度就慢了。

只不過SparseArray和ArrayMap最大的區別是SparseArray的key是一個基本類型。

SparseArray的key是int類型,而不是Integer。像之前使用HashMap的時候,若是key是整形,必須是Integer。

Integer佔16個字節,int只佔4個字節,若是元素比較多,從而能夠很好的減小內存的佔用。

除了SparseArray類還有以下類可供使用:

SparseBooleanMap <boolean,Object>
SparseIntMap <int,Object>
SparseLongMap <long,Object>

SparseArray和ArrayMap的使用建議使用方法都是同樣的。

3、Thread與Thread Pool

在android開發中,一些耗時的操做都會放到後臺線程去執行,好比:網絡、本地文件、數據庫等。

new Thread(new Runnable() {
        @Override
        public void run() {
            //do something...
        }
    }).start();

每一個線程至少消耗64k的內存,若是你在某個時間點,迅速開啓了不少線程(好比加載列表圖片,而後用戶滑動列表),這個時候可能內存使用量就會飆升。

  • 會出現內存抖動(memory churn),由於短期開啓了不少線程,完成任務後,這些線程都會被回收。內存表現爲:低-高-低。甚至可能出現OOM。

  • 一個系統所能處理的線程數量是有限的,若是超多了最大承載量,性能會受到很大的影響。並且可能還會影響用戶的後續操做。

這時候Thread Pool線程池的做用就凸顯出來了。

Java爲咱們提供了操做線程池的api ThreadPoolExecutor ,ExecutorService是一個接口,相關的線程池的類都實現了該接口,如 ThreadPoolExecutor 。

建立一個線程池能夠經過 ThreadPoolExecutor類來實現。

ThreadPoolExecutor pool = new ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);//新建一個線程池
pool.execute(Runnable);//執行任務

下面是官方對ThreadPoolExecutor的參數說明:

Parameters:
	corePoolSize - the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set
	maximumPoolSize - the maximum number of threads to allow in the pool
	keepAliveTime - when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
	unit - the time unit for the keepAliveTime argument
	workQueue - the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method.
  • corePoolSize 核心線程數,核心線程會一直存活,即便沒有任務須要處理。當線程數小於核心線程數時,即便現有的線程空閒,線程池也會優先建立新線程來處理任務,而不是直接交給現有的線程處理。核心線程在allowCoreThreadTimeout被設置爲true時會超時退出,默認狀況下不會退出。

  • maxPoolSize 線程池容許最大的線程數量。

  • keepAliveTime 當線程空閒時間達到keepAliveTime,該線程會退出,直到線程數量等於corePoolSize。若是allowCoreThreadTimeout設置爲true,則全部線程均會退出直到線程數量爲0。

  • allowCoreThreadTimeout 是否容許核心線程空閒keepAliveTime退出,默認值爲false。

  • workQueue 任務隊列。pool.execute(runnable)提交的task都會放到workQueue。

下面來一個簡單的sample:

public class MyClass {

    private ThreadPoolExecutor pool ;

    private MyClass(){
    	//建立線程池
        pool = new ThreadPoolExecutor(4, 7, 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
    }

    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        for (int i = 0; i < 10; i++) {
        	//提交任務
            myClass.pool.execute(new MyRunnable(myClass));
        }
        myClass.pool.shutdown();
    }

    private String getCount() {
        return pool.getCorePoolSize()+"-"+pool.getActiveCount() + "-" + pool.getMaximumPoolSize();
    }

    private static class MyRunnable implements Runnable {
        MyClass myClass;

        MyRunnable(MyClass myClass) {
            this.myClass = myClass;
        }

        @Override
        public void run() {
            System.out.println("thread name:" + Thread.currentThread().getName() + " start " + myClass.getCount());
            try {
            	//模擬耗時任務
                Thread.sleep(3000L);
                System.out.println("thread name:" + Thread.currentThread().getName() + " end " + myClass.getCount());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

上面的代碼很簡單:建立了一個corePoolSize爲4,maxPoolSize爲7的線程池。
而後往線程池裏提交10個任務,每一個任務打印pool.getCorePoolSize()+"-"+pool.getActiveCount() + "-" + pool.getMaximumPoolSize(),即corePoolSize(核心線程數),activeCount(正在活動的線程總數)和maximumPoolSize(線程池容許的最大線程數)值。

測試結果以下:

thread name:pool-1-thread-1 start 4-2-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-3 start 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-1 end 4-4-7
thread name:pool-1-thread-2 end 4-3-7
thread name:pool-1-thread-4 end 4-4-7
thread name:pool-1-thread-1 start 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-3 end 4-4-7
thread name:pool-1-thread-3 start 4-4-7
thread name:pool-1-thread-2 end 4-4-7
thread name:pool-1-thread-4 end 4-4-7
thread name:pool-1-thread-3 end 4-4-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-1 end 4-3-7
thread name:pool-1-thread-2 start 4-4-7
thread name:pool-1-thread-4 end 4-2-7
thread name:pool-1-thread-2 end 4-2-7

Process finished with exit code 0

從測試結果來看,咱們打印pool.getCorePoolSize()+"-"+pool.getActiveCount() + "-" + pool.getMaximumPoolSize()的值是正常的。可是隻建立了4個線程:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-3
pool-1-thread-4

咱們設置了線程池的最大數爲7,咱們提交了10個任務,可是爲何只建立了corePoolSize=4個線程?

查看官方文檔能夠找到答案:

  • 當經過execute(Runnable)提交一個新任務,而且小於corePoolSize正在運行的線程數,將會建立一個新的線程來處理這個任務,無論線程池裏有沒有空閒的線程。

  • If there are more than corePoolSize but less than maximumPoolSize threads running, a new thread will be created only if the queue is full.
    大於corePoolSize小於maximumPoolSize,workQueue隊列滿了,纔會建立新的線程。

  • 若是corePoolSize和maximumPoolSize值設置成同樣的,至關於建立了一個固定數量的線程池。

  • 多數狀況下,都是經過構造方法來設置corePoolSize和maximumPoolSize,可是也能夠經過setCorePoolSize和setMaximumPoolSize來動態設置。

因此上面的例子,只建立了4個線程,由於雖然咱們提交了10個任務,可是構建workQueue時候沒有傳入隊列大小,默認大小是Integer.MAX_VALUE,因此workQueue是不會滿的。因此最多就建立了4個線程。

據此,我把workQueue隊列容量改爲4:

pool = new ThreadPoolExecutor(4, 7, 60, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(4));

測試結果:

thread name:pool-1-thread-1 start 4-2-7
thread name:pool-1-thread-2 start 4-2-7
thread name:pool-1-thread-3 start 4-3-7
thread name:pool-1-thread-4 start 4-4-7
thread name:pool-1-thread-5 start 4-6-7
thread name:pool-1-thread-6 start 4-6-7
thread name:pool-1-thread-1 end 4-6-7
thread name:pool-1-thread-2 end 4-6-7
thread name:pool-1-thread-2 start 4-5-7
thread name:pool-1-thread-1 start 4-6-7
thread name:pool-1-thread-3 end 4-6-7
thread name:pool-1-thread-3 start 4-6-7
thread name:pool-1-thread-4 end 4-6-7
thread name:pool-1-thread-5 end 4-6-7
thread name:pool-1-thread-4 start 4-6-7
thread name:pool-1-thread-6 end 4-5-7
thread name:pool-1-thread-1 end 4-4-7
thread name:pool-1-thread-2 end 4-4-7
thread name:pool-1-thread-3 end 4-2-7
thread name:pool-1-thread-4 end 4-1-7

Process finished with exit code 0

發現建立了6個線程,大於上一次的測試結果(上一次是建立了4個線程),但是咱們設置的maximumPoolSize爲7,按道理應該是建立7個線程纔對呀,這是爲何呢?

這須要瞭解下workQueue隊列的策略了。咱們上面的列子使用的是 LinkedBlockingQueue。

下面來看看官方文檔對 BlockingQueue的描述:

Any link BlockingQueue may be used to transfer and hold
submitted tasks.  The use of this queue interacts with pool sizing:
If fewer than corePoolSize threads are running, the Executor
always prefers adding a new thread
rather than queuing.

If corePoolSize or more threads are running, the Executor
always prefers queuing a request rather than adding a new
thread.

If a request cannot be queued, a new thread is created unless
this would exceed maximumPoolSize, in which case, the task will be
rejected.

主要意思就是:

  • 若是運行的線程少於 corePoolSize,Executor會建立新線程來執行任務,不會把任務放進queue。

  • 若是運行的線程等於或多於 corePoolSize,Executor將請求加入隊列,而不是建立新的線程。

  • 若是隊列已滿,沒法將請求加入隊列,則建立新的線程,除非建立此線程超出 maximumPoolSize,在這種狀況下,任務將被拒絕。

這樣就能解釋爲何只建立6個線程了。

總共有10個task,核心線程corePoolSize=4,因此3個任務是不會放進queue的,直接建立3個新線程來處理task了,而後再執行execute(Runnable)的時候,就會大於等於corePoolSize,因此就會把接下來的4個任務放進queue(容量爲4),而後就剩下3個task了,發現隊列已經滿了,建立3個線程來處理這剩下的3個task,因此總共只建立6個線程了。

maximumPoolSize=7,我就是想讓它建立7個線程,咱們知道了上面的原理就很簡單了,能夠把隊列的最大容量改爲3或者添加11個任務就能夠了:

new LinkedBlockingQueue<Runnable>(3)

for (int i = 0; i < 11; i++) {
    myClass.pool.execute(new MyRunnable(myClass));
}

效果以下:

thread name:pool-1-thread-1 start 0-1-7
thread name:pool-1-thread-2 start 0-2-7
thread name:pool-1-thread-3 start 1-4-7
thread name:pool-1-thread-4 start 4-5-7
thread name:pool-1-thread-5 start 4-7-7
thread name:pool-1-thread-6 start 4-7-7
thread name:pool-1-thread-7 start 4-7-7
.....

Java提供了3種策略的Queue

  • SynchronousQueue 直接傳送task(Direct handoffs)

  • LinkedBlockingQueue 無邊界隊列(Unbounded queues)

  • ArrayBlockingQueue 有邊界隊列(Bounded queues)
    更多詳細信息能夠查看官方文檔。

Java給咱們提供了一些工廠方法來來建立線程池():

  • Executors.newFixedThreadPool(int threads);

  • Executors.newCachedThreadPool();

  • Executors.newSingleThreadExecutor();

  • Executors.newScheduledThreadPool(int threads);

這些方法都是經過構建ThreadPoolExecutor來實現的,具體的細節能夠去看看文檔,若是都不知足你的需求,能夠本身構造ThreadPoolExecutor。

4、IntentService與Service

通常咱們在app裏的版本更新邏輯在Service裏起一個線程來檢測。

爲了不Service一直存在,減小內存消耗,檢測版本後,還須要手動stopSelf,略麻煩。

這時候用IntentService就比較合適了,默認就給你啓動了一個線程來執行耗時操做,完成自動關閉service。

Service和IntentService主要區別:

  • IntentService執行完會自動關閉(stopSelf),而Service不會。

  • IntentService會啓動一個線程來執行耗時操做,把耗時操做放到onHandleIntent(Intent intent)方法裏。而Service須要本身new Thread。

  • 若是調用startService(intent)屢次,IntentService會執行屢次onHandleIntent(Intent intent),且必須等本次的onHandleIntent(Intent intent)執行完,纔會執行下一次onHandleIntent(Intent intent),說白了就是若是正在執行任務,會把後面啓動的命令放到隊列裏。而屢次調用startService(intent),Service僅僅會屢次調用onStartCommand方法。

5、避免常見的內存泄露

一、CountDownTimer、TimerTask、Handler致使的內存泄露

在項目中,咱們經常可能要作活動倒計時的功能,我是用CountDownTimer來作的。如:

public static class TimeCounter extends CountDownTimer {
	public TimeCounter(long millisInFuture, long countDownInterval) {
	    super(millisInFuture, countDownInterval);
	}

	@Override
	public void onFinish() {
	    //倒計時結束
	}

	@Override
	public void onTick(long millisUntilFinished) {
	    //每間隔固定時間執行一次
	    //再次處理倒計時邏輯
	}
}

以下圖所示:

倒計時
由於要在TimeCounter內部要修改View的顯示,因此要把TextView傳遞進來,使用WeakReference來引用TextView避免內存泄露,如:

public static class TimeCounter extends CountDownTimer {

	private WeakReference<TextView> weakText;

    public TimeCounter(TextView textView, long millisInFuture, long countDownInterval) {
        super(millisInFuture, countDownInterval);
        weakText = new WeakReference<>(textView);
    }

	@Override
	public void onFinish() {
	    //倒計時結束
	}

	@Override
	public void onTick(long millisUntilFinished) {
	    //每間隔固定時間執行一次
	    //再次處理倒計時邏輯
	}

	private void setText(long millisUntilFinished){
		if (weakText.get() != null) {
			weakText.get().setText(xxx);
		}
	}
}

咱們知道,使用WeakReference包裝TextView,在發生GC的時候,TextView就會被回收。

須要注意的是,只有WeakReference所引用的對象沒有被其餘對象引用,當發生了GC,WeakReference所引用的對象纔會被回收的。

例如,A界面有個倒計時功能,而後把TextView傳給了上面的TimeCounter,而後在A界面的基礎上啓動了其餘界面,這時候假如發生了GC,這時候TextView是不會被回收的。

由於還有A界面對其還有引用。因此只有把A界面關閉了,且發生GC了 TextView纔會被收回。

在網上也看到一些,加上了WeakReference在GC的時候也沒有釋放。可能的緣由是WeakReference所引用的對象被其餘對象引用着,因此發生GC了,該對象仍是沒被回收。

相似這樣的內存泄露除了CountDownTimer還有Timer、Handler 等,表現以下:

Timer

//設置爲每秒執行一次(TimerTask的run方法是在後臺線程執行的)
new Timer().scheduleAtFixedRate(
        new TimerTask() {
            @Override
            public void run() {
                Log.e("Timer", "timer==========");
            }
        }, 0, 1000);

Handler

//設置爲每秒執行一次
handler = new android.os.Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.e("Handler", "Handler");
        sendEmptyMessageDelayed(1, 1000);
    }
};
handler.sendEmptyMessage(1);

//不可見時 移除消息
@Override
protected void onStop() {
    super.onStop();
    handler.removeMessages(1);
}
//可見的時 發送消息
@Override
protected void onResume() {
    super.onResume();
    if (!handler.hasMessages(1)) {
        sendEmptyMessageDelayed(1, 1000);
    }
}

上面的 TimerHandler 均可以實現諸如每隔1秒執行的功能,都會致使內存泄露的問題。

解決方案:

  • 使用靜態內部類(至關於外部類,訪問域不同),若是須要使用外部類的變量 如View,使用WeakReference引用外部的View;
  • 當界面關閉的時候必定要把定時器 Timer或 CountDownTimer關閉掉(cancel),若是是Handler使用removeMessages(int what)方法;

二、內部類(如Thread)致使的內存泄露

在項目經常要作一些耗時操做,能夠起一個Thread來作,如:

new Thread(new Runnable() {
    @Override
    public void run() {
        Log.e("Log", "執行耗時操做");
    }
}).start();

若是在Activity直接這樣使用,容易形成activity的內存泄露。

由於上面的Thread代碼段,其實是匿名內部類對象,它會對外部類(Activity)有一個引用。

若是Thread一直在執行,就算用戶按了返回鍵,activity對象會一直存在的。

由於匿名內部類對象一直引用者activity對象。

是否泄露在於Thread執行的耗時任務執行時間,若是Thread執行很是短期就完畢了,基本上形成不了多大的內存泄露,可是耗時任務執行的時間沒法預測的。

下面一個簡單的例子來演示下這個問題:

public class ThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activty_count_down_timer);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e("ThreadActivity", "執行耗時操做------" + ThreadActivity.this);
                try {
                    //模擬耗時操做60秒
                    Thread.sleep(1000 * 60);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e("ThreadActivity", "耗時操做執行完成------" + ThreadActivity.this);
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.e("ThreadActivity", "onDestroy------");
    }
}

運行起來後,接着按返回鍵,測試效果以下:

執行耗時操做------com.chiclaim.twitter.ThreadActivity@2e92d7ee
onDestroy------
耗時操做執行完成------com.chiclaim.twitter.ThreadActivity@2e92d7ee

從上面的代碼能夠看出,耗時任務咱們定爲60秒,啓動了Activity後,立馬輸出了:

執行耗時操做------com.chiclaim.twitter.ThreadActivity@2e92d7ee

緊接着按了返回鍵,輸出了:

onDestroy------

過了大概60秒,輸出了:

耗時操做執行完成------com.chiclaim.twitter.ThreadActivity@2e92d7ee

從上面的例子中咱們得出了2個結論:

  • 匿名內部類對象,它會對外部類有一個引用。
  • Activity執行了onDestroy方法,不見得Activity被銷燬了。上面的例子中,過了60秒依然能夠輸出外部類(Activity)對象。

解決方案:

  • 儘可能減小非靜態內部類的使用,上面的例子可使用靜態匿名內部類對象或者使用靜態內部類,這樣就不會對外部類對象由引用了。
  • 在app中的這種耗時任務,通常咱們處理邏輯的時候,只須要處理成功失敗的狀況,好比說網絡請求,若是一個界面有多個請求,那麼就會有不少內部類(回調)嵌套了,個人作法是使用者實現一個回調接口,該接口定義了成功和失敗的兩個方法,Activity onCreate的時候,把回調註冊到一個容器內,在onDestroy方法裏從容器中移除該回調。這樣也不會對Activity形成內存泄露。
  • 另外,若是某個耗時操做,須要傳入Context,若是沒有特殊的要求,不要傳遞Activity的Context。傳入Application級別的Context,這樣無論耗時操做執行多久,都不會致使Activity內存泄露。

三、由static的Activity、View、List致使的內存泄露

千萬不要是有static來修飾activity、View對象,這種內存泄露更加嚴重,由於它將貫穿程序的生命週期。
爲了更好的管理Activity經常把Activity放進容器中,如Stack。若是忘記了移除,也會形成內存泄漏。

6、onTrimMemory(int level)與onLowMemory()

onTrimMemory 回調是 Android 4.0 以後提供的一個API。

它的主要用來提示開發者在系統內存不足的時候,根據當前內存狀況(level),釋放相關資源以減少系統內存壓力,這樣能夠減小app進程被系統殺死的可能性。

儘量的保存app進程,等到用戶在下一次使用的時候,啓動速度就會比較快。

在Android 4.0以前都是onLowMemory來處理這類邏輯的,onLowMemory和onTrimMemory中的TRIM_MEMORY_COMPLETE級別相同。若是想兼容Android4.0以前的系統能夠實現該方法,不然只須要處理onTrimMemory方法。

下面來聊一聊onTrimMemory(int level)回調level的常量:

TRIM_MEMORY_UI_HIDDEN = 20 表示應用程序的全部UI界面被隱藏了,即用戶點擊了Home鍵或者剩下最後一個界面,按返回鍵後,Application的onTrimMemory回調也會調用。

下面三個等級是當咱們的應用程序正在前臺運行時的回調:

  • TRIM_MEMORY_RUNNING_MODERATE = 5 表示應用程序正常運行,而且不會被殺掉。可是目前手機的內存已經有點低了,你的正在運行的進程須要釋放一些不須要的內存資源。

  • TRIM_MEMORY_RUNNING_LOW = 10 表示應用程序正常運行,而且不會被殺掉。可是目前手機的內存已經很是低了,你的正在運行的進程須要釋放一些不須要的內存資源。

  • TRIM_MEMORY_RUNNING_CRITICAL = 15 表示應用程序仍然正常運行,可是系統內存已經極度低了,即將不能保留任何後臺進程 了。這個時候咱們應當儘量地去釋聽任何沒必要要的資源,下一步onLowMemory將會被調用,這樣的話,後臺將不會保留任何進程。

當app進程不可見處於LRU list中,則會收到如下常量的回調:

  • TRIM_MEMORY_BACKGROUND = 40 app進程不可見,處於LRU列表中,這時候是個釋放資源的好時機。

  • TRIM_MEMORY_MODERATE = 60 系統目前內存已經很低了,而且咱們的程序處於LRU緩存列表的中間位置。騰出一些內存讓系統運行其餘的進程。

  • TRIM_MEMORY_COMPLETE = 80 系統目前內存已經很低了,而且咱們的程序處於LRU緩存列表的最邊緣位置,若是系統找不到更多可能的內存,咱們的app進程很快將被殺死。

從上面能夠看出,這些常量大體能夠分爲兩類,一類是大於TRIM_MEMORY_UI_HIDDEN = 20,這類表示進程不可見。一類是小於TRIM_MEMORY_UI_HIDDEN = 20,這類表示app進程正在前臺運行。而且常量值越大,說明系統內存越緊張。

下面是官方推薦實現方案

/**
* Release memory when the UI becomes hidden or when system resources become low.
* @param level the memory-related event that was raised.
*/
public void onTrimMemory(int level) {

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

    case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
        /*
           Release any UI objects that currently hold memory.

           The user interface has moved to the background.
        */
        break;

    case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
    case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
    case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
        /*
           Release any memory that your app doesn't need to run.

           The device is running low on memory while the app is running.
           The event raised indicates the severity of the memory-related event.
           If the event is TRIM_MEMORY_RUNNING_CRITICAL, then the system will
           begin killing background processes.
        */
        break;

    case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
    case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
    case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
        /*
           Release as much memory as the process can.

           The app is on the LRU list and the system is running low on memory.
           The event raised indicates where the app sits within the LRU list.
           If the event is TRIM_MEMORY_COMPLETE, the process will be one of
           the first to be terminated.
        */
        break;

    default:
        /*
          Release any non-critical data structures.

          The app received an unrecognized memory level value
          from the system. Treat this as a generic low-memory message.
        */
        break;
}

如何針對上面的這些常量,分別釋放app裏哪些資源呢?(目前我只處理app在後臺的狀況)

當回調的參數level=TRIM_MEMORY_BACKGROUND(40)或TRIM_MEMORY_MODERATE(60)或TRIM_MEMORY_COMPLETE(80)時,

  • 把圖片佔用的內存釋放掉。

  • 清空緩存數據,例如列表中List的數據; 還能夠把動態建立的View或fragment釋放掉, 甚至activity的相關資源都釋放掉變成空Activity,重新回到這個Activity的時候,從新初始化數據。

  • 我會把全部的activity移除掉,僅留下主Activity。固然若是主Activity比較複雜,佔用的資源比較多,能夠把資源都釋放掉,留下一個空主Activity。當用戶回來的時候能夠迅速回來,若是內容清空了,從新加載便可。

相關注意事項:

  • 在回調中釋放內存,要注意該要釋放的界面是否可見,界面若是正在顯示,你卻把當前的界面的List數據清空了,這樣顯然是不妥的,系統會通知全部實現了onTrimMemory方法的相關組件(Application、Activity、Fragment、Service、ContentProvider)

  • 還要注意,作好相關數據恢復的邏輯,例如,把相關的數據清空了,該界面從新顯示的時候,要把相關釋放的數據,從新加載,若是能夠的話,儘量回到用戶上一次操做的狀態,例如滑動的位置,所填寫的數據等。

參考連接

http://blog.csdn.net/zhouhl_cn/article/details/7392607

https://www.youtube.com/watch?v=uCmHoEY1iTM&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&index=6

https://www.youtube.com/watch?annotation_id=annotation_2743589091&feature=iv&src_vid=uCmHoEY1iTM&v=NwFXVsM15Co

http://stackoverflow.com/questions/15524280/service-vs-intentservice

http://www.codeceo.com/article/android-ontrimmemory-mem.html

相關文章
相關標籤/搜索