Android內存優化11 內存泄漏常見狀況2 內部類泄漏

線程持久化java

Java中的Thread有一個特色就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對全部被激活狀態的線程都是持有強引用,致使GC永遠都沒法回收掉這些線程對象,除非線程被手動中止並置爲null或者用戶直接kill進程操做。因此當使用線程時,必定要考慮在Activity退出時,及時將線程也中止並釋放掉ide

內存泄漏1:AsyncTask

void startAsyncTask() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { while(true); } }.execute(); } super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View aicButton = findViewById(R.id.at_button); aicButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { startAsyncTask(); nextActivity(); } });

 

使用LeakCanary檢測到的內存泄漏:oop

這裏寫圖片描述

爲何?
上面代碼在activity中建立了一個匿名類AsyncTask,匿名類和非靜態內部類相同,會持有外部類對象,這裏也就是activity,所以若是你在Activity裏聲明且實例化一個匿名的AsyncTask對象,則可能會發生內存泄漏,若是這個線程在Activity銷燬後還一直在後臺執行,那這個線程會繼續持有這個Activity的引用從而不會被GC回收,直到線程執行完成。動畫

怎麼解決?
自定義靜態AsyncTask類,而且讓AsyncTask的週期和Activity週期保持一致,也就是在Activity生命週期結束時要將AsyncTask cancel掉。this

內存泄漏2:Handler

非靜態內部類致使的內存泄露在Android開發中有一種典型的場景就是使用Handler,不少開發者在使用Handler是這樣寫的:spa

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessageDelayed(msg,1000); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { // 作相應邏輯 } } }; } 

也許有人會說,mHandler並未做爲靜態變量持有Activity引用,生命週期可能不會比Activity長,應該不必定會致使內存泄露呢,顯然不是這樣的!線程

熟悉Handler消息機制的都知道,mHandler會做爲成員變量保存在發送的消息msg中,即msg持有mHandler的引用,而mHandlerActivity的非靜態內部類實例,即mHandler持有Activity的引用,那麼咱們就能夠理解爲msg間接持有Activity的引用。msg被髮送後先放到消息隊列MessageQueue中,而後等待Looper的輪詢處理(MessageQueueLooper都是與線程相關聯的,MessageQueueLooper引用的成員變量,而Looper是保存在ThreadLocal中的)。那麼當Activity退出後,msg可能仍然存在於消息對列MessageQueue中未處理或者正在處理,那麼這樣就會致使Activity沒法被回收,以至發生Activity的內存泄露。code

一般在Android開發中若是要使用內部類,但又要規避內存泄露,通常都會採用靜態內部類+弱引用的方式。對象

public class MainActivity extends AppCompatActivity { private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new MyHandler(this); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private static class MyHandler extends Handler { private WeakReference<MainActivity> activityWeakReference; public MyHandler(MainActivity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = activityWeakReference.get(); if (activity != null) { if (msg.what == 1) { // 作相應邏輯 } } } } } 

mHandler經過弱引用的方式持有Activity,當GC執行垃圾回收時,遇到Activity就會回收並釋放所佔據的內存單元。這樣就不會發生內存泄露了。生命週期

上面的作法確實避免了Activity致使的內存泄露,發送的msg再也不已經沒有持有Activity的引用了,可是msg仍是有可能存在消息隊列MessageQueue中,因此更好的是在Activity銷燬時就將mHandler的回調和發送的消息給移除掉。

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

爲何?

建立的Handler對象爲匿名類,匿名類默認持有外部類activity, Handler經過發送Message與主線程交互,Message發出以後是存儲在MessageQueue中的,有些Message也不是立刻就被處理的。這時activity被handler持有
handler被message持有,message被messagequeue持有,message queue被loop持有,主線程的loop是全局存在的,這時就形成activity被臨時性持久化,形成臨時性內存泄漏

怎麼解決?
能夠由上面的結論看出,產生泄漏的根源在於匿名類持有Activity的引用,所以能夠自定義Handler和Runnable類並聲明成靜態的內部類,來解除和Activity的引用。或者在activity 結束時,將發送的Message移除

內存泄漏3:Thread

代碼以下:
MainActivity.java

void spawnThread() { new Thread() { @Override public void run() { while(true); } }.start(); } View tButton = findViewById(R.id.t_button); tButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { spawnThread(); nextActivity(); } }); 

 

爲何?
Java中的Thread有一個特色就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對全部被激活狀態的線程都是持有強引用,致使GC永遠都沒法回收掉這些線程對象,除非線程被手動中止並置爲null或者用戶直接kill進程操做。看到這相信你應該也是心中有答案了吧 : 我在每個MainActivity中都建立了一個線程,此線程會持有MainActivity的引用,即便退出Activity當前線程由於是直接被GC Root引用因此不會被回收掉,致使MainActivity也沒法被GC回收

怎麼解決?
當使用線程時,必定要考慮在Activity退出時,及時將線程也中止並釋放掉

內存泄漏4:Timer Tasks

TimerTimerTask在Android中一般會被用來作一些計時或循環任務,好比實現無限輪播的ViewPager

public class MainActivity extends AppCompatActivity { private ViewPager mViewPager; private PagerAdapter mAdapter; private Timer mTimer; private TimerTask mTimerTask; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); mTimer.schedule(mTimerTask, 3000, 3000); } private void init() { mViewPager = (ViewPager) findViewById(R.id.view_pager); mAdapter = new ViewPagerAdapter(); mViewPager.setAdapter(mAdapter); mTimer = new Timer(); mTimerTask = new TimerTask() { @Override public void run() { MainActivity.this.runOnUiThread(new Runnable() { @Override public void run() { loopViewpager(); } }); } }; } private void loopViewpager() { if (mAdapter.getCount() > 0) { int curPos = mViewPager.getCurrentItem(); curPos = (++curPos) % mAdapter.getCount(); mViewPager.setCurrentItem(curPos); } } private void stopLoopViewPager() { if (mTimer != null) { mTimer.cancel(); mTimer.purge(); mTimer = null; } if (mTimerTask != null) { mTimerTask.cancel(); mTimerTask = null; } } @Override protected void onDestroy() { super.onDestroy(); stopLoopViewPager(); } } 

當咱們Activity銷燬的時,有可能Timer還在繼續等待執行TimerTask,它持有Activity的引用不能被回收,所以當咱們Activity銷燬的時候要當即cancelTimerTimerTask,以免發生內存泄漏。

爲何?
這裏內存泄漏在於Timer和TimerTask沒有進行Cancel,從而致使Timer和TimerTask一直引用外部類Activity。

怎麼解決?
在適當的時機進行Cancel。

內存泄漏5:屬性動畫形成內存泄露

動畫一樣是一個耗時任務,好比在Activity中啓動了屬性動畫(ObjectAnimator),可是在銷燬的時候,沒有調用cancle方法,雖然咱們看不到動畫了,可是這個動畫依然會不斷地播放下去,動畫引用所在的控件,所在的控件引用Activity,這就形成Activity沒法正常釋放。所以一樣要在Activity銷燬的時候cancel掉屬性動畫,避免發生內存泄漏。

@Override protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); } 
相關文章
相關標籤/搜索