内推
【长期有效】欢迎加入字节跳动我的团队:内推链接
看到这篇文章,想必已经弱应用/软引用有一定了解了。且平时也会用它来干些什么,比如防止内存泄露等。
下面请先阅读一段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带来的内存泄漏隐患,同时也埋下了这种坑。