ShadowContext中unregisterReceivery可能有问题
``fix(core.runtime): BroadcastReceiver与BroadcastReceiverWapper映射Map采用弱引用实现 这次的提交在反注册的时候应该是有问题的 起因是我这里报了一个异常Receiver not registered: com.tencent.shadow.core.runtime.BroadcastReceiverWrapper 然后看代码,反注册的时候未在mReceiverWrapperMap中的的还会往mReceiverWrapperMap中put一个,且反注册的时候没有remove掉相应的Receiver

反注册的时候未在mReceiverWrapperMap中的的还会往mReceiverWrapperMap中put一个,且反注册的时候没有remove掉相应的Receiver
设计如此。而且这里是WrapperMap,即便有bug,影响也应该是内存泄漏。不应该出现对应Wrapper没有注册的情况。
WrapperMap保存的是receiver和wrapper两个对象的映射关系,这个映射关系和receiver是否被注册到系统了没有关系。 这里采用WeakHashMap实现的原因就是只要receiver还被业务代码或者系统持有,那就是还有可能需要被重新映射为之前一样的wrapper对象。
最好还是在sample上复现一下有问题的场景,否则我暂时理解不了bug是怎么产生的。
反注册的时候未在mReceiverWrapperMap中的的还会往mReceiverWrapperMap中put一个,且反注册的时候没有remove掉相应的Receiver
设计如此。而且这里是WrapperMap,即便有bug,影响也应该是内存泄漏。不应该出现对应Wrapper没有注册的情况。
WrapperMap保存的是receiver和wrapper两个对象的映射关系,这个映射关系和receiver是否被注册到系统了没有关系。 这里采用WeakHashMap实现的原因就是只要receiver还被业务代码或者系统持有,那就是还有可能需要被重新映射为之前一样的wrapper对象。
最好还是在sample上复现一下有问题的场景,否则我暂时理解不了bug是怎么产生的。
之前那样写只会执行 super.unregisterReceiver(wrapper);中而不会执行super.unregisterReceiver(receiver);。我先在test里复现下,实际工程使用中就是这样的。改了866号PR后就不会崩了
反注册的时候未在mReceiverWrapperMap中的的还会往mReceiverWrapperMap中put一个,且反注册的时候没有remove掉相应的Receiver
设计如此。而且这里是WrapperMap,即便有bug,影响也应该是内存泄漏。不应该出现对应Wrapper没有注册的情况。 WrapperMap保存的是receiver和wrapper两个对象的映射关系,这个映射关系和receiver是否被注册到系统了没有关系。 这里采用WeakHashMap实现的原因就是只要receiver还被业务代码或者系统持有,那就是还有可能需要被重新映射为之前一样的wrapper对象。 最好还是在sample上复现一下有问题的场景,否则我暂时理解不了bug是怎么产生的。
之前那样写只会执行 super.unregisterReceiver(wrapper);中而不会执行super.unregisterReceiver(receiver);。我先在test里复现下,实际工程使用中就是这样的。改了866号PR后就不会崩了
我用示例中复出不出来>_<,场景就是动态广播在dialog中注册,activity中有多个dialog,dialog中的广播是onshowlistener中注册,ondismisslistener中反注册。使用示例中的代码虽然不会崩了。dialog使用的都是同一个context引用,也就是同一个mReceiverWrapperMap,这样activity只要不销毁,mReceiverWrapperMap的长度只增不减。我猜测我之前那个崩是因为某个原因没有把receiver添加到mReceiverWrapperMap中,然后反注册时调用了receiverToWrapper方法,然后又往mReceiverWrapperMap里添加了一个,然后反注册了BroadcastReceiverWrapper,而非原始receiver
我猜应该是这个错误导致的:#867 可以测一下能不能复现问题了。
我猜应该是这个错误导致的:#867 可以测一下能不能复现问题了。
直接运行sample-host不崩吗? 我这运行sample-host然后点启动插件就崩了(使用https://github.com/Tencent/Shadow/pull/867
中的代码)
2022-03-24 15:31:18.617 12176-12230/com.tencent.shadow.sample.host E/AndroidRuntime: FATAL EXCEPTION: pool-3-thread-1
Process: com.tencent.shadow.sample.host, PID: 12176
java.lang.RuntimeException: android.os.DeadObjectException
at com.tencent.shadow.sample.manager.SamplePluginManager$1.run(SamplePluginManager.java:142)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
Caused by: android.os.DeadObjectException
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:503)
at com.tencent.shadow.dynamic.manager.BinderPluginLoader.callApplicationOnCreate(BinderPluginLoader.java:79)
at com.tencent.shadow.sample.manager.FastPluginManager.callApplicationOnCreate(FastPluginManager.java:121)
at com.tencent.shadow.sample.manager.SamplePluginManager$1.run(SamplePluginManager.java:128)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
添加了一个 82fd26231156b3565e2cd379913cddf89cbbc9cb 修复了
添加了一个 82fd262 修复了
好了,另外反注册时mReceiverWrapperMap不移除一下么,要不然Map长度会一直增长
不需要。这个map跟receiver是否注册了没有关系。它是弱引用实现的,只要有其他强引用,就说明还有可能需要转换wrapper对象。如果不需要再转换了,外面一定也没有强引用了,也就自动释放了。
OK,那https://github.com/Tencent/Shadow/pull/866 我关闭了,用你这个了
https://github.com/Tencent/Shadow/pull/867 用了这个PR的代码使用示例工程不崩,但是接入到项目中就崩,还是提示 Receiver not registered: com.tencent.shadow.core.runtime.BroadcastReceiverWrapper。。。 我先用我那866号PR了。。。应该还是mReceiverWrapperMap中找不到BroadcastReceiver然后new 了一个BroadcastReceiverWrapper。反注册了BroadcastReceiverWrapper而不是BroadcastReceiver
如果按你的#866 修改没有问题的话,那应该是原本的BroadcastReceiver被直接注册到系统过。那它是怎么注册的呢?
如果按你的#866 修改没有问题的话,那应该是原本的BroadcastReceiver被直接注册到系统过。那它是怎么注册的呢?
是的这也是让我费解的地方。 #866 的修改就是 https://github.com/SpaceQ-Z/Shadow/commit/d2f3995dce2a4340d0b45af6505c5f6d819894b1 的unregisterReceiver方法之前的样,那样就没问题了。 >_<
如果还有问题再打开吧。
系统控件 ViewFlipper 在 onAttachedToWindow() 方法中使用 Contenxt.registerReceiverAsUser 方法进行广播注册;在 onDetachedFromWindow() 方法中使用 Context.unregisterReceiver 方法进行取消广播注册。
ShadowContext 没有重写 registerReceiverAsUser 方法进行拦截,因此系统中被注册的广播为原始的 Receiver 。在 ShadowContext.unregisterReceiver 方法中,通过 ShadowContext.receiverToWrapper 方法将原市 Recevier 转为 Wrapper ,并取消注册 Wrapper ,从而导致异常。
系统控件 ViewFlipper 在
onAttachedToWindow()方法中使用Contenxt.registerReceiverAsUser方法进行广播注册;在onDetachedFromWindow()方法中使用Context.unregisterReceiver方法进行取消广播注册。ShadowContext 没有重写
registerReceiverAsUser方法进行拦截,因此系统中被注册的广播为原始的 Receiver 。在ShadowContext.unregisterReceiver方法中,通过ShadowContext.receiverToWrapper方法将原市 Recevier 转为 Wrapper ,并取消注册 Wrapper ,从而导致异常。
我也遇到了同样的问题 本来也想重写 registerReceiverAsUser方法,奈何已经变成了隐藏api,无法被重写。 最后改写了ShadowContext unregisterReceiver方法中获取 Receiver的方法,既然是注销 则该广播应该是已经存在,如果调用receiverToWrapper方法,可能会重新new 一个新的Wrapper,此时若是直接传入这个新的Wrapper则会在注销中找不到这个广播,因为它就没注册过;所以这个时候如果从map中取到的wrapper为空,可以直接返回传入的Receiver
我的修改方案:新增加了一个 getReceiverWrapperFromMap 方法,在 ShadowContext.unregisterReceiver 中通过该方法获取 Wrapper ,不修改其他方法原始逻辑。
public class ShadowContext extends SubDirContextThemeWrapper {
...
@Override
public void unregisterReceiver(BroadcastReceiver receiver) {
BroadcastReceiverWrapper wrapper = getReceiverWrapperFromMap(receiver);
if (wrapper != null) {
super.unregisterReceiver(wrapper);
} else {
super.unregisterReceiver(receiver);
}
}
private BroadcastReceiverWrapper getReceiverWrapperFromMap(BroadcastReceiver receiver) {
if (receiver == null) {
return null;
}
synchronized (mReceiverWrapperMap) {
WeakReference<BroadcastReceiverWrapper> weakReference
= mReceiverWrapperMap.get(receiver);
return weakReference == null ? null : weakReference.get();
}
}
...
}