一、什麼是內存泄漏
內存泄漏(Memory Leak)是指一塊已經被開闢的堆內存由於某種原因程序未釋放或無法釋放,使得這部分內存長時間不能被使用,最終導致程序運行速度減慢甚至崩潰。
Android應用中的內存泄漏是指在應用中申請的內存空間,沒有被程序釋放,一直存在於內存當中,最終導致應用卡頓、崩潰等問題。內存泄漏往往難以發現,但是一旦發現,一定要及時修復,否則成倍的內存泄漏最終會導致應用的崩潰。
二、如何發現內存泄漏
Android提供了一些工具,如內存分析器,幫助我們檢測Android應用中的內存泄漏。
在Android Studio中,我們可以使用Android Profiler來檢測內存泄漏。在Profier中,可以查看應用的內存使用情況、資源使用情況和網絡數據傳輸等信息。其中,在內存選項卡中,可以分別查看Java Heap、Native Heap和GPU Memory的情況,以及Allocation Tracker和Heap Dump的信息。
//使用LeakCanary監測內存泄漏: dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' }
public class ExampleApplication extends Application { @Override public void onCreate() { super.onCreate(); if (LeakCanary.isInAnalyzerProcess(this)) { // This process is dedicated to LeakCanary for heap analysis. // You should not init your app in this process. return; } LeakCanary.install(this); // Normal app init code... } }
三、內存泄漏的常見場景
1. 對象持有
當一個Activity結束時,如果這個Activity中包含的控件仍被其他地方所持有,這個Activity所佔用的內存就無法釋放。比如當一個Activity持有了一些Bitmap,並將其傳遞給了其他對象使用,但是沒有及時trim內存或者內存不足時,這些Bitmap就會導致內存泄漏。
public class ExampleActivity extends Activity { private ImageView mImageView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_example); mImageView = findViewById(R.id.image); // 使用了R.drawable.large_image這張大圖 mImageView.setImageResource(R.drawable.large_image); } }
在以上代碼中,如果這張大圖沒有及時回收,這將會導致內存一直暴增,程序異常。
2. 靜態變量持有對象
靜態變量是不能被GC回收的,如果靜態變量持有了Activity等Context對象,那麼在Activity被回收之前,GC將不能回收Activity所佔用的內存。
以下代碼演示了靜態變量引用了Activity,從而導致Activity不能正常實現回收,API 21及以上的系統機制,可將該Activity加入「歷史記錄列表」,便可被回收。
public class ExampleActivity extends Activity { private static Activity sCurrentActivity; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_example); sCurrentActivity = this; } @Override protected void onDestroy() { super.onDestroy(); sCurrentActivity = null; } }
3. Handler導致泄露
Handler類在Android開發中經常被用來做一些異步的操作,帶有延時的Runnable容易導致內存被泄漏。
public class ExampleActivity extends Activity { private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // do some thing; } }; private void postSomeThingDelay() { mHandler.postDelayed(new Runnable() { @Override public void run() { } }, 2000); } }
在以上代碼中,如果Activity被銷毀,則handler將長期持有它,並阻止它被回收。
4. 監聽器導致泄露
監聽器是一個常用的功能,在Android中有諸如setOnClickListener、addTextChangedListener等監聽器。如果一個對象把它自己傳給第三方,並註冊一個監聽器,那麼這個對象將不能被GC回收。
public class ExampleActivity extends Activity { private EditText editText; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText = findViewById(R.id.edit_text); editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } @Override public void afterTextChanged(Editable s) { } }); } }
在以上代碼中,如果EditText對象被銷毀之前,內存泄漏將被固定。
四、解決內存泄漏
發現內存泄漏後,要儘快進行解決,以下是幾點解決方案。
1. 及時釋放對象持有的資源
在Activity的onStop()或onDestroy()中,需要釋放那些在onCreate()中創建的資源。如Image、Bitmap等。
2. 定期調用System.gc()
System.gc()的作用是告訴GC執行送達,但並不強制GC立刻執行,GC並不是萬能的,它需要系統去為其提供垃圾收集的條件。所以並不建議調用。
3. 使用WeakReference和軟引用
使用這兩種引用類型可以避免長時間持有Activity對象,當對象沒有其他引用時,會被GC自動回收。
public class ExampleActivity extends Activity { private WeakReference mContext; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = new WeakReference(this); } private void doSomething() { if (mContext.get() != null) { // do something } } }
4. 使用HandlerThread
將線程和Handler分離使用,這樣Handler執行結束時,Thread不會立刻結束。
public class ExampleActivity extends Activity { private HandlerThread mHandlerThread; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandlerThread = new HandlerThread("MyHandlerThread"); mHandlerThread.start(); Handler mHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { // do some thing } }; } @Override protected void onDestroy() { super.onDestroy(); mHandlerThread.quit(); } }
5. 使用LeakCanary進行定位
LeakCanary是一個強大的Android庫,可以在應用內部發現內存泄漏。其會在弱引用對象被回收時,記錄下調用堆棧(如果發現調用堆棧在不斷增大,那麼很可能存在泄漏),當我們檢測到一個對象已經被泄露時,可以通過查看堆轉儲文件,查找泄露原因。
總結
內存泄漏是Android開發中常見的問題,對應的解決方案也有很多,但是依然需要開發者掌握一些技巧才能更好地避免內存泄漏。多檢查自己的代碼,適時使用各種工具進行檢測和解決。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hk/n/295430.html