Java之禅——软/弱引用踩坑记

内推

【长期有效】欢迎加入字节跳动我的团队:内推链接

看到这篇文章,想必已经弱应用/软引用有一定了解了。且平时也会用它来干些什么,比如防止内存泄露等。


下面请先阅读一段manager代码:

/**
 * 生命周期很长的一个类,例如管理类这种单例
 */
public class LongLifeManager {
    public static final String TAG = "LongLifeManager";

    private List<WeakReference<ILifeCallback>> mILifeCallbacks = new ArrayList<>();

    /**
     * 单例
     */
    private static final class SingleHolder {
        private static final LongLifeManager INSTANCE = new LongLifeManager();
    }

    private LongLifeManager() {
    }

    public static LongLifeManager getInstance() {
        return SingleHolder.INSTANCE;
    }

    /**
     * 初始化这个管理类,这里模拟一个很长的初始化过程
     */
    public void init() {
        Handler handler = new Handler();
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                notifyInit(); //通知初始化完成

            }
        }, 10000);
    }

    /**
     * 添加监听,弱引用方式,防止泄露
     */
    public void addCallback(ILifeCallback callback) {
        mILifeCallbacks.add(new WeakReference<>(callback));
    }


    /**
     * 移除监听
     */
    public void removeCallback(ILifeCallback callback) {
        for (WeakReference<ILifeCallback> wc : mILifeCallbacks) {
            ILifeCallback c = wc.get();
            if (c == callback) {
                mILifeCallbacks.remove(wc);
            }
        }
    }

    /**
     * 通知初始化完成
     */
    private void notifyInit() {
        for (WeakReference<ILifeCallback> c : mILifeCallbacks) {
            ILifeCallback cg = c.get();
            if (cg == null) {
                continue;
            }

            cg.onInit();
        }
    }

    public interface ILifeCallback {
        void onInit(); //初始化完成
    }
}

是的,这个LongLifeManager用弱应用包装了监听器,防止了callback对象的泄露.
然而,悄悄踩坑没发现,当我们使用写法1监听时,我们期待的onInit方法,一定会回调么?

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

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

        //写法1
        LongLifeManager.getInstance().addCallback(new LongLifeManager.ILifeCallback() {
            @Override
            public void onInit() {
                //do something
                Log.d(TAG, "onInit: ");
            }
        });
        LongLifeManager.getInstance().init();
    }
}

答案是否定的,因为实践过了,初始化期间(LongLifeManager是模拟10s),只要虚拟机进行gc(可以手动gc进行试验),这个callback就gg了,哈哈哈。
老司机秒懂的就不用往下看了,不过我当时是很惊讶的。


思路大致如下:
1.不对啊,callback是个匿名内部类,activity不应该强引用了这个callback吗,callback的命应该跟随activity的生命周期跑啊。仔细想想就会发现,错了。这种写法,与写法2等效:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

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

        //写法2
        LongLifeManager.ILifeCallback iLifeCallback = new LongLifeManager.ILifeCallback() {
            @Override
            public void onInit() {
                Log.d(TAG, "onInit: ");
            }
        };
		LongLifeManager.getInstance().addCallback(iLifeCallback);
	    LongLifeManager.getInstance().init();
    }
}

这就很明了了,写法1和2中,这个callback是个局部内部类,activity并不引用它,只可以理解为方法栈区有个强引用引用了它而已,方法跑完了,这个引用就丢失了,自然可以被回收。但这个callback强应用了activity,这是毋庸置疑了,概念不能搞混。


2.当然,理解了第一点,改进方案就产生了,写法3:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

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

        LongLifeManager.getInstance().addCallback(mILifeCallback);
        LongLifeManager.getInstance().init();
    }

    //写法3
    LongLifeManager.ILifeCallback mILifeCallback = new LongLifeManager.ILifeCallback() {
        @Override
        public void onInit() {
            Log.d(TAG, "onInit: ");
        }
    };

}

符合预期,这个activity强引用了callback了,所以是callback的生命周期可以大致理解为跟随activity。当然,你也可以直接使activity implements 这个callback。


3.引发思考:虽然通过研究manager中callback管理方式,解决了这个生命周期导致的回调问题,但是生产中,manager作为服务方,addListener方法并不一定对外暴露,或者说对用户不透明,用户通过写法1的方式获取回调的写法也很正常,而用户将会对获取不到回调产生很大的困惑。所以这种弱引用包装callback的方式还有待考察,虽然一定程度上解决了用户忘记removeListener带来的内存泄漏隐患,同时也埋下了这种坑。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值