Android 内存泄漏的一些情况。

时间:2021-03-03 20:55:48

1. 静态 Activity

2. 静态 View

3. 非静态内部类

4. 匿名类

5. Handler

6. Thread

7. TimerTask

8. SensorManager

 

1.资源对象没关闭造成的内存泄漏

2.构造Adapter时,没有使用缓存的convertView

3.Bitmap对象不在使用时调用recycle()释放内存

4.试着使用关于application的context来替代和activity相关的context

5.注册没取消造成的内存泄漏

6.集合中对象没清理造成的内存泄漏

 

 

 

避免引用Context造成的内存泄露

 

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

interface 放在fragment内或者放在外面单独存在,一般不会造成activity泄漏
public interface DialogCallback {
void showModifyInputDialog();
void showTheMessageDialog();
}



其实一般的匿名内部类是不会导致activity释放不了的,只要你不在handle内进行奇怪的操作
private Handler uiHandler = new Handler(){

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

 

内存泄漏全解析

最近在维护代码,发现一个自定义View(这个View是在一个AsyncTask的工作线程doInBackground中新建的,在UI线程onPostExecute中添加进window中的)经常会泄漏内存,导致其引用的Activity一直得不到释放,每次退出再进去都会导致Activity的对象+1.

package com.xxx.launcher.view;

import android.content.Context;
import android.util.Log;
import android.view.View;

public class WeatherTextView extends SkinTextView {

public WeatherTextView (Context context) {
super(context); postDelayed(mShowCityRunnable,
200);//这一步有问题
}

@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == View.VISIBLE) {
post(mShowCityRunnable);
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
onCancel();
};

public void onCancel(){
removeCallbacks(mShowCityRunnable);
}
private Runnable mShowCityRunnable = new Runnable() {

@Override
public void run() {
Log.i(
"mShowCityRunnable-------TAG", "run"+mShowCityRunnable);
setText(city);
}
};
}

 

Android 内存泄漏的一些情况。

最后通过MAT工具查看内存快照的比较,发现了如下的情况,把内存泄露的地方锁定在了WeatherTextView$2的第二个内部类中mShowCityRunnable ,一开始始终都想不到这个内部类到底有什么地方泄露了,最后突然灵光一闪,是不是View的post()方法导致的,在网上一查,发现确实。

public boolean post(Runnable action) {  
Handler handler;
AttachInfo attachInfo
= mAttachInfo;
if (attachInfo != null) {
handler
= attachInfo.mHandler;
}
else {
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}

return handler.post(action);
}

在post() 函数注释中,明确写着:This method can be invoked from outside of the UI thread only when this View is attached to a window.

当View还没有attach到当前window时,mAttachInfo 值为 null,故而执行 else语句,再看一下getRunQueue()和其post() 方法:

static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>();  

static RunQueue getRunQueue() {
RunQueue rq
= sRunQueues.get();
if (rq != null) {
return rq;
}
rq
= new RunQueue();
sRunQueues.
set(rq);
return rq;
}
……
static final class RunQueue {
private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

void post(Runnable action) {
postDelayed(action,
0);
}

void postDelayed(Runnable action, long delayMillis) {
HandlerAction handlerAction
= new HandlerAction();
handlerAction.action
= action;
handlerAction.delay
= delayMillis;

synchronized (mActions) {
mActions.add(handlerAction);
}
}

void executeActions(Handler handler) {
synchronized (mActions) {
final ArrayList
<handleraction> actions = mActions;
final
int count = actions.size();

for (int i = 0; i < count; i++) {
final HandlerAction handlerAction
= actions.get(i);
handler.postDelayed(handlerAction.action, handlerAction.delay);
}

actions.clear();
}
}
……
}

这样会把Runnable 插入到一个静态的ThreadLocal的RunQueue队列里(在工作线程中post,就会插入工作线程的RunQueue队列),针对本文开头给出的例子,那么插入的Runnable什么时候得到执行呢?

调用RunQueue.executeActions()方法只有一处,即在ViewRootImpl类的如下非静态方法中

private void performTraversals() {  

if (mLayoutRequested && !mStopped) {
// Execute enqueued actions on every layout in case a view that was detached
// enqueued an action after being detached
getRunQueue().executeActions(attachInfo.mHandler);
}
}

该方法是在UI线程执行的(见ViewRootImpl.handleMessage()), 故当UI线程执行到该performTraversals() 里的 getRunQueue() 时,得到的是UI线程中的RunQueue,这样AsyncTask 线程中的 RunQueue永远不会被执行到, 并且AsyncTask的是用线程池实现的,AsyncTask启动的线程会长期存在,造成如下引用关系:

 

AsyncTask线程 => 静态的ThreadLocal的RunQueue => Runnable => View=> Activity;

如此即使activity finish 了,确始终存在一个静态引用链引用这该activity,而 Activity一般又引用着很多资源,比如图片等,最终造成严重资源泄漏。

最后我是写改成

package com.xxx.launcher.view;

import android.content.Context;
import android.util.Log;
import android.view.View;

public class WeatherTextView extends SkinTextView {

public WeatherTextView (Context context) {
super(context);
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
postDelayed(mShowCityRunnable,
200); //在onAttachedToWindow方法中执行post方法
}

@Override
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
if (visibility == View.VISIBLE) {
post(mShowCityRunnable);
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
onCancel();
};

public void onCancel(){
removeCallbacks(mShowCityRunnable);
}
private Runnable mShowCityRunnable = new Runnable() {

@Override
public void run() {
Log.i(
"mShowCityRunnable-------TAG", "run"+mShowCityRunnable);
setText(city);
}
};
}

Android 内存泄漏的一些情况。

Android 内存泄漏的一些情况。

 

这样Activity就没有再被其他东西引用了,就不会发生Activity的泄漏了,Activity就可以被释放了。这样,不管进入退出进入这个MainMenuActivity多少次,MainMenuActivity的对象就只会保存一份。

ps:至于为什么在两个Histogram(直方图)的比较图中还是显示MainMenuActivity+1,则是因为这是类名,类被加载之后,在进程结束之前不会被回收

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

===============================================================================================================================

===============================================================================================================================

 

Android 内存泄漏的一些情况。

这种泄漏一般是因为mStorageManager 注册了但是没有取消注册

mStorageManager = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE);
mStorageManager.registerListener(mStoragelistener);

取消注册就可以了

if (mStorageManager != null) {
mStorageManager.unregisterListener(mStoragelistener);
}