防止网络请求(或其他回调)引用,从而造成内存泄漏

46 篇文章 1 订阅
20 篇文章 1 订阅

本文的解决方案用来解决类似如:Activity请求网络,而回调传的是自身,造成Activity执行finish()后并没有被销毁,而是被网络请求持有.和其相类似的问题

正文

1.网络请求使用Activity当做回调,如:

public class MainActivity extends BaseActivity implements ObserverCallBack {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        HttpMethod.login("", "", this);//回调传入了Activity自身
    }
}

这时,如果用户按下了返回键,而网速又比较慢,如果30秒才会连接超时,则此时Activity会被网络请求给引用大约30秒然后才有可能被释放,由此造成了内存泄漏

2.解决方案1,取消请求:

我使用的是鸿洋大神的OkHttpUtils,可以在发起一个网络请求的时候设置一个tag,然后可以通过该tag来取消请求

OkHttpUtils.post().url(url).params(map).tag(activity)//设置tag
                .build().connTimeOut(20000)
                .execute(new StringCallback() {}

@Override
protected void onDestroy() {
    super.onDestroy();
    OkHttpUtils.getInstance().cancelTag(this);//在Activity的销毁回调中取消该Activity中的所有网络请求
}

但是这样还有一起其他的问题,比如在特定页面的onDestroy()中我想发一个记录的网络请求,但是我不需要知道结果,这样操作就会造成这个请求发不出去.

可能你会说我另设置一个tag即可,其实这样也可以,但是会提高复杂度,并容易出错,下面第二种解决方案

3.解决方案2,解耦回调:

我写了一个类来进行解耦回调,我会把用法写在下面(可以copy该类并将所有ObserverCallBack替换为你的回调类)

import android.text.TextUtils;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * 创    建:  lt  
 * 作    用:  解耦网络请求
 * 注意事项:
 */

public class CallBackTask<T> {

    private CallBackTask(boolean isM) {
        this.isM = isM;
        id = 0;
        map = new HashMap<>();
    }

    private boolean isM;//是否运行在多线程环境下(多线程会进行加锁操作,比较耗时)

    private long id;//唯一的key,依次递增
    /**
     * 存储CallBack的map
     * key为对象的内存地址+id
     * 值为对象的弱引用
     */
    private HashMap<String, WeakReference<T>> map;

    private final int ADD = 0,
            REMOVE_KEY = 1,
            REMOVE_OBJECT = 2,
            GET = 3,
            CLEAR = 4;

    private static CallBackTask<ObserverCallBack> task = new CallBackTask<>(false);

    /**
     * 获取单实例,由于网络请求在应用中基本一直会用到,所以直接使用饿汉单例
     * ps:如果有需求需要同时存其他种类的回调,可以再创建一个这样的方法,new的泛型改一下就行
     */
    public static CallBackTask<ObserverCallBack> getInstance() {
        return task;
    }

    /**
     * 将回调存储在此,并返回key
     */
    public String add(T value) {
        return isM ? doubleThread(ADD, value).toString() : singleThread(ADD, value).toString();
    }

    /**
     * 根据key移除引用
     */
    public void remove(String key) {
        if (isM)
            doubleThread(REMOVE_KEY, key);
        else
            singleThread(REMOVE_KEY, key);
    }

    /**
     * 根据对象的地址移除对象
     */
    public void remove(T value) {
        if (isM)
            doubleThread(REMOVE_OBJECT, value);
        else
            singleThread(REMOVE_OBJECT, value);
    }

    /**
     * 根据key取出回调,需要判断返回值是否为null,若为null可能已经销毁(该方法的实现在get后会自动remove,如果不想如此可以自行修改方法)
     */
    public T get(String key) {
        Object t = isM ? doubleThread(GET, key) : singleThread(GET, key);
        return t == null ? null : (T) t;
    }

    /**
     * 清理所有是null值的对象
     */
    public void cleanUpNull() {
        if (isM)
            doubleThread(CLEAR, null);
        else
            singleThread(CLEAR, null);
    }

    /**
     * 单线程方法,isM为false时执行,需要调用者保证调用的都是在同一个线程,否则可能抛出ConcurrentModificationException
     */
    private Object singleThread(int state, Object obj) {
        switch (state) {
            case ADD: {
                if (obj == null)
                    return "";
                String key = obj.toString() + id;
                id++;
                map.put(key, new WeakReference<>((T) obj));
                return key;
            }
            case REMOVE_KEY: {
                if (obj == null || map.size() == 0)
                    return null;
                String stringKey = obj.toString();
                if (TextUtils.isEmpty(stringKey))
                    return null;
                map.remove(stringKey);
                break;
            }
            case REMOVE_OBJECT: {
                if (obj == null || map.size() == 0)
                    return null;
                Iterator<String> iterator = map.keySet().iterator();
                while (iterator.hasNext()) {
                    String key = iterator.next();
                    if (key.contains(obj.toString()))
                        iterator.remove();
                }
                break;
            }
            case GET: {
                if (obj == null || map.size() == 0)
                    return null;
                String stringKey = obj.toString();
                if (TextUtils.isEmpty(stringKey))
                    return null;
                WeakReference<T> tWeakReference = map.get(stringKey);
                if (tWeakReference == null) {
                    map.remove(stringKey);
                    return null;
                }
                T callBack = tWeakReference.get();
                map.remove(stringKey);
                return callBack;
            }
            case CLEAR: {
                if (map.size() == 0)
                    return null;
                Iterator<Map.Entry<String, WeakReference<T>>> iterator = map.entrySet().iterator();
                while (iterator.hasNext()) {
                    Map.Entry<String, WeakReference<T>> entry = iterator.next();
                    if (entry.getValue() == null)
                        iterator.remove();
                    else if (entry.getValue().get() == null)
                        iterator.remove();
                }
                break;
            }
        }
        return null;
    }

    /**
     * 多线程方法,isM为true时执行,无需担心抛异常,但是效率略低(相对于单线程方法)
     */
    private synchronized Object doubleThread(int state, Object obj) {
        return singleThread(state, obj);
    }
}

在网络请求时,在把Activity当做回调传入的时候,可以先调用add()方法存进CallBackTask中,并获得key,然后发送handler消息使该方法栈弹栈,使其不直接引用Activity,然后在需要回调的时候通过key去CallBackTask中取出回调,如果为null表示该回调已经被销毁了,则不需要继续往下走,伪代码如下:

发送请求并传入回调

 public static void requestGetOrPost(final String url,
                                        final ObserverCallBack callBack) {
        Message msg = Message.obtain();
        msg.obj = new Object[]{
                url,
                CallBackTask.getInstance().add(callBack)//调用方法把回调存入CallBackTask中并获取到String类型的key
        };
        handler.sendMessage(msg);//发送handler消息,则该方法栈弹栈,不在引用这个callBack
    }

Handler获取到消息在调用方法发送网络请求

private static Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Object[] objects = (Object[]) msg.obj;
            start(
                    (String) objects[0],
                    (String) objects[1]
            );
        }
    };

网络请求成功后找到通过key找到回调并调用回调方法

                            @Override
                            public void onResponse(String response, int id) {//OkHttpUtils请求成功后的回调
                                try {
                                    ObserverCallBack callBack = CallBackTask.getInstance().get(callBackId);//根据key找到回调
                                    if (callBack == null)//判断回调如果是null则表示已经被销毁,无需往下走
                                        return;
                                    callBack.handleResult(response);
                                    } catch (Exception e) {
                                    }
                            }

如果Activity销毁时可以调用(可直接封装进Base)

    @Override
    protected void onDestroy() {
        super.onDestroy();
        CallBackTask.getInstance().remove(this);//移除该回调的所有引用
    }

这样就解决了上面提到的内存泄漏问题,并且不用担心在onDestroy()中执行网络请求被取消了

原理解析

原理就比较简单了,回调存储时使用软引用,然后使用HashMap来存储key和软引用,如果内存不足时,若只有该软引用引用时则会被gc给回收掉,减少造成oom的可能

其次,工具类里有一个参数来指定多线程和单线程模式,这是因为里面涉及到了map的循环,众所周知,在java数据结构循环的时候修改该数据结构会抛异常,所以设定了这个单线程和多线程的模式

如果帮到您的话请点一下赞

转发请附上原文链接谢谢

对Kotlin或KMP感兴趣的同学可以进Q群 101786950

如果这篇文章对您有帮助的话

可以扫码请我喝瓶饮料或咖啡(如果对什么比较感兴趣可以在备注里写出来)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值