Shadow icon indicating copy to clipboard operation
Shadow copied to clipboard

ShadowContext中unregisterReceivery可能有问题

Open SpaceQ-Z opened this issue 3 years ago • 16 comments

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

image

SpaceQ-Z avatar Mar 24 '22 03:03 SpaceQ-Z

反注册的时候未在mReceiverWrapperMap中的的还会往mReceiverWrapperMap中put一个,且反注册的时候没有remove掉相应的Receiver

设计如此。而且这里是WrapperMap,即便有bug,影响也应该是内存泄漏。不应该出现对应Wrapper没有注册的情况。

WrapperMap保存的是receiver和wrapper两个对象的映射关系,这个映射关系和receiver是否被注册到系统了没有关系。 这里采用WeakHashMap实现的原因就是只要receiver还被业务代码或者系统持有,那就是还有可能需要被重新映射为之前一样的wrapper对象。

最好还是在sample上复现一下有问题的场景,否则我暂时理解不了bug是怎么产生的。

shifujun avatar Mar 24 '22 03:03 shifujun

反注册的时候未在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后就不会崩了

SpaceQ-Z avatar Mar 24 '22 03:03 SpaceQ-Z

反注册的时候未在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

SpaceQ-Z avatar Mar 24 '22 06:03 SpaceQ-Z

我猜应该是这个错误导致的:#867 可以测一下能不能复现问题了。

shifujun avatar Mar 24 '22 06:03 shifujun

我猜应该是这个错误导致的:#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) 

SpaceQ-Z avatar Mar 24 '22 07:03 SpaceQ-Z

添加了一个 82fd26231156b3565e2cd379913cddf89cbbc9cb 修复了

shifujun avatar Mar 24 '22 08:03 shifujun

添加了一个 82fd262 修复了

好了,另外反注册时mReceiverWrapperMap不移除一下么,要不然Map长度会一直增长

SpaceQ-Z avatar Mar 24 '22 08:03 SpaceQ-Z

添加了一个 82fd262 修复了

好了,另外反注册时mReceiverWrapperMap不移除一下么,要不然Map长度会一直增长

不需要。这个map跟receiver是否注册了没有关系。它是弱引用实现的,只要有其他强引用,就说明还有可能需要转换wrapper对象。如果不需要再转换了,外面一定也没有强引用了,也就自动释放了。

shifujun avatar Mar 24 '22 08:03 shifujun

OK,那https://github.com/Tencent/Shadow/pull/866 我关闭了,用你这个了

SpaceQ-Z avatar Mar 24 '22 08:03 SpaceQ-Z

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

SpaceQ-Z avatar Mar 24 '22 10:03 SpaceQ-Z

如果按你的#866 修改没有问题的话,那应该是原本的BroadcastReceiver被直接注册到系统过。那它是怎么注册的呢?

shifujun avatar Mar 25 '22 02:03 shifujun

如果按你的#866 修改没有问题的话,那应该是原本的BroadcastReceiver被直接注册到系统过。那它是怎么注册的呢?

是的这也是让我费解的地方。 #866 的修改就是 https://github.com/SpaceQ-Z/Shadow/commit/d2f3995dce2a4340d0b45af6505c5f6d819894b1 的unregisterReceiver方法之前的样,那样就没问题了。 >_<

SpaceQ-Z avatar Mar 25 '22 07:03 SpaceQ-Z

如果还有问题再打开吧。

shifujun avatar Apr 29 '22 06:04 shifujun

系统控件 ViewFlipper 在 onAttachedToWindow() 方法中使用 Contenxt.registerReceiverAsUser 方法进行广播注册;在 onDetachedFromWindow() 方法中使用 Context.unregisterReceiver 方法进行取消广播注册。

ShadowContext 没有重写 registerReceiverAsUser 方法进行拦截,因此系统中被注册的广播为原始的 Receiver 。在 ShadowContext.unregisterReceiver 方法中,通过 ShadowContext.receiverToWrapper 方法将原市 Recevier 转为 Wrapper ,并取消注册 Wrapper ,从而导致异常。

yanglw avatar May 23 '22 06:05 yanglw

系统控件 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

anjinok123 avatar Sep 02 '22 07:09 anjinok123

我的修改方案:新增加了一个 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();
        }
    }

    ...
}

yanglw avatar Sep 02 '22 07:09 yanglw