什么叫内存泄漏呢?
gc没有办法回收activity的内存。
那什么情况下会GC
呢?
- 屏幕旋转的时候
- 内存不够用的时候。
什么情况会发生内存泄漏呢?
静态资源问题
静态变量在整个应用的内存里只保存一份,一旦创建就不会释放该变量的内存,直到整个应用都销毁才会释放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才不会导致内存泄漏
内部类Handler类引起内存泄漏
在Activity中定义了一个内部Handler类:
public class MainActivity extends Activity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//TODO handle message...
}
};
@TargetApi(11)
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 60000);//延迟一分钟执行
//just finish this activity
finish();//关闭activity
}
}
以上代码将会导致内存泄漏的问题(原因):
- 在Java中,非静态(匿名)内部类会引用外部类对象。而静态内部类不会引用外部类对象。
当Activity finish
后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity(从以上原因可知道)。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
修改:将Handler
改为静态类,并使用WeakReference
来保持外部的activity
对象。
private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler{
private final WeakReference<Activity> mActivity;
public MyHandler(Activity activity) {
mActivity = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
System.out.println(msg);
if(mActivity.get() == null) {
return;
}
}
}
结论:当你在Activity中使用内部类的时候,需要时刻考虑您是否可以控制该内部类的生命周期,如果不可以,则最好定义为静态内部类。
以上摘自:内部Handler类引起内存泄露
另一种解决的方式:
在activty
的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);
}
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 的强引用
AsynTask 内部类
跟线程一样的原理。
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
}
}
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()
进行中断,但不一定成功的。所以最好的使用静态内存类**或者使用弱引用
。
实战检测定位内存泄露
1. 使用MAT工具定位Android应用内存泄漏
# 使用Android studio分析内存泄露
资料:
Android Context 是什么?
# Android内存泄露之Thread
# Android内存泄露之Handler
Android App 内存泄露之资源
Android内存泄露之DDMS –> Heap工具,这个系列的文章。
Android学习系列(37)--App调试内存泄露之Context篇(下)
Android内存泄露的整理