1.什么是内存泄露
Java内存泄漏指的是进程中某些对象(垃圾对象)已经没有使用价值了,但是它们却可以直接或间接地引用到gc roots导致无法被GC回收。
2.Handler的内部类造成的内存泄露由什么原因造成?
Handler mHandler = new Handler() {A.使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
B.如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
简而言之:
- 在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象。
- 当Activity Finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity(从以上原因可知道)。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
解决方案:
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
在activity的onDestroy方法对Handler的取消:
public void onDestroy() {
mHandler.removeMessages(MESSAGE_1);
mHandler.removeMessages(MESSAGE_2);
mHandler.removeMessages(MESSAGE_3);
mHandler.removeMessages(MESSAGE_4);
// ... ...
mHandler.removeCallbacks(mRunnable);
// ... ...
}
或者:
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
}
方法二:将Handler
改为静态类,并使用WeakReference
来保持外部的activity
对象。
静态类不持有外部类对象的引用,所以你的Activity可以随意被回收。代码如下:
static class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
mImageView.setImageBitmap(mBitmap);
}
}
由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):
static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mImageView.setImageBitmap(mBitmap);
}
}
}
结论:当你在Activity中使用内部类的时候,需要时刻考虑您是否可以控制该内部类的生命周期,如果不可以,则最好定义为静态内部类。
3.Thread对象
同上:Thread的生命周期不一定是和Activity生命周期一致。
同上的解决方式为:
- 改为静态内部类
- 采用弱引用来保存
Context
引用
正确的使用为:
public class ThreadAvoidActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread(this).start();
}
private void dosomthing() {//dosomthing是ThreadAvoidActivity的方法
}
private static class MyThread extends Thread {
WeakReference<ThreadAvoidActivity> mThreadActivityRef;//弱引用
public MyThread(ThreadAvoidActivity activity) {
mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
activity);
}
@Override
public void run() {//先判断是否为null,回收掉
super.run();
if (mThreadActivityRef == null)
return;
if (mThreadActivityRef.get() != null)
mThreadActivityRef.get().dosomthing();//通过弱引用得到外部的activity来调用dosomthing方法
// dosomthing
}
}
}
以上切断两个对象的双向强引用链接
- 静态内部类:切断Activity 对于 MyThread的强引用。
- 弱引用: 切断MyThread对于Activity 的强引用
静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放static静态变量的内存.
例子:
public class MyCustomResource {
//静态变量drawable
private static Drawable drawable;
private View view;
public MyCustomResource(Context context) {
Resources resources = context.getResources();
drawable = resources.getDrawable(R.drawable.ic_launcher);
view = new View(context);
view.setBackgroundDrawable(drawable);
}
}
//查看setBackgroundDrawable的源代码:
public void setBackgroundDrawable(Drawable background) {
..........
/**此处的this就是当前View对象,而View对象又是有Context对象获得 因此,变量background持有View对象的引用,View持有Context的引用, 所有background间接持有Context对象的引用了*/
background.setCallback(this);
.......
}
分析:
此处的background.setCallback(this);
的this
是当前的view
的对象。由于background
是一个静态变量,会一直持有View对象的引用,而然View对象又是由Context对象创建出来的,因此background会间接持有Context的对象的引用。
所以:
该Context对应的Activity退出finish掉的时候其实该Activity是不能完全释放内存的。
值得注意的是:
代码是由于静态资源drawable
持有View
对象的引用导致内存泄漏隐患的,并不是由于context.getResourc
e导致内存泄漏,因此如果你想通过Context.getApplicaitonContext
来获取getResource
是解决不了内存泄漏的.
另外提一点:(使用getApplicaitonContext
的错误)
android.app.Application cannot be cast to android.app.Activity
因此:在android 3.0 中:
修改了setBackgroundDrawable内部方法中的 background.setCallback(this);方法。里面的实现使用了弱引用来持有View对象的引用,从而避免了内存泄漏隐患。
总结:以后代码中避免使用静态资源,或者使用弱引用来解决相应的问题也是可以的。
单例模式导致内存泄漏(单例中的类是静态的,引用context将导致以上的问题)
public class CustomManager {
private static CustomManager sInstance;
public static CustomManager getInstance(Context context) {
if (sInstance == null) {
sInstance = new CustomManager(context);//使用context的引用。
}
return sInstance;
}
private Context mContext;
private CustomManager(Context context) {
mContext = context;
}
}
单例模式使用的是静态类的方式,让该对象在整个应用的内存中保持一份该对象,从而减少对多次创建对象带来的资源浪费。
同样的问题:
在创建该单例的时候使用了生命周期端的Context对象的引用,如果你是在Application中创建以上单例的话是木有任何问题的。因为Application的Context生命周期是整个应用,和单例的生命周期一样,因此不会导致内存泄漏。但是,如果你是在Activity中创建以上单例的话,将一样导致跟上一个一样的内存问题。
所以讲以上的代码改成:
if (sInstance == null) {
sInstance = new CustomManager(context.getApplicationContext()); }//注意这里的不同。
return sInstance;
以上全摘自:Android Context 是什么?
通过上面大家可能对Context
比较模糊。请阅读:Android Context 是什么?。
记录几点:
- getApplication和getApplicationContext返回同一个
Application
对象,只是里面方法调用的成员返回不同。 - 所有Context都是在应用的主线程ActivityThread中创建的
- 尽量少用Context对象去获取静态变量,静态方法,以及单例对象。以免导致内存泄漏
- 在创建与UI相关的地方,比如创建一个Dialog,或者在代码中创建一个TextView,都用Activity的Context去创建。然而在引用静态资源,创建静态方法,单例模式等情况下,使用生命周期更长的Application的Context才不会导致内存泄露AsynTask 内部类线程一样的原理。
5.AsyncTask
内部的实现机制是运用了ThreadPoolExcutor, 该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的。
你可能会说:AsynTask不是有cancel的方法么?对的,是有这个方法,但还是不一定能取消。通过源码可以知道
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
问题归因于mFuture.cancel(mayInterruptIfRunning);
是否能取消正在执行的任务呢?
通过线程执行者(九)执行者取消一个任务,文章不是重点,重点是评论,提出了是否能真正取消任务的质疑?
查阅:Android学习系列(37)--App调试内存泄露之Context篇(下),这里引用官方的言语: cancel是不一定成功的。
总结:只要是使用线程正在进行执行任务,都不一定能够取消。大多数是使用:Thread.interrupt()
进行中断,但不一定成功的。所以最好的使用静态内存类或者使用弱引用
。
官方的例子:
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
// Escape early if cancel() is called
// 注意下面这行,如果检测到cancel,则及时退出
if (isCancelled()) break;
}
return totalSize;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
在后台循环中时刻监听cancel状态,防止没有及时退出。
所以:使用cancel
并不靠谱,那么如何书写呢?跟Thread
的思路是一样,差不多:
/**
*
* 弱引用
* @version 1.0.0
* @author Abay Zhuang <br/>
* Create at 2014-7-17
*/
public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget>
extends AsyncTask<Params, Progress, Result> {
protected WeakReference<WeakTarget> mTarget;
public WeakAsyncTask(WeakTarget target) {
mTarget = new WeakReference<WeakTarget>(target);
}
@Override
protected final void onPreExecute() {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPreExecute(target);
}
}
@Override
protected final Result doInBackground(Params... params) {
final WeakTarget target = mTarget.get();
if (target != null) {
return this.doInBackground(target, params);
} else {
return null;
}
}
@Override
protected final void onPostExecute(Result result) {
final WeakTarget target = mTarget.get();
if (target != null) {
this.onPostExecute(target, result);
}
}
protected void onPreExecute(WeakTarget target) {
// Nodefaultaction
}
protected abstract Result doInBackground(WeakTarget target,
Params... params);
protected void onPostExecute(WeakTarget target, Result result) {
// Nodefaultaction
}
}
6.BroadcastReceiver对象
原因:没有取消注册。
直接:getContext().unregisterReceiver(receiver);
即可.
注册与反注册:
addCallback <==> removeCallback
registerReceiver <==> unregisterReceiver
addObserver <==> deleteObserver
registerContentObserver <==> unregisterContentObserver
TimerTask对象
TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。
要在合适的时候进行Cancel
即可。
private void cancelTimer(){
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
剩下几个小点:
- Dialog对象:使用isFinishing()判断Activity是否退出。才可以
showDialog
- Bitmap没调用recycle()
- 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们
- 构造Adapter时,没有使用缓存的 convertView
- 集合容器对象没清理造成的内存泄露(尤其是static的集合,需要clear清理)
- WebView对象没有销毁。它的destory()函数来销毁它
总结:
泄漏都是有生命周期和Activity不一定一致,导致无法进行回收。
只要是使用线程正在进行执行任务,都不一定能够取消。大多数是使用:Thread.interrupt()
进行中断,但不一定成功的。所以最好的使用静态内存类**或者使用弱引用
。
参考:http://www.jianshu.com/p/a53197d588bf