什么是內存泄漏:
在Android開發過程中,當一個對象已經不需要再使用了,本該被回收時,而另個正在使用的對象持有它引用從而導致它不能被回收,這就導致本該被回收的對象不能被回收而停留在堆內存中,內存泄漏就產生了。
內存泄漏的危害?
它是造成應用程序OOM的主要原因之一;由于Android系統為每個應用程序分配的內存有限,當一個應用中產生的內存泄漏比較多時,就難免會導致應用所需要的內存超過系統分配的內存限額,這就造成了內存泄漏而導致應用Crash;
內存泄漏排查:
1、使用adb命令:adb shell dumpsys meminfo 包名,查看當前activity數量。不停打開關閉要排查頁面,由于關閉頁面后垃圾回收不會立即執行,為了測試,借助Android Studio自帶的Profiler,點擊強制垃圾回收。若activiy數量和最開始時一致,則表示正常,若activity數量增加,則表明內存泄漏。
2、使用AS中Profiler進一步問題排查,點擊Dump JAVA heap導出堆分配。
常見內存泄漏的情況:
1、靜態Activity(Activity上下文Context)和View
靜態變量Activity和View會導致內存泄漏,在下面代碼中對Activity的Context和TextView設置為靜態對象,從而產生內存泄漏;
因為context和textView的實例的生命周和應用的生命一樣,而他們持有當前Activity(MemoryTestActivity)的引用,一旦MemoryTestActivity銷毀,而他的引用一直持有,就不會被回收,所以產生內存泄漏了;
public class MemoryTestActivity extends AppCompatActivity {
private static Context context;
private static TextView textView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_test);
context = this;
textView = new TextView(this);
}
}
2、單例造成的內存泄漏
Android的單例模式是開發中經常使用的模式,使用不恰當可能導致內存泄漏;單例的生命周期和應用的生命周期一樣,也就是單例持有必須是和應用生命周期一樣的對象,不能持有和應用生命周期不一致的對象例如:Activity(Context)上下文:
Java
public class TestManager {
private static TestManager manager;
private Context context;
private TestManager(Context context) {
this.context = context;
}
/**
* 如果傳入的context是activity,service的上下文,會導致內存泄漏
* 原因是我們的manger是一個static的靜態對象,這個對象的生命周期和整個app的生命周期一樣長
* 當activity銷毀的時候,我們的這個manger仍然持有者這個activity的context,就會導致activity對象無法被釋放回收,就導致了內存泄漏
*/
public static TestManager getInstance(Context context) {
if (manager == null) {
manager = new TestManager(context);
}
return manager;
}
}
解決方法:修改TestManager單例模式使用的上下文Context,TestManager單例模式引用ApplicationContext,TestManager單例模式和應用生命周期一樣,ApplicationContext和應用的生命周期是一樣,這樣不會出現內存泄漏;
Java
public class TestManager {
private static TestManager manager;
private Context context;
private TestManager(Context context) {
this.context = context;
}
//正確寫法
public static TestManager getInstance(Context context) {
if (manager == null) {
manager = new TestManager(context.getApplicationContext());
}
return manager;
}
}
3、線程造成的內存泄漏
匿名線程內部類會隱式引用Activity,當執行耗時任務時,一直隱式引用Activity,當Activity關閉時,由于匿名線程內部類會隱式引用Activity無法及時回收;
Java
public class MemoryTestActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_test);
anonymousInnerClass();
}
//匿名內部類持有MemoryTestActivity實例引用,當耗時匿名線程內部類執行完成以后MemoryTestActivity實例才會回收;
public void anonymousInnerClass() {
new AsyncTask<Void, Void, Void>(){
@Override
protected Void doInBackground(Void... voids) {
//執行異步處理
SystemClock.sleep(120000);
return null;
}
}.execute();
}
}
解決方法:修改AsyncTask匿名內部類為靜態類,解除Activity隱式引用,MemoryTestActivity銷毀時要及時取消異步任務staticAsyncTask.cancel(true),防止異步任務執行完成更新銷毀MemoryTestActivity實例的UI;
Java
public class MemoryTestActivity extends AppCompatActivity {
private StaticAsyncTask staticAsyncTask;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_test);
staticAsyncTask = new StaticAsyncTask(this);
staticAsyncTask.execute();
}
private static class StaticAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public StaticAsyncTask(Context context) {
weakReference = new WeakReference<Context>(context);
}
@Override
protected Void doInBackground(Void... voids) {
//執行異步處理
SystemClock.sleep(120000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MemoryTestActivity activity = (MemoryTestActivity) weakReference.get();
if (activity != null) {
//異步任務執行完成,執行UI處理
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
staticAsyncTask.cancel(true);
}
}
4、非靜態內部類創建靜態實例造成的內存泄漏
Java
public class MemoryTestActivity extends AppCompatActivity {
private static TestResource testResource;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_test);
testResource = new TestResource();
}
class TestResource{
//資源類
}
}
這樣就在Activity內部創建了一個非靜態內部類的單例,每次啟動Activity時都會使用該單例的數據,這樣雖然避免重復創建,不過這種寫法會造成內存泄漏,因為非靜態內部類默認會持有外部類的引用,而又使用了該非靜態內部類創建了一個靜態實例,該實例的生命周期和應用一樣長,這就導致了該靜態實例一直持有該Activity的引用,導致Activity的內存資源不能正常回收;
解決方法:將該內部類設為靜態內部類或將內部類抽象出來封裝一個單例,如果需要使用Context,請使用ApplicationContext;
5、Handler造成的內存泄漏
Handler的使用造成的內存泄漏問題是比較常見的,平時處理網絡任務或者封裝一些請求回調等api都應該會借助Handler處理,對于Handler的使用代碼不規范可能會造成內存泄漏,如下示例:
Java
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
//處理UI顯示
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_test);
loadData();
}
//loadData()方法在子線程中執行
private void loadData() {
Message message = Message.obtain();
//模擬線程延遲120秒發送Message
mHandler.sendMessageDelayed(message, 120000);
}
}
這種創建Handler的方式可能造成內存泄漏,由于mHandler是Handler的非靜態匿名內部類的實例,所以它持有外部類Activity引用,我們知道消息隊列是在一個Looper線程中不斷輪詢處理消息,那么當這個Activity退出時,消息隊列還有未處理的消息或者正在處理的消息(例如上面的例子,子線程中處理耗時任務,還沒有執行完畢,activity就退出銷毀),而消息隊列中Message持有mHandler實例引用,mHander又持有Activity的引用,所以導致Activity的內存無法及時回收,引發內存泄漏;
Java
public class MemoryTestActivity extends AppCompatActivity {
private Handler handler = new StaticHandler(this);
private static class StaticHandler extends Handler {
WeakReference<Context> weakReference;
public StaticHandler(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
//處理UI顯示
MemoryTestActivity activity = (MemoryTestActivity) weakReference.get();
if (activity != null) {
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_test);
loadData();
}
//loadData()方法在子線程中執行
private void loadData() {
Message message = Message.obtain();
//模擬線程延遲120秒發送Message
handler.sendMessageDelayed(message, 120000);
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
}
創建一個靜態Handler內部類,然后對Handler持有的對象使用弱應用,這樣在回收時也可以回收Handler持有的對象,這樣避免了Activity泄漏,如果Handler被delay(延遲執行),在Activity的Destroy或者Stop時應該移除消息隊列中的消息;
handler.removeCallbacksAndMessages(null);移除消息隊列中所有的消息和線程;
解決方案總結:
- 通過程序邏輯來進行維護
- 在關閉Activity的時候停掉后臺線程;線程停掉相當于切斷了Handler和外部連接線,Activity自然會被在合適的時候回收;
- 如果Handler被delay延遲的Message持有了引用,那么使用相應的Handler的removeCallbacks()方法,把消息對象從消息隊列移除就行;
- 將Handler聲明為靜態類
- 在Java中,非靜態的內部類和匿名內部類都會隱式持有其外部類的引用,靜態內部類不會持有外部類的引用。靜態類不持有外部類的對象,所以你的Activity可以隨意被回收;由于Handler不在持有外部類的對象的引用,導致程序不允許你在Handler中操作Activity中的對象了,所以你需要在Handler中增加一個對Activity的弱引用(WeakReference);
6、動畫
在屬性動畫中有一類無限循環動畫,如果在Activity中播放這類動畫并且在onDestroy()中沒有去停止動畫,那么動畫會一直播放下去,這時候Activity會被View所持有,從而導致Activity無法被釋放。解決此類問題要在onDestroy()方法中去調用objectAnimator.cancel()來停止動畫;
Java
public class MemoryTestActivity extends AppCompatActivity {
private TextView textView;
private ObjectAnimator objectAnimator;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_memory_test);
textView = (TextView)this.findViewById(R.id.textView2);
objectAnimator = ObjectAnimator.ofFloat(textView, "rotation", 0, 360);
objectAnimator.setRepeatCount(ValueAnimator.INFINITE);
objectAnimator.start();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
由于未在onDestroy()方法中去調用objectAnimator.cancel()來停止動畫,執行動畫的View一直引用Activity,導致Activity無法銷毀;
解決辦法:在onDestroy()方法中去調用objectAnimator.cancel()來停止動畫;
7、第三方庫使用不當
1、對于EventBus,RxJava等一些第三方開源框架的使用,若是Activity銷毀之前沒有進行解除訂閱會導致內存泄漏;
2、需要在生命周期相對注冊與注銷(onCreate->onDestory | onResume->onPause … )
8、資源未關閉造成的內存泄漏
對于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存泄漏。