【Android】 Android 內存優化

原文地址:http://blog.csdn.net/feng8888bbb/article/details/70161455



 我們知道,Dalvik虛擬機實則也算是一個Java虛擬機,只不過它執行的不是class文件,而是dex文件。雖然Android 4.4發佈了一個ART運行時,準備用來替換掉之前一直使用的Dalvik虛擬機,希望籍此解決飽受詬病的性能問題。但是這裏我們先通過羅昇陽的Dalvik虛擬機簡要介紹和學習計劃瞭解Dalvik虛擬機的內存管理與垃圾回收,對我們分析android的內存優化會有很多的啓示的。

       

      內存泄漏

        1.內存泄漏的原因:垃圾回收器無法回收原本應該被回收的對象,這個對象就引發了內存泄露。

        2.內存泄露的危害:
          過多的內存泄露最終會導致內存溢出(OOM)。
          內存泄露導致可用內存不足,會頻繁觸發GC,不管是Android2.2以前的單線程GC還是現在的CMS和G1,都有一部分的操作會導致用戶線程停止(就是所謂的Stop the world),從而導致UI卡頓。
     
         

       內存溢出

         Android爲每個進程設置Dalvik Heap Size閾值,這個閾值在不同的設備上會因爲RAM大小不同而各有差異。如果APP想要分配的內存超過這個閾值,就會發生OOM。
         
         

       避免內存泄漏

         1.Bitmap的處理
         <1> Bitmap壓縮
         <2> Lru機制處理Bitmap,也可以使用那些有名的圖片緩存框架
         2.持有Context的引用導致的內存泄漏
            在Android應用程序中通常可以使用兩種Context對象:Activity和Application。當類或方法需要Context對象的時候常見的做法是使用第一個作爲Context參數。這樣就意味着對整個activity保持引用。
          示例一(LeakCanary的示例)
           
  1. public class HomeActivity extends AppCompatActivity {  
  2.     @Override  
  3.     protected void onCreate(Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         setContentView(R.layout.act_home);  
  6.   
  7.         findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {  
  8.             @Override  
  9.             public void onClick(View view) {  
  10.                startAsyncTask();  
  11.             }  
  12.         });  
  13.     }  
  14.   
  15.     /*關於隱式引用----內部類可以直接去調用外部類的成員(屬性和方法), 
  16.     如果沒有持有外部類的引用,內部類是沒辦法去調用外部類的成員, 
  17.     但是內部類又沒有顯示的去指定聲明引用,所以稱之爲隱式引用。*/  
  18.   
  19.     /*AsyncTask是一個匿名的內部類,隱式的持有外部類(MainActivity)的引用, 
  20.     當activity被銷燬的時候,如果AsyncTask(代碼sleep 20秒,模擬了一個耗時操作) 
  21.     沒有執行完成,則MainActivity將會泄漏*/  
  22.     void startAsyncTask() {  
  23.         // This async task is an anonymous class and therefore has a hidden reference to the outer  
  24.         // class MainActivity. If the activity gets destroyed before the task finishes (e.g. rotation),  
  25.         // the activity instance will leak.  
  26.         new AsyncTask<Void, Void, Void>() {  
  27.             @Override  
  28.             protected Void doInBackground(Void... params) {  
  29.                 // Do some slow work in background  
  30.                 SystemClock.sleep(20000);  
  31.                 return null;  
  32.             }  
  33.         }.execute();  
  34.     }  
  35.   
  36.    }  
       示例二
    
  1. package com.example.guoliuya.memorytest;  
  2. import android.content.Context;  
  3.   
  4. /** 
  5.  * Created by idea on 2016/12/30. 
  6.  * 單例模式持有context 對象引發內存泄漏示例 
  7.  */  
  8.     public class MyInstanceTest {  
  9.         private Context context;  
  10.         private static MyInstanceTest mInstance;  
  11.   
  12.         public static MyInstanceTest getInstance(Context context) {  
  13.             if (mInstance == null) {  
  14.                 synchronized (MyInstanceTest.class) {  
  15.                     if (mInstance == null)  
  16.                         mInstance = new MyInstanceTest(context);   
  17.                   //解決方法  把context的引用替換成ApplicationContext的引用  
  18.                   mInstance = new MyInstanceTest(context.getApplicationContext());  
  19.                 }  
  20.             }  
  21.             return mInstance;  
  22.         }  
  23.   
  24.         private MyInstanceTest(Context context) {  
  25.             this.context = context;  
  26.         }  
  27.   
  28.     }  
         示例三
 
  1. public class HomeActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.act_home);  
  7.         test();  
  8.   
  9.     }  
  10.     //加上static,變成靜態匿名內部類  
  11.     public static void test() {  
  12.         //匿名內部類會引用其外圍實例HomeActivity.this,所以會導致內存泄漏  
  13.         new Thread(new Runnable() {  
  14.   
  15.             @Override  
  16.             public void run() {  
  17.                 while (true) {  
  18.                     try {  
  19.                         Thread.sleep(10000);  
  20.                     } catch (InterruptedException e) {  
  21.                         e.printStackTrace();  
  22.                     }  
  23.                 }  
  24.             }  
  25.         }).start();  
  26.     }  
  27.   
  28.   
  29. //解決方法  
  30. public class HomeActivity extends AppCompatActivity {  
  31.   
  32.     @Override  
  33.     protected void onCreate(Bundle savedInstanceState) {  
  34.         super.onCreate(savedInstanceState);  
  35.         setContentView(R.layout.act_home);  
  36.         test();  
  37.   
  38.     }  
  39.     //加上static,變成靜態匿名內部類  
  40.     public static void test() {  
  41.         //匿名內部類會引用其外圍實例HomeActivity.this,所以會導致內存泄漏  
  42.         new Thread(new Runnable() {  
  43.   
  44.             @Override  
  45.             public void run() {  
  46.                 while (true) {  
  47.                     try {  
  48.                         Thread.sleep(10000);  
  49.                     } catch (InterruptedException e) {  
  50.                         e.printStackTrace();  
  51.                     }  
  52.                 }  
  53.             }  
  54.         }).start();  
  55.     }  
  56.   
  57. }  
        示例四
  1. public class HomeActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.act_home);  
  7.   
  8.         findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {  
  9.             @Override  
  10.             public void onClick(View view) {  
  11.                 loadData();  
  12.             }  
  13.         });  
  14.   
  15.     }  
  16.     private Handler mHandler = new Handler() {  
  17.         public void handleMessage(android.os.Message msg) {  
  18.             switch (msg.what) {  
  19.                 case 0:  
  20.                     // 刷新數據  
  21.   
  22.                     break;  
  23.                 default:  
  24.                     break;  
  25.             }  
  26.   
  27.         };  
  28.     };  
  29.   
  30.     private void loadData() {  
  31.         //獲取數據  
  32.         mHandler.sendEmptyMessage(0);  
  33.     }  
  34. }  
  35.   
  36. //解決方法  
  37.   
  38. //第一步,將Handler改成靜態內部類。  
  39.     private static class MyHandler extends Handler {  
  40.         //第二步,將需要引用Activity的地方,改成弱引用。  
  41.         private WeakReference<HomeActivity> homaActivityInstance;  
  42.         public MyHandler(HomeActivity hai) {  
  43.             this.homaActivityInstance = new WeakReference<HomeActivity>(hai);  
  44.         }  
  45.   
  46.         @Override  
  47.         public void handleMessage(Message msg) {  
  48.             super.handleMessage(msg);  
  49.             HomeActivity aty = homaActivityInstance == null ? null : homaActivityInstance.get();  
  50.             //如果Activity被釋放回收了,則不處理這些消息  
  51.             if (aty == null||aty.isFinishing()) {  
  52.                 return;  
  53.             }  
  54.   
  55.         }  
  56.     }  
  57.   
  58.     private void loadData() {  
  59.         // 獲取數據  
  60.         myHandler.sendEmptyMessage(0);  
  61.     }  
  62.   
  63.     @Override  
  64.     protected void onDestroy() {  
  65.         //第三步,在Activity退出的時候移除回調  
  66.         super.onDestroy();  
  67.         myHandler.removeCallbacksAndMessages(null);  
  68.     }  

        總結:
        1 避免context泄露:
        在Android中其生命週期是在進程啓動時開始,進程死亡時結束。所以在程序的運行期間,如果進程沒有被殺死,靜態變量就會一直存在,不會被回收掉。如果靜態變量強引用了某個Activity中變量,那麼這個Activity就同樣也不會被釋放,即便是該Activity執行了onDestroy(不要將執行onDestroy和被回收劃等號)。這類問題的解決方案爲:1.尋找與該靜態變量生命週期差不多的替代對象。2.若找不到,將強引用方式改成弱引用。比較典型的例子如下:在java中,創建一個非靜態的內部類實例,就會引用它的外圍實例。如果這個非靜態內部類實例做了一些耗時的操作,就會造成外圍對象不會被回收,從而導致內存泄漏。這類問題的解決方案爲:1.將內部類變成靜態內部類 2.如果有強引用Activity中的屬性,則將該屬性的引用方式改爲弱引用。3.在業務允許的情況下,當Activity執行onDestory時,結束這些耗時任務。

       2.Cursor遊標結果集,I/O流,數據庫,網絡的連接用完及時關閉。
       資源性對象比如(Cursor,File文件等)往往都用了一些緩衝,我們在不使用的時候,應該及時關閉它們,以便它們的緩衝及時回收內存。它們的緩衝不僅存在於 java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置爲null,而不關閉它們,往往會造成內存泄漏。因爲有些資源性對象,比如 SQLiteCursor(在析構函數finalize(),如果我們沒有關閉它,它自己會調close()關閉),如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。因此對於資源性對象在不使用的時候,應該調用它的close()函數,將其關閉掉,然後才置爲null.在我們的程序退出時一定要確保我們的資源性對象已經關閉。

       3.在Android程序裏面存在很多需要register 和 unregister的監聽器,我們需要確保及時unregister監聽器

       4.使用ArrayMap/SparseArray來代替HashMap,ArrayMap/SparseArray是專門爲移動設備設計的高效的數據結構

       5.不要輕易使用Enum
          這點在Google的Android官方培訓課程提到過,具體可以參考胡凱前輩的《 Android性能優化典範(三)》

       6.避免創建不必要的對象
          最好能重用對象而不是在每次需要的時候就創建一個相同功能的新對象

          String s = new String("hello world"); //don't do this!

          使用單例模式

          當心無意識的自動裝箱

  • public static void main(String[] args) {
          Long sum = 0L;
          for (long i = 0; i < Integer.MAX_VALUE; i++) {
              sum += i;
          }
          System.out.println(sum);
    }

     7.資源文件需要選擇合適的文件夾進行存放
     hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設備上會經過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那麼根據換算關係,xxhdpi的手機去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,內存佔用是會顯著提高的。對於不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。

     8.謹慎使用static對象
     static對象的生命週期過長,應該謹慎使用

     9.不要使用String進行字符串拼接
     嚴格的講,String拼接只能歸結到內存抖動中,因爲產生的String副本能夠被GC,不會造成內存泄露。
頻繁的字符串拼接,使用StringBuffer(不建議使用)或者StringBuilder代替String,可以在一定程度上避免OOM和內存抖動。

     10.非靜態內部類內存泄露
     在Activity中創建非靜態內部類,非靜態內部類會持有Activity的隱式引用,若內部類生命週期長於Activity,會導致Activity實例無法被回收。(屏幕旋轉後會重新創建Activity實例,如果內部類持有引用,將會導致旋轉前的實例無法被回收)。解決方案:如果一定要使用內部類,就改用static內部類,在內部類中通過WeakReference的方式引用外界資源.

正確的代碼示例:

  1. static class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {  
  2.   
  3.         private String url;  
  4.         private WeakReference<PhotoAdapter> photoAdapter;  
  5.   
  6.         public ImageDownloadTask(PhotoAdapter photoAdapter) {  
  7.             this.photoAdapter = new WeakReference<PhotoAdapter>(photoAdapter);  
  8.         }  
  9.   
  10.         @Override  
  11.         protected Bitmap doInBackground(String... params) {  
  12.             //在後臺開始下載圖片  
  13.             url = params[0];  
  14.             Bitmap bitmap = photoAdapter.get().loadBitmap(url);  
  15.             if (bitmap != null) {  
  16.                 //把下載好的圖片放入LruCache中  
  17.                 String key = MD5Tools.decodeString(url);  
  18.                 photoAdapter.get().put(key, bitmap);  
  19.             }  
  20.             return bitmap;  
  21.         }  
  22.   
  23.         @Override  
  24.         protected void onPostExecute(Bitmap bitmap) {  
  25.             super.onPostExecute(bitmap);  
  26.             //把下載好的圖片顯示出來  
  27.             ImageView mImageView = (ImageView) photoAdapter.get().mGridView.get().findViewWithTag(MD5Tools.decodeString(url));  
  28.             if (mImageView != null && bitmap != null) {  
  29.                 mImageView.setImageBitmap(bitmap);  
  30.                 photoAdapter.get().mDownloadTaskList.remove(this);//把下載好的任務移除  
  31.             }  
  32.         }  
  33.     }  

       11.匿名內部類內存泄漏
       跟非靜態內部類一樣,匿名內部類也會持有外部類的隱式引用,比較常見的情況有,耗時Handler,耗時Thread,都會造成內存泄漏,解決方式也是static+WeakReference。

       12.webview對象沒有及時的destroy
           一般情況下我們在activity的destory()方法裏面會調用webView.destory(),但注意在android5.1之後,這樣做會有引起內存泄漏的風險。具體可參考Android 5.1 WebView內存泄漏分析

       13.慎用Services
        service用於在後臺執行一些耗時操作,只用當它執行任務的時候纔開啓,否則其他時刻都應該不工作,service完成任務之後要主動停止,否則如果用戶發現有常駐後臺行爲的應用並且可能卸載它甚至引起內存泄漏。
當你開啓一個service,系統會傾向爲了保留這個service而一直保留service所在的進程。這使得進程的運行代價很高,因爲系統沒有辦法把service所佔用的RAM空間騰出來讓給其他組件。
推薦使用IntentService, 它會在工作線程處理完交代給它的intent任務之後自動停止。

IntentService是Service類的子類,用來處理異步請求。客戶端可以通過startService(Intent)
方法傳遞請求給IntentService。IntentService在onCreate()函數中通過HandlerThread單獨
開啓一個線程來處理所有Intent請求對象(通過startService的方式發送過來的)所對應的任務,這
樣以免事務處理阻塞主線程。執行完所一個Intent請求對象所對應的工作之後,如果沒有新的Intent
請求達到,則自動停止Service;否則執行下一個Intent請求所對應的任務。
  IntentService在處理事務時,還是採用的Handler方式,創建一個名叫ServiceHandler的內部
Handler,並把它直接綁定到HandlerThread所對應的子線程。 ServiceHandler把處理一個intent
所對應的事務都封裝到叫做onHandleIntent的虛函數;因此我們直接實現虛函數onHandleIntent,再
在裏面根據Intent的不同進行不同的事務處理就可以了。
另外,IntentService默認實現了Onbind()方法,返回值爲null。
  使用IntentService需要兩個步驟:
  1、寫構造函數
  2、實現虛函數onHandleIntent,並在裏面根據Intent的不同進行不同的事務處理就可以了。
好處:處理異步請求的時候可以減少寫代碼的工作量,比較輕鬆地實現項目的需求
注意:IntentService的構造函數一定是參數爲空的構造函數,然後再在其中調用super("name")這種形式的構造函數。
因爲Service的實例化是系統來完成的,而且系統是用參數爲空的構造函數來實例化Service的

      內存泄漏的檢測

       在AndroidStudio的Terminal中輸入 adb shell dumpsys meminfo (應用包名) 查看內存信息

          LeakCanary,一款非常好用的內存泄露檢測工具,安裝在手機上,能夠通過Log的方式告訴你是哪塊代碼發生了內存泄露。               LeakCanary的Github上的地址是:https://github.com/square/leakcanary

          參考:
           Android 性能優化&內存篇
           Android性能優化那些事
           檢測工具
           Android內存泄漏檢測工具LeakCanary上手指南
           Android內存泄漏終極解決篇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章