线程持久化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(); }