4

I am creating an Android WebView in the background using the Application Context so that it is loaded and ready when I need to display it. I attach it to my Activity using addView when it is needed. This mostly works great, however when I try to open an HTML select dropdown I get a crash:

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)

I'm assuming this is because I created the WebView with the ApplicationContext. My question is: is there any way to work around this issue? Is there any way to "attach" the existing WebView to a different Activity or Window so that the Dialog can be created? Is there any way to "hack" this together using Reflection by changing the Context at runtime?

EDIT: As suggested below I tested using a MutableContextWrapper and it appears to solve this problem nicely!

Doron Yakovlev Golani
  • 5,188
  • 9
  • 36
  • 60
BenV
  • 1,196
  • 1
  • 12
  • 19

1 Answers1

3

So, we've actually run into the same issue (using a WebView in a retained Fragment to prevent page reloads, while not leaking an Activity context) and the short answer seems to be that there's no really clean way to do so. I've even tried a custom WebView subclass that returned the host Activity's window token instead of the WebView's own token, to no success.

We ended up using reflection, as you suggested, to modify the underlying context:

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;
    }
}

which just sets the mContext field on the View instance and returns true if it was successfully modified. However, I saw another suggestion recently (and I haven't tested this, so YMMV) to use MutableContextWrapper. So you would inflate the WebView with the Activity Context, wrapped in a MutableContextWrapper. Then, when you need to release the previous reference, you'd cast that WebView's Context to a MutableContextWrapper, and then set the base Context to the new Activity. So something like this to inflate the layout:

MutableContextWrapper contextWrapper = new MutableContextWrapper(activity);

WebView webView = (WebView) LayoutInflater.from(contextWrapper)
        .inflate(R.layout.your_webview_layout, theParent, false);

Then, to re-attach to a new Activity:

if (webView.getContext() instanceof MutableContextWrapper) {
    ((MutableContextWrapper) webView.getContext()).setBaseContext(newActivity);
}
Kevin Coppock
  • 133,643
  • 45
  • 263
  • 274
  • I had tested a solution using reflection after writing this question and it seemed to work (although I had to go a few levels deeper for Android 4.4 with the Chromium WebView). Thanks for pointing out MutableContextWrapper, I was not aware of that class and it looks like it could do the trick! I'll try that out and report back. – BenV Jan 19 '15 at 23:21
  • 1
    MutableContextWrapper appears to solve this nicely, I'll update the question with that fact, thanks again! – BenV Jan 20 '15 at 18:27