1. 程式人生 > >Android記憶體洩漏------常見場景

Android記憶體洩漏------常見場景

記憶體管理和引用型別

記憶體洩漏的檢測流程、捕捉以及分析

1、單例造成的記憶體洩露

單例模式是非常常用的設計模式,使用單例模式的類,只會產生一個物件,這個物件看起來像是一直佔用著記憶體,但這並不意味著就是浪費了記憶體,記憶體本來就是拿來裝東西的,只要這個物件一直都被高效的利用就不能叫做洩露。

實質是靜態變數引用Activity,在getInstance(Context context)方法中傳入的引數context如果是某個Activity,但是Activity的生命週期較短,而單例作為Static物件是全生命週期的,這樣當Activity結束後,單例物件仍然持有Activity的引用,GC機制無法判定為垃圾進行回收,此時發生記憶體洩露。

public class SingleInstanceTest {

    private static SingleInstanceTest sInstance;
    private Context mContext;

    private SingleInstanceTest(Context context){
        //不能直接使用Activity或者短週期的Context
        this.mContext = context.getApplicationContext();
    }

    public static SingleInstanceTest newInstance(Context context){
        if(sInstance == null){
            sInstance = new SingleInstanceTest(context);
        }
        return sInstance;
    }
}

二、非靜態內部類導致記憶體洩露

非靜態內部類自動獲得外部類的強引用,而且它的生命週期甚至比外部類更長,這便埋下了記憶體洩露的隱患。如果一個 Activity 的非靜態內部類的生命週期比 Activity 更長,那麼 Activity 的記憶體便無法被回收,也就是發生了記憶體洩露,而且還有可能發生難以預防的空指標問題。解決辦法:將內部類宣告為靜態內部類,使其無法於外部類建立聯絡。

三、匿名內部類導致記憶體洩露

比方說Thread、Handler等
1、new 出一個匿名的 Thread,進行耗時的操作,如果 MainActivity 被銷燬而 Thread 中的耗時操作沒有結束的話,便會產生記憶體洩露;
解決方法:繼承 Thread  實現靜態內部類
2、new 出一個匿名的 Handler,這時如果 MainActivity 被銷燬,而 Handler 裡面的訊息還沒傳送完畢的話,Activity 的記憶體也不會被回收;
解決方法:
A:繼承 Handler 實現靜態內部類,以及在 Activity 的 onDestroy() 方法中,移除所有的訊息 mHandler.removeCallbacksAndMessages(null);
B:通過內部靜態類和弱引用的方法來及時釋放Activity,需要注意的是在Activity退出時記得清除掉訊息佇列中的訊息

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);
        new Thread(new Runnable() {
            @Override
            public void run() {
                。。。。。。
            }
        }).start();


        Message message = Message.obtain();
        mHandler.sendMessageDelayed(message, 10000);
    }

四、Handler導致記憶體洩露

其實還是匿名內部類的問題,都是錯誤的建立Handler,導致非靜態物件引用了Activity

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 onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
}

五、資源未關閉導致記憶體洩露

對於使用了ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者登出,否則這些資源將不會被回收,造成記憶體洩漏。
還有其他:
手動註冊廣播時,退出時忘記 unregisterReceiver()
Service 執行完後忘記 stopSelf()
EventBus 等觀察者模式的框架忘記手動解除註冊

六、集合中物件沒清理造成的記憶體洩漏

Android主要的集合類
1、List 主要有ArrayList、LinkedList、Vector和Stack 
2、Set 主要有HashSet 和 TreeSet 
3、Map 主要有 HashMap 和 TreeMap 
參考:https://blog.csdn.net/dbpggg/article/details/80825294
https://blog.csdn.net/gundumw100/article/details/69977700
https://www.jianshu.com/p/37bb043aef73

程式設計過程中,我們常常會把一些物件加入到集合中。在我們不再需要該物件時,如果沒有及時把它從集合中清理掉,就會導致這個集合佔用的記憶體越來越大。
同時如果這個集合是靜態的話,那情況就更嚴重了。
解決辦法:集合中不再使用的物件應及時釋放掉。應該在Activity的onDestroy()方法中,及時清理set裡的元素,避免無用物件繼續存在強引用。

public class MainActivity extends AppCompatActivity {
    static List<Object> objectList = new ArrayList<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        for (int i = 0; i < 10; i++) {
            Object obj = new Object();
            objectList.add(obj);
            obj = null;
        }
    }

    public void onDestory(){
        objectList.clear();
        objectList = null;
    }
}

七、ListView的記憶體洩漏問題

構造Adapter時,沒有使用快取的 convertView,導致記憶體洩露。
來向ListView提供每一個item所需要的view物件。初始時ListView會從BaseAdapter中根據當前的屏幕布局例項化一定數量的view物件,同時ListView會將這些view物件快取起來。如果我們不去使用convertView,而是每次都在getView()中重新例項化一個View物件的話,即浪費資源也浪費時間,也會使得記憶體佔用越來越大。

public View getView(int position, View convertView, ViewGroup parent) {
  View view = null;
  if (convertView != null) {
  view = convertView;
  ...
  } else {
  view = new Xxx(...);
  ...
  }
  return view;
}

八、static關鍵字所導致的記憶體洩漏

public class SecondActivity extends Activity{
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            SecondActivity.this.finish();
            this.removeMessages(0);
        }
    };
 
    private static Hehe hehe;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        haha = new Haha();
        mHandler.sendEmptyMessageDelayed(0,5000);
    }
 
    class Hehe{
 
    }
}

其實還是非靜態內部類的靜態引用。然後在5秒之後我們要finish掉這個activity,會造成什麼問題呢?我們知道,內部類和外部類之間是相互持有引用的,SecondActivity例項持有了hehe的引用,但這裡hehe是用static修飾的,上面說了,虛擬機器不會回收hehe這個物件,從而導致SecondActivity例項也得不到回收,造成記憶體溢位
解決辦法:只要在Activity的onDestroy方法裡把haha設為null就行啦

 

九、不當的使用記憶體,雖然不能導致洩漏,但是可能導致OOM