【问题标题】:Android - How can I Attach a WebView to an Activity after creating it with the Application ContextAndroid - 如何在使用应用程序上下文创建 WebView 后将其附加到 Activity
【发布时间】:2015-01-17 00:07:33
【问题描述】:

我正在使用应用程序上下文在后台创建一个 Android WebView,以便在我需要显示它时加载并准备好它。我在需要时使用 addView 将它附加到我的活动中。这通常效果很好,但是当我尝试打开 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 以便可以创建 Dialog?有没有办法通过在运行时更改上下文来使用反射来“破解”它?

编辑:如下所示,我使用MutableContextWrapper 进行了测试,它似乎很好地解决了这个问题!

【问题讨论】:

    标签: android webview mutable-context-wrapper


    【解决方案1】:

    所以,我们实际上遇到了同样的问题(在保留的 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。但是,我最近看到了另一个建议(我还没有测试过,所以 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);
    }
    

    【讨论】:

    • 我在写完这个问题后使用反射测试了一个解决方案,它似乎有效(尽管我必须使用 Chromium WebView 为 Android 4.4 更深入一些级别)。感谢您指出 MutableContextWrapper,我不知道该类,它看起来可以解决问题!我会尝试并报告。
    • MutableContextWrapper 似乎很好地解决了这个问题,我会用这个事实更新问题,再次感谢!
    猜你喜欢
    • 1970-01-01
    • 2013-09-22
    • 2014-06-24
    • 1970-01-01
    • 2018-09-13
    • 1970-01-01
    • 1970-01-01
    • 2015-03-23
    • 2013-05-10
    相关资源
    最近更新 更多