1. 程式人生 > >Android 效能優化—記憶體篇

Android 效能優化—記憶體篇

一、android官方一些記憶體方面的記憶體tips

1、避免建立不必要的物件。

  • 如儘量避免字串的加號拼接,可以使用StringBuilder來拼接。
  • 如果需要TextView設定多個字串片段,可以使用textView.append方法,不要直接用加號拼起來。

2、儘量使用for-each迴圈,對於ArrayList,請使用普通的for,如:

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

3、使用系統庫函式。

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

二、使用 ArrayMap、SparseArray代替HashMap

ArrayMap 和 HashMap的在記憶體的使用上,更加高效。

ArrayMap實現上有兩個陣列,一個數組是儲存key hash,另一個數組儲存value,ArrayMap通過二分法(binary search)來進行查詢的。

HashMap通過一個數組來實現的,key hash作為陣列的索引,這樣就需要更大的記憶體來減少key hash的衝突,key hash就是陣列的索引,所以查詢效率很高。

使用建議:

  • 當資料量比較小的時候(小於1000),優先使用ArrayMap,否則使用HashMap。

  • map裡巢狀map。

使用方法:

  • 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的使用建議使用方法都是一樣的。

三、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。

四、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方法。

五、避免常見的記憶體洩露

1、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介面(Activity)對TextView還有引用(強引用)。所以只有把A介面關閉了(也就是Activity對TextView的強引用沒有了),且發生GC了 TextView才會被收回。也就是說 當一個物件僅僅被弱引用引用的時候, 發生GC該物件才會被回收

在網上也看到一些,加上了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)方法;

2、內部類(如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記憶體洩露。

3、由static的Activity、View、List導致的記憶體洩露

千萬不要是有static來修飾activity、View物件,這種記憶體洩露更加嚴重,因為它將貫穿程式的生命週期。
為了更好的管理Activity常常把Activity放進容器中,如Stack。如果忘記了移除,也會造成記憶體洩漏。

六、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)

  • 還要注意,做好相關資料恢復的邏輯,例如,把相關的資料清空了,該介面重新顯示的時候,要把相關釋放的資料,重新載入,如果可以的話,儘可能回到使用者上一次操作的狀態,例如滑動的位置,所填寫的資料等。

參考連結

相關推薦

Android 效能優化——記憶體

歡迎轉載,轉載請標明出處【Hoolay Team】: http://www.cnblogs.com/hoolay/p/6278229.html Author : Hoolay Android Team  Chiclaim 一、android官方一

Android 效能優化記憶體

一、android官方一些記憶體方面的記憶體tips 1、避免建立不必要的物件。 如儘量避免字串的加號拼接,可以使用StringBuilder來拼接。 如果需要TextView設定多個字串片段,可以使用textView.append方法,不要直接用加號拼起

Android效能優化--記憶體

看到很多關於記憶體優化的部落格好文,也收藏了好多地址,但每次看時都需要在眾多收藏的地址裡尋找一番,不如寫到自己的部落格裡,方便隨時翻看。 記憶體優化也是android進階的必學內容。APP記憶體的使用,是評價一款應用效能高低的一個重要指標。 1.記憶體與記憶

android效能優化——記憶體洩漏

在專案初期階段或者業務邏輯很簡單的時候對於app效能之一塊沒有太多感覺,但是隨著專案版本的迭代和專案業務邏輯越來越大,越來越複雜的時候,就會逐漸感覺到app效能的重要性,所以在專案初期階段時,就要有app效能這一意識,也便於專案後期的版本迭代和業務擴充套件;這裡所提到的效能優化問題是:記憶體洩漏

Android效能優化——工具

Android效能優化是Android開發中經常遇見的一個問題,接下來將對Android效能優化方面的知識點做一個簡單的梳理和總結,將從工具和程式碼兩方面進行梳理。所謂工欲善其事必先利其器,本文首先來看一下Android效能優化有哪些得力的工具。 1、TraceView

使用 adb 獲取 Android 效能資料--記憶體

記憶體說明 VSS - Virtual Set Size 虛擬耗用記憶體(包含共享庫佔用的記憶體) RSS - Resident Set Size 實際使用實體記憶體(包含共享庫佔用的記憶體) PSS - Proportional Set Size 實際使用的實體記憶

Android 效能優化 ---- 記憶體優化

### 1、Android記憶體管理機制 #### 1.1 Java記憶體分配模型 先上一張JVM將記憶體劃分區域的圖 ![](https://img2020.cnblogs.com/blog/967362/202007/967362-20200717091650518-110380858.png) 程式

Android效能優化記憶體優化--記憶體洩漏

文章目錄 介紹 什麼是記憶體洩露 android中導致記憶體洩漏的主要幾種情況 1.單例模式 2.使用非靜態內部類 3.使用非同步事件處理機制Handler 4.使用靜態

[Android 效能優化系列]記憶體之基礎--Android如何管理記憶體

轉載請標明出處(http://blog.csdn.net/kifile),再次感謝 在接下來的一段時間裡,我會每天翻譯一部分關於效能提升的Android官方文件給大家 下面是本次的正文: ################ 隨機訪問儲存器(Ram) 不管在哪種軟體開發

Android效能優化記憶體

Google近期在Udacity上釋出了Android效能優化的線上課程,分別從渲染,運算與記憶體,電量幾個方面介紹瞭如何去優化效能,這些課程是Google之前在Youtube上釋出的Android效能優化典範專題課程的細化與補充。 下面是記憶體篇章的學習筆記,部分內容與前面的效能優化典範有重合,歡

【朝花夕拾】Android效能優化之(四)Apk打包

        APK,即Android Package,是將android程式和資源整合在一起,形成的一個.apk檔案。相信所有的Android程式設計師是在IDE的幫助下,完成打包輕而易舉,但對打包流程真正清楚的可能並不多。本章的內容比較簡單,也是非常基礎的內容,但是對理解android應用的結構卻有很大

【朝花夕拾】Android效能優化之(一)序言及JVM

序言 筆者從事Anroid開發有些年頭了,深知掌握Anroid效能優化方面的知識的必要性,這是一個程式設計師必須修煉的內功。在面試中,它是面試官的摯愛,在工作中,它是程式碼質量的攔路虎,其重要性可見一斑。在團隊中,效能優化的工作又往往由經驗豐富的老師傅來完成,可見要做好效能優化,絕不是一件容易的事情。    

【朝花夕拾】Android效能優化之(五)Android虛擬機器簡介

前言        Android虛擬機器的使用,使得android應用和Linux核心分離,這樣做使得android系統更穩定可靠,比如程式中即使包含惡意程式碼,也不會直接影響系統檔案;也提高了跨平臺相容性。在Android4.4以前的系統中,Android系統均採用Dalvik作為執行andorid程式的

【朝花夕拾】Android效能優化之(五)Android虛擬機器

前言        Android虛擬機器的使用,使得android應用和Linux核心分離,這樣做使得android系統更穩定可靠,比如程式中即使包含惡意程式碼,也不會直接影響系統檔案;也提高了跨平臺相容性。在Android4.4以前的系統中,Android系統均採用Da

android效能優化實戰前

本文地址:http://blog.csdn.net/iamws/article/details/51629160 前言:         最近因為某專案cpu,記憶體的使用率實在讓人不敢恭維;手機發燙,電量下降已經讓使用者無法忍受;頻繁快速迭代發版導致各種效能問題突出;由

[Android 效能優化系列]佈局之減少你的介面層級

轉載請標明出處(http://blog.csdn.net/kifile),再次感謝 在接下來的一段時間裡,我會每天翻譯一部分關於效能提升的Android官方文件給大家 效能優化之佈局篇: 題外話: 複雜的佈局,既會提高我們的設計難度,也會降低我們的程式碼效

Android 效能優化之String

Android 效能優化之 String篇 關於String相關知識都是老掉牙的東西了,但我們經常可能在不經意的String 字串拼接的情況下浪費記憶體,影響效能,也常常會成為觸發記憶體OOM的最後一步。 所以本文對String字串進行深度解析,有

Android 效能優化——管理應用的記憶體

請保持淡定,分析程式碼,記住:效能很重要。 隨機存取儲存器(RAM)在任何軟體開發環境中都是一個很寶貴的資源。這一點在實體記憶體通常很有限的移動作業系統上,顯得尤為突出。儘管 Android Runtime(ART)和 Dalvik 虛擬機器都扮演了常

Android 效能優化記憶體洩漏檢測以及記憶體優化(中)

Android 記憶體洩漏檢測   通過上篇部落格我們瞭解了 Android JVM/ART 記憶體的相關知識和洩漏的原因,再來歸類一下記憶體洩漏的源頭,這裡我們簡單將其歸為一下三類:自身編碼引起由專案開發人員自身的編碼造成;第三方程式碼引起這裡的第三

Android 效能優化記憶體檢測、卡頓優化、耗電優化、APK瘦身

導語 自2008年智慧時代開始,Android作業系統一路高歌,10年智慧機發展之路,如今 Android 9.0 代號P  都發布了,Android系統性能已經非常流暢了。但是,到了各大廠商手裡,改原始碼自定系統,使得Android原生系統變得魚龍混雜。另外,到了不同層次的