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 ContextWebView 充气,包裹在 MutableContextWrapper 中。然后,当您需要释放之前的引用时,您可以将 WebViewContext 转换为 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);
}