Android - 如何在使用应用程序上下文创建WebView后将其附加到Activity?

4

我正在使用应用程序上下文在后台创建一个Android WebView,以便在需要显示它时加载并准备好。当需要时,我使用addView将其附加到我的Activity。这通常非常有效,但是当我尝试打开HTML选择下拉框时,会崩溃:

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“附加”到不同的Activity或Window,以便可以创建对话框?是否有任何方法可以使用Reflection在运行时更改上下文来“hack”这个问题?
编辑:如下所建议,我尝试使用MutableContextWrapper,它似乎很好地解决了这个问题!
1个回答

3
所以,我们实际上遇到了同样的问题(在保留的Fragment中使用WebView来防止页面重新加载,同时不泄露Activity上下文),简而言之,并没有真正干净的方法可以这样做。我甚至尝试过一个自定义的WebView子类,它返回宿主Activity的窗口令牌而不是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字段,如果成功修改,则返回true。然而,我最近看到了另一种建议(并且我没有测试过,所以效果可能会有所不同),就是使用MutableContextWrapper。因此,您将使用包装在MutableContextWrapper中的ActivityContext来填充WebView。然后,在需要释放先前引用时,将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);
}

我在写完这个问题后,使用了反射测试了一个解决方案,似乎可以运行(虽然对于带有Chromium WebView的Android 4.4我还需要深入几个层级)。感谢指出MutableContextWrapper,我之前不知道这个类,看起来它可以解决问题!我会尝试一下并汇报结果。 - BenV
1
MutableContextWrapper似乎很好地解决了这个问题,我会在问题中更新这个事实,再次感谢! - BenV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接