Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ShadowContext中unregisterReceivery可能有问题 #865

Closed
SpaceQ-Z opened this issue Mar 24, 2022 · 16 comments · Fixed by #867 or #1108
Closed

ShadowContext中unregisterReceivery可能有问题 #865

SpaceQ-Z opened this issue Mar 24, 2022 · 16 comments · Fixed by #867 or #1108
Labels
bug Something isn't working

Comments

@SpaceQ-Z
Copy link
Contributor

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

image

@shifujun
Copy link
Collaborator

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

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

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

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

@SpaceQ-Z
Copy link
Contributor Author

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

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

shifujun added a commit to shifujun/Shadow that referenced this issue Mar 24, 2022
receiver的注册和反注册可能由不同的context完成。

fixup! d2f3995
fix Tencent#865
@shifujun
Copy link
Collaborator

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

@SpaceQ-Z
Copy link
Contributor Author

SpaceQ-Z commented Mar 24, 2022

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

直接运行sample-host不崩吗? 我这运行sample-host然后点启动插件就崩了(使用#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) 

@shifujun
Copy link
Collaborator

添加了一个 82fd262 修复了

@SpaceQ-Z
Copy link
Contributor Author

添加了一个 82fd262 修复了

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

@shifujun
Copy link
Collaborator

添加了一个 82fd262 修复了

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

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

@SpaceQ-Z
Copy link
Contributor Author

OK,那#866 我关闭了,用你这个了

shifujun added a commit that referenced this issue Mar 24, 2022
receiver的注册和反注册可能由不同的context完成。

fixup! d2f3995
fix #865
@SpaceQ-Z SpaceQ-Z reopened this Mar 24, 2022
@SpaceQ-Z
Copy link
Contributor Author

SpaceQ-Z commented Mar 24, 2022

#867 用了这个PR的代码使用示例工程不崩,但是接入到项目中就崩,还是提示
Receiver not registered: com.tencent.shadow.core.runtime.BroadcastReceiverWrapper。。。
我先用我那866号PR了。。。应该还是mReceiverWrapperMap中找不到BroadcastReceiver然后new 了一个BroadcastReceiverWrapper。反注册了BroadcastReceiverWrapper而不是BroadcastReceiver

@shifujun
Copy link
Collaborator

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

@SpaceQ-Z
Copy link
Contributor Author

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

是的这也是让我费解的地方。
#866 的修改就是 SpaceQ-Z@d2f3995 的unregisterReceiver方法之前的样,那样就没问题了。 >_<

@shifujun
Copy link
Collaborator

如果还有问题再打开吧。

@shifujun shifujun added the bug Something isn't working label Apr 29, 2022
@yanglw
Copy link

yanglw commented May 23, 2022

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

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

@shifujun shifujun reopened this May 23, 2022
@anjinok123
Copy link

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

@yanglw
Copy link

yanglw commented Sep 2, 2022

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

    ...
}

shifujun added a commit to shifujun/Shadow that referenced this issue Nov 19, 2022
Shadow对于BroadcastReceiver所需的处理只涉及onReceive方法传回的参数。
由于插件的BroadcastReceiver是通过正常的Context对象直接注册到系统中的,
所以对于系统来说这些BroadcastReceiver和宿主的其他BroadcastReceiver
没有什么区别。系统回调onReceive方法时传回的Context和Intent也都是宿主的。
其中需要把Context换回ShadowContext,Intent中的ClassLoader换回插件的。

Tencent#865#issuecomment-1134246493指出:系统控件 ViewFlipper
在 onAttachedToWindow() 方法中使用 Contenxt.registerReceiverAsUser
 方法进行广播注册;在 onDetachedFromWindow() 方法中使用
 Context.unregisterReceiver 方法进行取消广播注册。

本地提交改为通过Transform修改插件中的onReceive方法,在BroadcastReceiver
内部进行参数转换。从而避免引入新的类型,同时也不依赖如何注册和反注册Receiver。

fix Tencent#865
close Tencent#1105
shifujun added a commit to shifujun/Shadow that referenced this issue Nov 19, 2022
Shadow对于BroadcastReceiver所需的处理只涉及onReceive方法传回的参数。
由于插件的BroadcastReceiver是通过正常的Context对象直接注册到系统中的,
所以对于系统来说这些BroadcastReceiver和宿主的其他BroadcastReceiver
没有什么区别。系统回调onReceive方法时传回的Context和Intent也都是宿主的。
其中需要把Context换回ShadowContext,Intent中的ClassLoader换回插件的。

Tencent#865#issuecomment-1134246493指出:系统控件 ViewFlipper
在 onAttachedToWindow() 方法中使用 Contenxt.registerReceiverAsUser
 方法进行广播注册;在 onDetachedFromWindow() 方法中使用
 Context.unregisterReceiver 方法进行取消广播注册。

本地提交改为通过Transform修改插件中的onReceive方法,在BroadcastReceiver
内部进行参数转换。从而避免引入新的类型,同时也不依赖如何注册和反注册Receiver。

fix Tencent#865
close Tencent#1105
shifujun added a commit to shifujun/Shadow that referenced this issue Nov 19, 2022
Shadow对于BroadcastReceiver所需的处理只涉及onReceive方法传回的参数。
由于插件的BroadcastReceiver是通过正常的Context对象直接注册到系统中的,
所以对于系统来说这些BroadcastReceiver和宿主的其他BroadcastReceiver
没有什么区别。系统回调onReceive方法时传回的Context和Intent也都是宿主的。
其中需要把Context换回ShadowContext,Intent中的ClassLoader换回插件的。

Tencent#865#issuecomment-1134246493指出:系统控件 ViewFlipper
在 onAttachedToWindow() 方法中使用 Contenxt.registerReceiverAsUser
 方法进行广播注册;在 onDetachedFromWindow() 方法中使用
 Context.unregisterReceiver 方法进行取消广播注册。

本地提交改为通过Transform修改插件中的onReceive方法,在BroadcastReceiver
内部进行参数转换。从而避免引入新的类型,同时也不依赖如何注册和反注册Receiver。

fix Tencent#865
close Tencent#1105
shifujun added a commit that referenced this issue Nov 19, 2022
Shadow对于BroadcastReceiver所需的处理只涉及onReceive方法传回的参数。
由于插件的BroadcastReceiver是通过正常的Context对象直接注册到系统中的,
所以对于系统来说这些BroadcastReceiver和宿主的其他BroadcastReceiver
没有什么区别。系统回调onReceive方法时传回的Context和Intent也都是宿主的。
其中需要把Context换回ShadowContext,Intent中的ClassLoader换回插件的。

#865#issuecomment-1134246493指出:系统控件 ViewFlipper
在 onAttachedToWindow() 方法中使用 Contenxt.registerReceiverAsUser
 方法进行广播注册;在 onDetachedFromWindow() 方法中使用
 Context.unregisterReceiver 方法进行取消广播注册。

本地提交改为通过Transform修改插件中的onReceive方法,在BroadcastReceiver
内部进行参数转换。从而避免引入新的类型,同时也不依赖如何注册和反注册Receiver。

fix #865
close #1105
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
4 participants