線程持久化java
Java中的Thread有一個特色就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對全部被激活狀態的線程都是持有強引用,致使GC永遠都沒法回收掉這些線程對象,除非線程被手動中止並置爲null或者用戶直接kill進程操做。因此當使用線程時,必定要考慮在Activity退出時,及時將線程也中止並釋放掉ide
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
非靜態內部類致使的內存泄露在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
的引用,而mHandler
是Activity
的非靜態內部類實例,即mHandler
持有Activity
的引用,那麼咱們就能夠理解爲msg
間接持有Activity
的引用。msg
被髮送後先放到消息隊列MessageQueue
中,而後等待Looper
的輪詢處理(MessageQueue
和Looper
都是與線程相關聯的,MessageQueue
是Looper
引用的成員變量,而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移除
代碼以下:
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退出時,及時將線程也中止並釋放掉
Timer
和TimerTask
在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銷燬的時候要當即cancel
掉Timer
和TimerTask
,以免發生內存泄漏。
爲何?
這裏內存泄漏在於Timer和TimerTask沒有進行Cancel,從而致使Timer和TimerTask一直引用外部類Activity。
怎麼解決?
在適當的時機進行Cancel。
動畫一樣是一個耗時任務,好比在Activity
中啓動了屬性動畫(ObjectAnimator
),可是在銷燬的時候,沒有調用cancle
方法,雖然咱們看不到動畫了,可是這個動畫依然會不斷地播放下去,動畫引用所在的控件,所在的控件引用Activity
,這就形成Activity
沒法正常釋放。所以一樣要在Activity
銷燬的時候cancel
掉屬性動畫,避免發生內存泄漏。
@Override protected void onDestroy() { super.onDestroy(); mAnimator.cancel(); }