Android内存泄漏

引用

内存回收机制:

当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用。我们都知道将一个对象设置为null之后,那么这个对象就不再被引用了,最终JVM就会回收它。所以我们在开发过程中需尽早释放一些无用对象的引用。

什么是引用

那么这个引用,就需要具体来说说了。我们都知道在Java中引用包括四种:强引用,软引用,弱引用,虚引用

强引用

只要引用存在,垃圾回收器永远不会回收。
强引用可通过以下代码实现
Object obj = new Object();
这个 obj对象对后面new Object的一个强引用,只有当obj这个引用被释放之后,对象才会被释放掉,这也是我们经常所用到的编码形式。

软引用

如果一个对象具有软引用,当内存空间不足,GC会回收这些对象的内存。
软引用可通过以下代码实现

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有时候会返回null

这时候sf是对obj的一个软引用,我们可以通过sf.get()来获取这个对象,当这个对象被标记为需要回收的时候,返回值为null,我们可以通过返回值来判断是否已经被回收。

弱引用

如果一个对象具有弱引用,在GC线程扫描内存区域的过程中,不管当前内存空间足够与否,都会回收内存。
它和软引用的区别是:只具有弱引用的对象拥有更短暂的生命周期。
弱引用可通过以下代码实现

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾

这时候wf是对obj的一个软引用,我们可以通过wf.get()来获取这个对象,当这个对象被回收的时候,返回值为null,我们可以通过返回值来判断是否已经被回收。
我们也可以通过wf.isEnQueued();判断是否被垃圾回收器标记为即将回收的垃圾

虚引用

如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收。
虚引用必须和引用队列联合使用,虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用可通过以下代码实现

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除

虚引用是每次垃圾回收的时候都会被回收,通过虚引用的get方法永远获取到的数据为null,因此也被成为幽灵引用。
虚引用主要用于检测对象是否已经从内存中删除。

内存泄漏

我们再来说说内存泄漏和引用的关系
什么是内存泄露?一个对象不再被程序使用,但是垃圾回收器却无法回收它们,这就叫内存泄漏。
一个对象不再被使用了,为什么还无法回收呢?因为这个对象还在被引用着。
我们来看下面这段代码:

public class A {
    public int num;
}

public class B {
    public A a;
    public String name;
}

上面的代码中,A是B的一个属性,那么B对象就引用A对象,我们也可以说,B对象持有A对象的引用。
我们再来看一段代码:

A a = new A();
B b = new B();
b.a = a;
a = null;

我们把a设置为null,那么a对象就不再被程序使用,可是b对象仍然持有a对象的引用,垃圾回收器回收内存的时候就无法回收a对象,这个时候,就已经产生了内存泄漏。


匿名类

匿名类定义

在实际的项目中看到一个很奇怪的现象,Java可以直接new一个接口,然后在new里面粗暴的加入实现代码,向下面这样:

Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println(this.getClass());
    }
};
runnable.run();

匿名类相当于在定义类的同时再新建这个类的实例

知识点

匿名类也持有着外部类的强引用。

案例1_匿名AsyncTsk

当你在Activity中定义了匿名的AsyncTsk,当异步任务在后台执行耗时任务期间,Activity不幸被销毁了(用户退出,系统回收),这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。
我们来看如下代码:

//创建匿名类
void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}
//匿名类使用
button.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        startAsyncTask();
    }
});

如上代码中匿名类AsyncTask执行期间,所有持有的引用Activity被销毁,那么这个activity实例就无法被垃圾回收器回收,直到异步任务结束。

案例2_匿名Handler

同样道理,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式强引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。

//创建匿名类
void createHandler() {
    new Handler() {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }.postDelayed(new Runnable() {
        @Override public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}

//匿名类使用
button.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createHandler();
    }
});

案例3_匿名Thread

定义匿名的Thread未结束之前,Activity实例不会被销毁,也会导致内存泄漏。

//创建匿名类
void spawnThread() {
    new Thread() {
        @Override public void run() {
            while(true);
        }
    }.start();
}

//匿名类使用
button.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        spawnThread();
    }
});

案例4_匿名TimerTask

定义匿名的TimerTask未结束之前,Activity实例不会被销毁,也会导致内存泄漏。

//创建匿名类
void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}

//匿名类使用
button.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        scheduleTimer();
    }
});

总结

看了上面4个例子,我们来总结一下:只要是匿名类的实例,不管是不是在工作线程中,都会持有外部类(Activity)的强引用,导致内存泄漏。

 

单例

知识点

单例的生命周期和应用(Application)的生命周期一样长

案例

我们先来看一段代码

public class GlobalManager {
    private volatile static GlobalManager instance;
    private Context mContext;

    private GlobalManager(Context context) {
        this.mContext = context;
    }

    public static GlobalManager getInstance(Context context) {
        if (instance == null) {
            synchronized (GlobalManager.class) {
                if (instance == null) {
                    instance = new GlobalManager(context);
                }
            }
        }
        return instance;
    }
}

这段代码中,单例的生命周期和Application是一样的。
我们看看activity的使用:

public class TestAct extends AppCompatActivity {
    private static GlobalManager globalManager = null;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_test);
        if (globalManager == null) {
            globalManager = GlobalManager.getInstance(this);
        }
    }
}

我们来分析一下这段代码:
globalManager = GlobalManager.getInstance(this),这里传入的是Activity的Context,当此Activity退出时,Activity应该被回收,可是这个单例持有这个activity的强引用,导致回收失败,所以造成内存泄漏。

解决方法

我们不管传入的是Context还是ApplicationContext,我们都将它转换为ApplicationContext,这样这个单例就不会持有Activity或其他Context的强引用了。单例代码修改如下

public class GlobalManager {
    private volatile static GlobalManager instance;
    private Context mContext;

    private GlobalManager(Context context) {
        this.mContext = context.getApplicationContext();
    }

    public static GlobalManager getInstance(Context context) {
        if (instance == null) {
            synchronized (GlobalManager.class) {
                if (instance == null) {
                    instance = new GlobalManager(context);
                }
            }
        }
        return instance;
    }
}

只是修改了构造方法:讲this.mContext = context;改成了this.mContext = context.getApplicationContext();,不管传入什么Context最终将使用Application的Context


非静态内部类作为静态变量

知识点

1.非静态内部类的实例持有外部类的强引用。
2.静态变量的生命周期和应用(Application)的生命周期一样长。

案例

我们来看一段代码

public class TestAct extends AppCompatActivity {
    private static InnerClass mInnerClass = null;    
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_test);
        if (mInnerClass == null) {
            mInnerClass = new InnerClass();
        }
    }    
    class InnerClass {
    }
}

在这个Activity中看似很好,每次创建activity的时候,静态实例并不会重新创建。那么我们来分析一下:
代码中有一个非静态内部类InnerClass,有一个静态变量mInnerClass,在代码中new InnerClass()创建了一个内部类的实例,这个实例就持有外部类(Activity)的强引用,这个实例赋值给静态变量之后,那么这个静态变量也就是静态实例,也会持有Activity的强引用,而该静态实例的生命周期和应用的生命周期一样长,这就导致了:由于这个静态实例持有activity的强引用,回收activity的时候而无法回收,也就是产生了内存泄漏。

解决方法

1.将该内部类设置为静态内部类(静态内部类不会持有外部类的引用),即如下代码:

public class TestAct extends AppCompatActivity {
    private static InnerClass mInnerClass = null;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_test);
        if (mInnerClass == null) {
            mInnerClass = new InnerClass();
        }
    }
    static class InnerClass {
    }
}

2.将该内部类抽取出来封装成一个单例,如果需要使用Context,就使用Application的Context。

public class InnerClass {
    private volatile static InnerClass instance;

    private InnerClass(Context context) {
    }

    public static InnerClass getInstance(Context context) {
        if (instance == null) {
            synchronized (InnerClass.class) {
                if (instance == null) {
                    instance = new InnerClass(context.getApplicationContext());
                }
            }
        }
        return instance;
    }
}

public class TestAct extends AppCompatActivity {
    private static InnerClass mInnerClass = null;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_test);
        if (mInnerClass == null) {
            mInnerClass = InnerClass.getInstance(this);
        }
    }
}

3.还有一种很简单粗暴的方法,就是在activity销毁的时候,将这个静态内部类设置为空。如下

public class TestAct extends AppCompatActivity {
    private static InnerClass mInnerClass = null;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_test);
        if (mInnerClass == null) {
            mInnerClass = InnerClass.getInstance(this);
        }
    }

    @Override
    protected void onDestroy() {
        mInnerClass = null;
        super.onDestroy();
    }
}

 

Handler

知识点

非静态内部类匿名类内部类的实例都会潜在持有它们所属的外部类的强引用,但是静态内部类却不会

使用匿名内部类

我们来看一段代码:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

这个Activity中new Runnable()是一个匿名内部类,这个内部类持有外部类Activity的强引用,内部类被封装成消息Message被传递到Handler的消息队列MessageQueue中,即消息持有Activity的强引用。在Message消息没有被Handler处理之前,Activity实例不会被销毁了,于是导致内存泄漏。发送postDelayed这样的消息,你输入延迟多少秒,它就会泄露至少多少秒。而发送没有延迟的消息的话,当队列中的消息过多时,也会照成一个临时的泄露。可参考Android内存泄漏之匿名内部类

使用静态内部类

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final TextView mTextView;

        protected MyRunnable(TextView textView) {
            mTextView = textView;
        }

        @Override
        public void run() {
            mTextView.setText("Finished");
        }
    }
}

这段代码中,我们使用的是静态内部类,那么这样还会内存泄漏吗?
答案:会
上面知识点中我们提到了静态内部类不会持有外部类的引用,那么为什么这里还会内存泄漏呢。
因为TextView持有Activity的强引用,我们都知道View都持有Context的引用,这里的Context就是Activity。new MyRunnable(textView)持有TextView的强引用,这样MyRunnable也就持有Activity的强引用了,所以消息为处理之前,Activity实例不会被销毁,于是导致内存泄漏。

解决方案1:弱引用+静态内部类

匿名内部类因为持有Activity的强引用,所以会导致内存泄漏。
静态内部类中的TextView持有Activity的强引用,所以也会导致内存泄漏
Android内存泄漏和引用的关系中,我们有讲到弱引用:如果一个对象具有弱引用,在GC线程扫描内存区域的过程中,不管当前内存空间足够与否,都会回收内存。
代码如下:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);

        handler.postDelayed(new MyRunnable(textView), 5000000L);
    }

    private static final class MyRunnable implements Runnable {
        private final WeakReference<TextView> wr;

        protected MyRunnable(TextView textView) {
            wr = new WeakReference<TextView>(textView);
        }

        @Override
        public void run() {
            final TextView tv = wr.get();
            if (tv != null) {
                tv.setText("Finished");
            }
        }
    }
}

这里我们把静态内部类的TextView改成弱引用了,这样虽然textView持有activity的强引用,但是new MyRunnable(textView)持有的是TextView的弱引用,这样MyRunnable持有Activity的引用也是弱引用,所以内存回收的时候,是可以回收的,我们可以通过wr.get()是否为空判断是否已经回收。

解决方案2:在onDestory的时候,手动清除Message

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }

    @Override
    protected void onDestroy() {
        handler.removeCallbacksAndMessages(null);
        super.onDestroy();
    }
}

如上方法,我们在ondestory的时候,清除所有未处理的Message,就不会有哪个消息持有Activity的强引用了,这样也不会导致内存泄漏。

解决方案3:使用第三方控件WeakHandler

WeakHandler是一个第三方库,我们看看他是怎么使用的:

public class HandlerAct extends AppCompatActivity {
    private TextView textView;
    private WeakHandler handler = new WeakHandler();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.act_handler);
        textView = (TextView) findViewById(R.id.tv_value);
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                textView.setText("Finished");
            }
        }, 5000000L);
    }
}

它用起来很简单,不需要考虑弱应用的情况,你只需要把以前的Handler替换成WeakHandler就行了。
我们看看WeakHandler的源代码:

private final WeakHandler.ExecHandler mExec;
public final boolean postDelayed(Runnable r, long delayMillis) {
    return this.mExec.postDelayed(this.wrapRunnable(r), delayMillis);
}
private WeakHandler.WeakRunnable wrapRunnable(@NonNull Runnable r) {
    if(r == null) {
        throw new NullPointerException("Runnable can\'t be null");
    } else {
        WeakHandler.ChainedRef hardRef = new WeakHandler.ChainedRef(this.mLock, r);
        this.mRunnables.insertAfter(hardRef);
        return hardRef.wrapper;
    }
}
static class WeakRunnable implements Runnable {
    private final WeakReference<Runnable> mDelegate;
    private final WeakReference<WeakHandler.ChainedRef> mReference;

    WeakRunnable(WeakReference<Runnable> delegate, WeakReference<WeakHandler.ChainedRef> reference) {
        this.mDelegate = delegate;
        this.mReference = reference;
    }

    public void run() {
        Runnable delegate = (Runnable)this.mDelegate.get();
        WeakHandler.ChainedRef reference = (WeakHandler.ChainedRef)this.mReference.get();
        if(reference != null) {
            reference.remove();
        }

        if(delegate != null) {
            delegate.run();
        }

    }
}
private static class ExecHandler extends Handler {
    private final WeakReference<Callback> mCallback;

    ExecHandler() {
        this.mCallback = null;
    }

    ExecHandler(WeakReference<Callback> callback) {
        this.mCallback = callback;
    }

    ExecHandler(Looper looper) {
        super(looper);
        this.mCallback = null;
    }

    ExecHandler(Looper looper, WeakReference<Callback> callback) {
        super(looper);
        this.mCallback = callback;
    }

    public void handleMessage(@NonNull Message msg) {
        if(this.mCallback != null) {
            Callback callback = (Callback)this.mCallback.get();
            if(callback != null) {
                callback.handleMessage(msg);
            }
        }
    }
}

源代码中可以清楚的看到它将Handler和Runnable做了弱引用封装,而ExecHandler和WeakRunnable也就是封装之后的内部类。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值