Android造成OOM 和ANR的原因及解決辦法

Android 基礎(chǔ)

1.內(nèi)存泄漏

一個(gè)程序中,已經(jīng)不需要使用某個(gè)對(duì)象,但是仍然有引用指向它,垃圾回收器無(wú)法回收它,該對(duì)象占用的內(nèi)存無(wú)法被回收時(shí),就容易造成內(nèi)存泄漏。

一、單例造成的內(nèi)存泄漏

Android的單例模式非常受開(kāi)發(fā)者的喜愛(ài),不過(guò)使用的不恰當(dāng)?shù)脑捯矔?huì)造成內(nèi)存泄漏。因?yàn)閱卫撵o態(tài)特性使得單例的生命周期和應(yīng)用的生命周期一樣長(zhǎng),這就說(shuō)明了如果一個(gè)對(duì)象已經(jīng)不需要使用了,而單例對(duì)象還持有該對(duì)象的引用,那么這個(gè)對(duì)象將不能被正常回收,這就導(dǎo)致了內(nèi)存泄漏。

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

這是一個(gè)普通的單例模式,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候,由于需要傳入一個(gè)Context,所以這個(gè)Context的生命周期的長(zhǎng)短至關(guān)重要:
1、傳入的是Application的Context:這將沒(méi)有任何問(wèn)題,因?yàn)閱卫纳芷诤虯pplication的一樣長(zhǎng) ;
2、傳入的是Activity的Context:當(dāng)這個(gè)Context所對(duì)應(yīng)的Activity退出時(shí),由于該Context和Activity的生命周期一樣長(zhǎng)(Activity間接繼承于Context),所以當(dāng)前Activity退出時(shí)它的內(nèi)存并不會(huì)被回收,因?yàn)閱卫龑?duì)象持有該Activity的引用。

正確的單例應(yīng)該修改為下面這種方式:

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context.getApplicationContext();
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

這樣不管傳入什么Context最終將使用Application的Context,而單例的生命周期和應(yīng)用的一樣長(zhǎng),這樣就防止了內(nèi)存泄漏。

二、非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏

有的時(shí)候我們可能會(huì)在啟動(dòng)頻繁的Activity中,為了避免重復(fù)創(chuàng)建相同的數(shù)據(jù)資源,會(huì)出現(xiàn)這種寫法:

public class MainActivity extends AppCompatActivity {
   private static TestResource mResource = null;
   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       if(mManager == null){
           mManager = new TestResource();
       }
       //...
   }
   class TestResource {
       //...
   }
}

這樣就在Activity內(nèi)部創(chuàng)建了一個(gè)非靜態(tài)內(nèi)部類的單例,每次啟動(dòng)Activity時(shí)都會(huì)使用該單例的數(shù)據(jù),這樣雖然避免了資源的重復(fù)創(chuàng)建,不過(guò)這種寫法卻會(huì)造成內(nèi)存泄漏,因?yàn)榉庆o態(tài)內(nèi)部類默認(rèn)會(huì)持有外部類的引用,而又使用了該非靜態(tài)內(nèi)部類創(chuàng)建了一個(gè)靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用的一樣長(zhǎng),這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正常回收。正確的做法為:
將該內(nèi)部類設(shè)為靜態(tài)內(nèi)部類或?qū)⒃搩?nèi)部類抽取出來(lái)封裝成一個(gè)單例,如果需要使用Context,請(qǐng)使用ApplicationContext 。

三、Handler造成的內(nèi)存泄漏

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //...
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        loadData();
    }
    private void loadData(){
        //...request
        Message message = Message.obtain();
        mHandler.sendMessage(message);
    }
}

mHandler是Handler的非靜態(tài)匿名內(nèi)部類的實(shí)例,所以它持有外部類Activity的引用,我們知道消息隊(duì)列是在一個(gè)Looper線程中不斷輪詢處理消息,那么當(dāng)這個(gè)Activity退出時(shí)消息隊(duì)列中還有未處理的消息或者正在處理消息,而消息隊(duì)列中的Message持有mHandler實(shí)例的引用,mHandler又持有Activity的引用,所以導(dǎo)致該Activity的內(nèi)存資源無(wú)法及時(shí)回收,引發(fā)內(nèi)存泄漏,所以另外一種做法為:

public class MainActivity extends AppCompatActivity {
   private MyHandler mHandler = new MyHandler(this);
   private TextView mTextView ;
   private static class MyHandler extends Handler {
       private WeakReference<Context> reference;
       public MyHandler(Context context) {
           reference = new WeakReference<>(context);
       }
       @Override
       public void handleMessage(Message msg) {
           MainActivity activity = (MainActivity) reference.get();
           if(activity != null){
               activity.mTextView.setText("");
           }
       }
   }

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       mTextView = (TextView)findViewById(R.id.textview);
       loadData();
   }

   private void loadData() {
       //...request
       Message message = Message.obtain();
       mHandler.sendMessage(message);
   }
}

創(chuàng)建一個(gè)靜態(tài)Handler內(nèi)部類,然后對(duì)Handler持有的對(duì)象使用弱引用,這樣在回收時(shí)也可以回收Handler持有的對(duì)象,這樣雖然避免了Activity泄漏,不過(guò)Looper線程的消息隊(duì)列中還是可能會(huì)有待處理的消息,所以我們?cè)贏ctivity的Destroy時(shí)或者Stop時(shí)應(yīng)該移除消息隊(duì)列中的消息,更準(zhǔn)確的做法如下:

public class MainActivity extends AppCompatActivity {
   private MyHandler mHandler = new MyHandler(this);
   private TextView mTextView ;
   private static class MyHandler extends Handler {
       private WeakReference<Context> reference;
       public MyHandler(Context context) {
           reference = new WeakReference<>(context);
       }
       @Override
       public void handleMessage(Message msg) {
           MainActivity activity = (MainActivity) reference.get();
           if(activity != null){
               activity.mTextView.setText("");
           }
       }
   }

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       mTextView = (TextView)findViewById(R.id.textview);
       loadData();
   }

   private void loadData() {
       //...request
       Message message = Message.obtain();
       mHandler.sendMessage(message);
   }

   @Override
   protected void onDestroy() {
       super.onDestroy();
       mHandler.removeCallbacksAndMessages(null);
   }
}

使用mHandler.removeCallbacksAndMessages(null);是移除消息隊(duì)列中所有消息和所有的Runnable。當(dāng)然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();來(lái)移除指定的Runnable和Message。

四、線程造成的內(nèi)存泄漏

//——————test1
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(10000);
                return null;
            }
        }.execute();
//——————test2
        new Thread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(10000);
            }
        }).start();

上面的異步任務(wù)和Runnable都是一個(gè)匿名內(nèi)部類,因此它們對(duì)當(dāng)前Activity都有一個(gè)隱式引用。如果Activity在銷毀之前,任務(wù)還未完成, 那么將導(dǎo)致Activity的內(nèi)存資源無(wú)法回收,造成內(nèi)存泄漏。正確的做法還是使用靜態(tài)內(nèi)部類的方式,如下:

   static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
       private WeakReference<Context> weakReference;

       public MyAsyncTask(Context context) {
           weakReference = new WeakReference<>(context);
       }

       @Override
       protected Void doInBackground(Void... params) {
           SystemClock.sleep(10000);
           return null;
       }

       @Override
       protected void onPostExecute(Void aVoid) {
           super.onPostExecute(aVoid);
           MainActivity activity = (MainActivity) weakReference.get();
           if (activity != null) {
               //...
           }
       }
   }
   static class MyRunnable implements Runnable{
       @Override
       public void run() {
           SystemClock.sleep(10000);
       }
   }
//——————
   new Thread(new MyRunnable()).start();
   new MyAsyncTask(this).execute();

這樣就避免了Activity的內(nèi)存資源泄漏,當(dāng)然在Activity銷毀時(shí)候也應(yīng)該取消相應(yīng)的任務(wù)AsyncTask::cancel(),避免任務(wù)在后臺(tái)執(zhí)行浪費(fèi)資源。

五、資源未關(guān)閉造成的內(nèi)存泄漏

對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷,否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏。

2.anr (應(yīng)用程序無(wú)響應(yīng)):

1.只有主線程才會(huì)產(chǎn)生ANR,主線程就是UI線程;
2.必須發(fā)生某些輸入事件或特定操作,比如按鍵或觸屏等輸入事件,在BroadcastReceiver或Service的各個(gè)生命周期調(diào)用函數(shù);
3.上述事件響應(yīng)超時(shí),不同的context規(guī)定的上限時(shí)間不同
a.主線程對(duì)輸入事件5秒內(nèi)沒(méi)有處理完畢
b.主線程在執(zhí)行BroadcastReceiver的onReceive()函數(shù)時(shí)10秒內(nèi)沒(méi)有處理完畢
c.主線程在Service的各個(gè)生命周期函數(shù)時(shí)20秒內(nèi)沒(méi)有處理完畢。

ANR的根本原因是什么呢?簡(jiǎn)單的總結(jié)有以下兩點(diǎn):

1.主線程執(zhí)行了耗時(shí)操作,比如數(shù)據(jù)庫(kù)操作或網(wǎng)絡(luò)編程
2.其他進(jìn)程(就是其他程序)占用CPU導(dǎo)致本進(jìn)程得不到CPU時(shí)間片,比如其他進(jìn)程的頻繁讀寫操作可能會(huì)導(dǎo)致這個(gè)問(wèn)題。
細(xì)分的話,導(dǎo)致ANR的原因有如下幾點(diǎn):
1.耗時(shí)的網(wǎng)絡(luò)訪問(wèn)
2.大量的數(shù)據(jù)讀寫
3.數(shù)據(jù)庫(kù)操作
4.硬件操作(比如camera)
5.調(diào)用thread的join()方法、sleep()方法、wait()方法或者等待線程鎖的時(shí)候
6.service binder的數(shù)量達(dá)到上限
7.system server中發(fā)生WatchDog ANR
8.service忙導(dǎo)致超時(shí)無(wú)響應(yīng)
9.其他線程持有鎖,導(dǎo)致主線程等待超時(shí)
10.其它線程終止或崩潰導(dǎo)致主線程一直等待

那么如何避免ANR的發(fā)生呢或者說(shuō)ANR的解決辦法是什么呢?

1.避免在主線程執(zhí)行耗時(shí)操作,所有耗時(shí)操作應(yīng)新開(kāi)一個(gè)子線程完成,然后再在主線程更新UI。
2.BroadcastReceiver要執(zhí)行耗時(shí)操作時(shí)應(yīng)啟動(dòng)一個(gè)service,將耗時(shí)操作交給service來(lái)完成。
3.避免在Intent Receiver里啟動(dòng)一個(gè)Activity,因?yàn)樗鼤?huì)創(chuàng)建一個(gè)新的畫面,并從當(dāng)前用戶正在運(yùn)行的程序上搶奪焦點(diǎn)。如果你的應(yīng)用程序在響應(yīng)Intent廣 播時(shí)需要向用戶展示什么,你應(yīng)該使用Notification Manager來(lái)實(shí)現(xiàn)。
導(dǎo)出anr日志命令行:

adb shell 
cat  /data/anr/traces.txt   >/mnt/sdcard/traces.txt   
exit

3.內(nèi)存溢出:

當(dāng)對(duì)象的內(nèi)存占用已經(jīng)超出分配內(nèi)存的空間大小,這時(shí)未經(jīng)處理的異常就會(huì)拋出。比如常見(jiàn)的內(nèi)存溢出情況有:bitmap過(guò)大;引用沒(méi)釋放;資源對(duì)象沒(méi)關(guān)閉
內(nèi)存溢出的原因
1、內(nèi)存泄露導(dǎo)致
由于我們程序的失誤,長(zhǎng)期保持某些資源(如Context)的引用,垃圾回收器就無(wú)法回收它,當(dāng)然該對(duì)象占用的內(nèi)存就無(wú)法被使用,這就造成內(nèi)存泄露。
Android 中常見(jiàn)就是Activity被引用在調(diào)用finish之后卻沒(méi)有釋放,第二次打開(kāi)activity又重新創(chuàng)建,這樣的內(nèi)存泄露不斷的發(fā)生,則會(huì)導(dǎo)致內(nèi)存的溢出。
Android的每個(gè)應(yīng)用程序都會(huì)使用一個(gè)專有的Dalvik虛擬機(jī)實(shí)例來(lái)運(yùn)行,它是由Zygote服務(wù)進(jìn)程孵化出來(lái)的,也就是說(shuō)每個(gè)應(yīng)用程序都是在屬于自己的進(jìn)程中運(yùn)行的。Android為不同類型的進(jìn)程分配了不同的內(nèi)存使用上限,如果程序在運(yùn)行過(guò)程中出現(xiàn)了內(nèi)存泄漏的而造成應(yīng)用進(jìn)程使用的內(nèi)存超過(guò)了這個(gè)上限,則會(huì)被系統(tǒng)視為內(nèi)存泄漏,從而被kill掉,這使得僅僅自己的進(jìn)程被kill掉,而不會(huì)影響其他進(jìn)程.
2、占用內(nèi)存較多的對(duì)象
保存了多個(gè)耗用內(nèi)存過(guò)大的對(duì)象(如Bitmap)或加載單個(gè)超大的圖片,造成內(nèi)存超出限制。
內(nèi)存泄漏(memory leak)

有些對(duì)象只有有限的生命周期。當(dāng)它們的任務(wù)完成之后,它們將被垃圾回收。如果在對(duì)象的生命周期本該結(jié)束的時(shí)候,這個(gè)對(duì)象還被一系列的引用,這就會(huì)導(dǎo)致內(nèi)存泄漏。隨著泄漏的累積,app將消耗完內(nèi)存。
比如,在Activity.onDestroy()被調(diào)用之后,view樹(shù)以及相關(guān)的bitmap都應(yīng)該被垃圾回收。如果一個(gè)正在運(yùn)行的后臺(tái)線程繼續(xù)持有這個(gè)Activity的引用,那么相關(guān)的內(nèi)存將不會(huì)被回收,這最終將導(dǎo)致OutOfMemoryError崩潰。
memory leak會(huì)最終會(huì)導(dǎo)致out of memory!

4.內(nèi)存溢出和內(nèi)存泄漏的區(qū)別

內(nèi)存溢出 out of memory,是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory;比如申請(qǐng)了一個(gè)integer,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出。
內(nèi)存泄露 memory leak,是指程序在申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的內(nèi)存空間,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很嚴(yán)重,無(wú)論多少內(nèi)存,遲早會(huì)被占光。

5.Android學(xué)習(xí)之 內(nèi)存管理機(jī)制與應(yīng)用內(nèi)存優(yōu)化:

Random Access Memory(RAM)在任何軟件開(kāi)發(fā)環(huán)境中都是一個(gè)很寶貴的資源。這一點(diǎn)在物理內(nèi)存通常很有限的移動(dòng)操作系統(tǒng)上,顯得尤為突出。盡管Android的Dalvik虛擬機(jī)扮演了常規(guī)的垃圾回收的角色,但這并不意味著你可以忽視app的內(nèi)存分配與釋放的時(shí)機(jī)與地點(diǎn)。于大多數(shù)apps來(lái)說(shuō),Dalvik的GC會(huì)自動(dòng)把離開(kāi)活動(dòng)線程的對(duì)象進(jìn)行回收。

一、Android系統(tǒng)是如何管理內(nèi)存的

Android并沒(méi)有提供內(nèi)存的交換區(qū)(Swap space),但是它有使用paging(內(nèi)存分頁(yè))與memory-mapping(mmapping)的機(jī)制來(lái)管理內(nèi)存。這意味著任何你修改的內(nèi)存(無(wú)論是通過(guò)分配新的對(duì)象還是訪問(wèn)到mmaped pages的內(nèi)容)都會(huì)貯存在RAM中,而且不能被paged out。因此唯一完整釋放內(nèi)存的方法是釋放那些你可能hold住的對(duì)象的引用,這樣使得它能夠被GC回收。只有一種例外是:如果系統(tǒng)想要在其他地方reuse這個(gè)對(duì)象。

    1. 內(nèi)存共享
      Android通過(guò)下面幾個(gè)方式在不同的Process中來(lái)共享RAM:
      1.每一個(gè)app的process都是從同一個(gè)被叫做Zygote的進(jìn)程中fork出來(lái)的。Zygote進(jìn)程在系統(tǒng)啟動(dòng)并且載入通用的framework的代碼與資源之后開(kāi)始啟動(dòng)。為了啟動(dòng)一個(gè)新的程序進(jìn)程,系統(tǒng)會(huì)fork Zygote進(jìn)程生成一個(gè)新的process,然后在新的process中加載并運(yùn)行app的代碼。這使得大多數(shù)的RAM pages被用來(lái)分配給framework的代碼與資源,并在應(yīng)用的所有進(jìn)程中進(jìn)行共享。
      2.大多數(shù)static的數(shù)據(jù)被mmapped到一個(gè)進(jìn)程中。這不僅僅使得同樣的數(shù)據(jù)能夠在進(jìn)程間進(jìn)行共享,而且使得它能夠在需要的時(shí)候被paged out。例如下面幾種static的數(shù)據(jù):
      ① Dalvik code (by placing it in a pre-linked .odex file for direct mmapping
      ② App resources (by designing the resource table to be a structure that can be mmapped and by aligning the zip entries of the APK)
      ③ Traditional project elements like native code in .so files.
      3.在許多地方,Android通過(guò)顯式的分配共享內(nèi)存區(qū)域(例如ashmem或者gralloc)來(lái)實(shí)現(xiàn)一些動(dòng)態(tài)RAM區(qū)域的能夠在不同進(jìn)程間的共享。例如,window surfaces在app與screen compositor之間使用共享的內(nèi)存,cursor buffers在content provider與client之間使用共享的內(nèi)存。
    1. 分配與回收內(nèi)存
      這里有下面幾點(diǎn)關(guān)于Android如何分配與回收內(nèi)存的事實(shí):
      1.每一個(gè)進(jìn)程的Dalvik heap都有一個(gè)限制的虛擬內(nèi)存范圍。這就是邏輯上講的heap size,它可以隨著需要進(jìn)行增長(zhǎng),但是會(huì)有一個(gè)系統(tǒng)為它所定義的上限。
      2.邏輯上講的heap size和實(shí)際物理上使用的內(nèi)存數(shù)量是不等的,Android會(huì)計(jì)算一個(gè)叫做Proportional Set Size(PSS)的值,它記錄了那些和其他進(jìn)程進(jìn)行共享的內(nèi)存大小。(假設(shè)共享內(nèi)存大小是10M,一共有20個(gè)Process在共享使用,根據(jù)權(quán)重,可能認(rèn)為其中有0.3M才能真正算是你的進(jìn)程所使用的)
      3.Dalvik heap與邏輯上的heap size不吻合,這意味著Android并不會(huì)去做heap中的碎片整理用來(lái)關(guān)閉空閑區(qū)域。Android僅僅會(huì)在heap的尾端出現(xiàn)不使用的空間時(shí)才會(huì)做收縮邏輯heap size大小的動(dòng)作。但是這并不是意味著被heap所使用的物理內(nèi)存大小不能被收縮。在垃圾回收之后,Dalvik會(huì)遍歷heap并找出不使用的pages,然后使用madvise把那些pages返回給kernal。因此,成對(duì)的allocations與deallocations大塊的數(shù)據(jù)可以使得物理內(nèi)存能夠被正常的回收。然而,回收碎片化的內(nèi)存則會(huì)使得效率低下很多,因?yàn)槟切┧槠姆峙漤?yè)面也許會(huì)被其他地方所共享到。
    1. 限制應(yīng)用的內(nèi)存
      為了維持多任務(wù)的功能環(huán)境,Android為每一個(gè)app都設(shè)置了一個(gè)硬性的heap size限制。準(zhǔn)確的heap size限制隨著不同設(shè)備的不同RAM大小而各有差異。如果你的app已經(jīng)到了heap的限制大小并且再嘗試分配內(nèi)存的話,會(huì)引起OutOfMemoryError的錯(cuò)誤。

在一些情況下,你也許想要查詢當(dāng)前設(shè)備的heap size限制大小是多少,然后決定cache的大小。可以通過(guò)getMemoryClass()來(lái)查詢。這個(gè)方法會(huì)返回一個(gè)整數(shù),表明你的app heap size限制是多少megabates。

    1. 切換應(yīng)用
      Android并不會(huì)在用戶切換不同應(yīng)用時(shí)候做交換內(nèi)存的操作。Android會(huì)把那些不包含foreground組件的進(jìn)程放到LRU cache中。例如,當(dāng)用戶剛開(kāi)始啟動(dòng)了一個(gè)應(yīng)用,這個(gè)時(shí)候?yàn)樗鼊?chuàng)建了一個(gè)進(jìn)程,但是當(dāng)用戶離開(kāi)這個(gè)應(yīng)用,這個(gè)進(jìn)程并沒(méi)有離開(kāi)。系統(tǒng)會(huì)把這個(gè)進(jìn)程放到cache中,如果用戶后來(lái)回到這個(gè)應(yīng)用,這個(gè)進(jìn)程能夠被resued,從而實(shí)現(xiàn)app的快速切換。

如果你的應(yīng)用有一個(gè)當(dāng)前并不需要使用到的被緩存的進(jìn)程,它被保留在內(nèi)存中,這會(huì)對(duì)系統(tǒng)的整個(gè)性能有影響。因此當(dāng)系統(tǒng)開(kāi)始進(jìn)入低內(nèi)存狀態(tài)時(shí),它會(huì)由系統(tǒng)根據(jù)LRU的規(guī)則與其他因素選擇殺掉某些進(jìn)程。

二、該如何管理/優(yōu)化你的應(yīng)用內(nèi)存

你應(yīng)該在開(kāi)發(fā)過(guò)程的每一個(gè)階段都考慮到RAM的有限性,甚至包括在開(kāi)發(fā)開(kāi)始之前的設(shè)計(jì)階段。有許多種設(shè)計(jì)與實(shí)現(xiàn)方式,他們有著不同的效率,盡管是對(duì)同樣一種技術(shù)的不斷組合與演變。

為了使得你的應(yīng)用效率更高,你應(yīng)該在設(shè)計(jì)與實(shí)現(xiàn)代碼時(shí),遵循下面的技術(shù)要點(diǎn):

*1、珍惜Services資源
如果你的app需要在后臺(tái)使用service,除非它被觸發(fā)執(zhí)行一個(gè)任務(wù),否則其他時(shí)候都應(yīng)該是非運(yùn)行狀態(tài)。同樣需要注意當(dāng)這個(gè)service已經(jīng)完成任務(wù)后停止service失敗引起的泄漏。
你啟動(dòng)一個(gè)service,系統(tǒng)會(huì)傾向?yàn)榱诉@個(gè)Service而一直保留它的Process。這使得process的運(yùn)行代價(jià)很高,因?yàn)橄到y(tǒng)沒(méi)有辦法把Service所占用的RAM讓給其他組件或者被Paged out。這減少了系統(tǒng)能夠存放到LRU緩存當(dāng)中的process數(shù)量,它會(huì)影響app之間的切換效率。它甚至?xí)?dǎo)致系統(tǒng)內(nèi)存使用不穩(wěn)定,從而無(wú)法繼續(xù)Hold住 所有目前正在運(yùn)行的Service。
限制你的service的最好辦法是使用IntentService, 它會(huì)在處理完扔給它的intent任務(wù)之后盡快結(jié)束自己。

*2、當(dāng)你的應(yīng)用UI隱藏時(shí) 釋放內(nèi)存
當(dāng)用戶切換到其它app且你的app UI不再可見(jiàn)時(shí),你應(yīng)該釋放應(yīng)用視圖所占的資源。在這個(gè)時(shí)候釋放UI資源可以顯著的增加系統(tǒng)cached process的能力,它會(huì)對(duì)用戶的質(zhì)量體驗(yàn)有著直接的影響。
為了能夠接收到用戶離開(kāi)你的UI時(shí)的通知,你需要實(shí)現(xiàn)Activtiy類里面的onTrimMemory()回調(diào)方法。你應(yīng)該使用這個(gè)方法來(lái)監(jiān)聽(tīng)到TRIM_MEMORY_UI_HIDDEN級(jí)別, 它意味著你的UI已經(jīng)隱藏,你應(yīng)該釋放那些僅僅被你的UI使用的資源。請(qǐng)注意:你的app僅僅會(huì)在所有UI組件的被隱藏的時(shí)候接收到onTrimMemory()的回調(diào)并帶有參數(shù)TRIM_MEMORY_UI_HIDDEN。這與onStop()的回調(diào)是不同的,onStop會(huì)在activity的實(shí)例隱藏時(shí)會(huì)執(zhí)行,例如當(dāng)用戶從你的app的某個(gè)activity跳轉(zhuǎn)到另外一個(gè)activity時(shí)onStop會(huì)被執(zhí)行。因此你應(yīng)該實(shí)現(xiàn)onStop回調(diào),所以說(shuō)你雖然實(shí)現(xiàn)了onStop()去釋放 activity 的資源例如網(wǎng)絡(luò)連接或者未注冊(cè)的廣播接收者, 但是應(yīng)該直到你收到 onTrimMemory(TRIM_MEMORY_UI_HIDDEN)才去釋放視圖資源否則不應(yīng)該釋放視圖所占用的資源。這確保了用戶從其他activity切回來(lái)時(shí),你的UI資源仍然可用,并且可以迅速恢復(fù)activity。

*3、當(dāng)內(nèi)存緊張時(shí) 釋放部分內(nèi)存
在你的app生命周期的任何階段,onTrimMemory回調(diào)方法同樣可以告訴你設(shè)備內(nèi)存資源緊張。根據(jù)onTrimMemory方法的內(nèi)存級(jí)別來(lái)進(jìn)一步?jīng)Q定釋放哪些資源。
① TRIM_MEMORY_RUNNING_MODERATE:你的app正在運(yùn)行并且不會(huì)被列為可殺死的。但是設(shè)備正運(yùn)行于低內(nèi)存狀態(tài)下,系統(tǒng)開(kāi)始激活殺死LRU Cache中的Process的機(jī)制。
② TRIM_MEMORY_RUNNING_LOW:你的app正在運(yùn)行且沒(méi)有被列為可殺死的。但是設(shè)備正運(yùn)行于更低內(nèi)存的狀態(tài)下,你應(yīng)該釋放不用的資源用來(lái)提升系統(tǒng)性能,這會(huì)直接影響了你的app的性能。
③ TRIM_MEMORY_RUNNING_CRITICAL:你的app仍在運(yùn)行,但是系統(tǒng)已經(jīng)把LRU Cache中的大多數(shù)進(jìn)程都已經(jīng)殺死,因此你應(yīng)該立即釋放所有非必須的資源。如果系統(tǒng)不能回收到足夠的RAM數(shù)量,系統(tǒng)將會(huì)清除所有的LRU緩存中的進(jìn)程,并且開(kāi)始?xì)⑺滥切┲氨徽J(rèn)為不應(yīng)該殺死的進(jìn)程,例如那個(gè)進(jìn)程包含了一個(gè)運(yùn)行中的Service。

*4、檢查你應(yīng)該使用多少內(nèi)存
通過(guò)調(diào)用getMemoryClass()來(lái)獲取你的app的可用heap大小。
在一些特殊的情景下,你可以通過(guò)在manifest的application標(biāo)簽下添加largeHeap=true的屬性來(lái)聲明一個(gè)更大的heap空間。如果你這樣做,你可以通過(guò)getLargeMemoryClass())來(lái)獲取到一個(gè)更大的heap size。

然而,能夠獲取更大heap的設(shè)計(jì)本意是為了一小部分會(huì)消耗大量RAM的應(yīng)用(例如一個(gè)大圖片的編輯應(yīng)用)。不要輕易的因?yàn)槟阈枰褂么罅康膬?nèi)存而去請(qǐng)求一個(gè)大的heap size。只有當(dāng)你清楚的知道哪里會(huì)使用大量的內(nèi)存并且為什么這些內(nèi)存必須被保留時(shí)才去使用large heap. 因此請(qǐng)盡量少使用large heap。使用額外的內(nèi)存會(huì)影響系統(tǒng)整體的用戶體驗(yàn),并且會(huì)使得GC的每次運(yùn)行時(shí)間更長(zhǎng)。在任務(wù)切換時(shí),系統(tǒng)的性能會(huì)變得大打折扣。

*5、避免Bitmap的浪費(fèi)
當(dāng)你加載一個(gè)bitmap時(shí),僅僅需要保留適配當(dāng)前屏幕設(shè)備分辨率的數(shù)據(jù)即可,如果原圖高于你的設(shè)備分辨率,需要做縮小的動(dòng)作。請(qǐng)記住,增加bitmap的尺寸會(huì)對(duì)內(nèi)存呈現(xiàn)出2次方的增加,因?yàn)閄與Y都在增加。
使用完Bitmap后 確定不會(huì)再使用 則需要注意Bitmap的內(nèi)存回收(recycle())處理。

*6、使用優(yōu)化的數(shù)據(jù)容器
利用Android Framework里面優(yōu)化過(guò)的容器類,例如SparseArray,SparseBooleanArray, 與LongSparseArray. 通常的HashMap的實(shí)現(xiàn)方式更加消耗內(nèi)存,因?yàn)樗枰粋€(gè)額外的實(shí)例對(duì)象來(lái)記錄Mapping操作。另外,SparseArray更加高效在于他們避免了對(duì)key與value的autobox自動(dòng)裝箱,并且避免了裝箱后的解箱。

*7、請(qǐng)注意內(nèi)存開(kāi)銷(資源的開(kāi)啟關(guān)閉 如數(shù)據(jù)庫(kù)查詢過(guò)程游標(biāo)的關(guān)閉、IO流的關(guān)閉、線程池的使用)

*8、請(qǐng)注意代碼編寫優(yōu)化 (Java代碼的性能優(yōu)化)

*9、Avoid dependency injection frameworks
使用類似Guice或者RoboGuice等f(wàn)ramework injection包是很有效的,因?yàn)樗麄兡軌蚝?jiǎn)化你的代碼。然而,那些框架會(huì)通過(guò)掃描你的代碼執(zhí)行許多初始化的操作,這會(huì)導(dǎo)致你的代碼需要大量的RAM來(lái)map代碼。但是mapped pages會(huì)長(zhǎng)時(shí)間的被保留在RAM中。

*10、謹(jǐn)慎使用external libraries
很多External library的代碼都不是為移動(dòng)網(wǎng)絡(luò)環(huán)境而編寫的,在移動(dòng)客戶端則顯示的效率不高。至少,當(dāng)你決定使用一個(gè)external library的時(shí)候,你應(yīng)該針對(duì)移動(dòng)網(wǎng)絡(luò)做繁瑣的porting與maintenance的工作。

*11、優(yōu)化整體性能
官方有列出許多優(yōu)化整個(gè)app性能的文章:Best Practices for Performance. 這篇文章就是其中之一。有些文章是講解如何優(yōu)化app的CPU使用效率,有些是如何優(yōu)化app的內(nèi)存使用效率。
其他 如 layout布局優(yōu)化。同樣還應(yīng)該關(guān)注lint工具所提出的建議,進(jìn)行優(yōu)化。

*12、使用ProGuard來(lái)剔除不需要的代碼
ProGuard能夠通過(guò)移除不需要的代碼,重命名類,域與方法等方對(duì)代碼進(jìn)行壓縮,優(yōu)化與混淆。使用ProGuard可以是的你的代碼更加緊湊,這樣能夠使用更少mapped代碼所需要的RAM。

*13、對(duì)最終的APK使用zipalign
在編寫完所有代碼,并通過(guò)編譯系統(tǒng)生成APK之后,你需要使用zipalign對(duì)APK進(jìn)行重新校準(zhǔn)。如果你不做這個(gè)步驟,會(huì)導(dǎo)致你的APK需要更多的RAM,因?yàn)橐恍╊愃茍D片資源的東西不能被mapped。

*14、分析你的RAM使用情況
一旦你獲取到一個(gè)相對(duì)穩(wěn)定的版本后,需要分析你的app整個(gè)生命周期內(nèi)使用的內(nèi)存情況,并進(jìn)行優(yōu)化,更多細(xì)節(jié)請(qǐng)參考Investigating Your RAM Usage.

*15、使用多進(jìn)程(注意是多進(jìn)程,別看成多線程了啊!!!)
通過(guò)把你的app組件切分成多個(gè)組件,運(yùn)行在不同的進(jìn)程中。這個(gè)技術(shù)必須謹(jǐn)慎使用,大多數(shù)app都不應(yīng)該運(yùn)行在多個(gè)進(jìn)程中。因?yàn)槿绻褂貌划?dāng),它會(huì)顯著增加內(nèi)存的使用,而不是減少。當(dāng)你的app需要在后臺(tái)運(yùn)行與前臺(tái)一樣的大量的任務(wù)的時(shí)候,可以考慮使用這個(gè)技術(shù)。一個(gè)典型的例子是創(chuàng)建可以長(zhǎng)時(shí)間后臺(tái)播放的Music Player。如果整個(gè)app運(yùn)行在一個(gè)進(jìn)程中,當(dāng)后臺(tái)播放的時(shí)候,前臺(tái)的那些UI資源也沒(méi)有辦法得到釋放。類似這樣的app可以切分成2個(gè)進(jìn)程:一個(gè)用來(lái)操作UI,另外一個(gè)用來(lái)后臺(tái)的Service.在各個(gè)應(yīng)用的 manifest 文件中為各個(gè)組件申明 android:process 屬性就可以分隔為不同的進(jìn)程.例如你可以指定你一運(yùn)行的服務(wù)從主進(jìn)程中分隔成一個(gè)新的進(jìn)程來(lái)并取名為"background"(當(dāng)然名字可以任意取)
<service android:name=".PlaybackService"
android:process=":background" />
進(jìn)程名字必須以冒號(hào)開(kāi)頭":"以確保該進(jìn)程屬于你應(yīng)用中的私有進(jìn)程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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