Android - 如何在使用应用程序上下文创建后将 WebView 附加到 Activity
Android - How can I Attach a WebView to an Activity after creating it with the Application Context
我正在使用应用程序上下文在后台创建一个 Android WebView,以便在我需要显示它时加载并准备就绪。我在需要时使用 addView 将它附加到我的 Activity。这在大多数情况下都很好用,但是当我尝试打开 HTML select 下拉菜单时,我遇到了崩溃:
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
at android.view.ViewRootImpl.setView(ViewRootImpl.java:540)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:259)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69)
at android.app.Dialog.show(Dialog.java:286)
at com.android.org.chromium.content.browser.input.SelectPopupDialog.show(SelectPopupDialog.java:217)
at com.android.org.chromium.content.browser.ContentViewCore.showSelectPopup(ContentViewCore.java:2413)
at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method)
at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5017)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
at dalvik.system.NativeStart.main(Native Method)
我假设这是因为我使用 ApplicationContext 创建了 WebView。我的问题是:有没有办法解决这个问题?有没有办法将现有的 WebView "attach" 更改为不同的 Activity 或 Window 以便创建对话框?有什么方法可以 "hack" 通过在运行时更改上下文来使用反射将它们一起使用吗?
编辑:按照下面的建议,我使用 MutableContextWrapper 进行了测试,它似乎很好地解决了这个问题!
所以,我们实际上 运行 遇到了同样的问题(在保留的 Fragment
中使用 WebView
来防止页面重新加载,同时不泄漏 Activity
上下文),简短的回答似乎是没有真正干净的方法可以做到这一点。我什至尝试了一个自定义 WebView
子类,该子类返回主机 Activity
的 window 令牌而不是 WebView
自己的令牌,但没有成功。
正如您所建议的,我们最终使用反射来修改底层上下文:
public static boolean setContext(View v, Context ctx) {
try {
final Field contextField = View.class.getDeclaredField("mContext");
contextField.setAccessible(true);
contextField.set(v, ctx);
return (v.getContext() == ctx);
} catch (IllegalAccessException | NoSuchFieldException e) {
Log.e(TAG, String.valueOf(e), e);
return false;
}
}
仅在 View
实例上设置 mContext
字段,如果修改成功则 returns 为真。然而,我最近看到另一个建议(我还没有测试过这个,所以YMMV)使用MutableContextWrapper
。所以你会用 Activity
Context
给 WebView
充气,包裹在 MutableContextWrapper
中。然后,当您需要释放之前的引用时,您可以将 WebView
的 Context
转换为 MutableContextWrapper
,然后将基础 Context
设置为新的 Activity
。所以像这样的东西来膨胀布局:
MutableContextWrapper contextWrapper = new MutableContextWrapper(activity);
WebView webView = (WebView) LayoutInflater.from(contextWrapper)
.inflate(R.layout.your_webview_layout, theParent, false);
然后,重新附加到一个新的Activity
:
if (webView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) webView.getContext()).setBaseContext(newActivity);
}
我正在使用应用程序上下文在后台创建一个 Android WebView,以便在我需要显示它时加载并准备就绪。我在需要时使用 addView 将它附加到我的 Activity。这在大多数情况下都很好用,但是当我尝试打开 HTML select 下拉菜单时,我遇到了崩溃:
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application at android.view.ViewRootImpl.setView(ViewRootImpl.java:540) at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:259) at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:69) at android.app.Dialog.show(Dialog.java:286) at com.android.org.chromium.content.browser.input.SelectPopupDialog.show(SelectPopupDialog.java:217) at com.android.org.chromium.content.browser.ContentViewCore.showSelectPopup(ContentViewCore.java:2413) at com.android.org.chromium.base.SystemMessageHandler.nativeDoRunLoopOnce(Native Method) at com.android.org.chromium.base.SystemMessageHandler.handleMessage(SystemMessageHandler.java:27) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:136) at android.app.ActivityThread.main(ActivityThread.java:5017) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595) at dalvik.system.NativeStart.main(Native Method)
我假设这是因为我使用 ApplicationContext 创建了 WebView。我的问题是:有没有办法解决这个问题?有没有办法将现有的 WebView "attach" 更改为不同的 Activity 或 Window 以便创建对话框?有什么方法可以 "hack" 通过在运行时更改上下文来使用反射将它们一起使用吗?
编辑:按照下面的建议,我使用 MutableContextWrapper 进行了测试,它似乎很好地解决了这个问题!
所以,我们实际上 运行 遇到了同样的问题(在保留的 Fragment
中使用 WebView
来防止页面重新加载,同时不泄漏 Activity
上下文),简短的回答似乎是没有真正干净的方法可以做到这一点。我什至尝试了一个自定义 WebView
子类,该子类返回主机 Activity
的 window 令牌而不是 WebView
自己的令牌,但没有成功。
正如您所建议的,我们最终使用反射来修改底层上下文:
public static boolean setContext(View v, Context ctx) {
try {
final Field contextField = View.class.getDeclaredField("mContext");
contextField.setAccessible(true);
contextField.set(v, ctx);
return (v.getContext() == ctx);
} catch (IllegalAccessException | NoSuchFieldException e) {
Log.e(TAG, String.valueOf(e), e);
return false;
}
}
仅在 View
实例上设置 mContext
字段,如果修改成功则 returns 为真。然而,我最近看到另一个建议(我还没有测试过这个,所以YMMV)使用MutableContextWrapper
。所以你会用 Activity
Context
给 WebView
充气,包裹在 MutableContextWrapper
中。然后,当您需要释放之前的引用时,您可以将 WebView
的 Context
转换为 MutableContextWrapper
,然后将基础 Context
设置为新的 Activity
。所以像这样的东西来膨胀布局:
MutableContextWrapper contextWrapper = new MutableContextWrapper(activity);
WebView webView = (WebView) LayoutInflater.from(contextWrapper)
.inflate(R.layout.your_webview_layout, theParent, false);
然后,重新附加到一个新的Activity
:
if (webView.getContext() instanceof MutableContextWrapper) {
((MutableContextWrapper) webView.getContext()).setBaseContext(newActivity);
}