12

I have a WebView I'm loading in an activity in order to have it preloaded so that it pops up immediately in a different Activity (launched from the first).

The problem is that in order to instantiate a WebView, I have to pass in a Context, in this case it's the first mentioned above.

So it works great, and the second Activity shows the WebView just fine. The problem is that if I click a <select> dropdown in the WebView, its selector dialog shows up UNDER the WebView. It feels like the select doesn't work at all until you hit the back button and briefly see the selection dialog just before you return to the parent activity.

It seems as though when I append the WebView to the layout in the second activity, it's modals get attached to that activity's window, but the WebView itself is attached to the parent activity's window, so it shows in a higher point in the hierarchy.

How can I possibly change the Context of the WebView after it's been instantiated?

This is a very difficult problem to solve -- I have to create the WebViews before the activity is started, but I also need the selection dialogs to work.

Please if anyone can give me some insights here I'd greatly appreciate it.

This is for an SDK project, so I will not have access to the parent activity. Also, saveState isn't working, because the bulk of what is shown in the WebView is generated by JavaScript, and the full DOM stack doesn't transfer.

rupps
  • 9,712
  • 4
  • 55
  • 95
crimulus
  • 123
  • 1
  • 4
  • 1
    "I have to create the WebViews before the activity is started" -- why? Yes, `WebView` is slow to instantiate. That doesn't mean that creating a `WebView` in one activity, with an eye towards using it in another activity, is going to work. Why not use fragments, and have all of that in one single activity? Or, just show the `WebView` when it is ready in the original activity. Why do you feel that you need two activities? – CommonsWare Dec 06 '14 at 00:23
  • Because this is a drop in activity as part of an SDK. I won't have access to the activity that launches my activity in production environments, so I have to store the web views in a static class and add them to my layout once my activity has started. I would love to debate the merits of the implementation, but I have to find a way to make this work, and I can't start a hidden activity, and I can't copy contents from one WebView to another preserving the full DOM. – crimulus Dec 06 '14 at 20:29

2 Answers2

16

You can try to create the WebView with a MutableContextWrapper:

MutableContextWrapper mMutableContext=new MutableContextWrapper(context);
WebView mWebView=new WebView(mMutableContext);

and later on you could do

mMutableContext.setBaseContext(newcontext);

But ...

  • WebView is a very complex component that will probably be using the passed context to create other objects like Handlers. WebView probably uses those handlers to post stuff to the original UI thread, so at the end you'll probably have a View with a mix of contexts, you know, a double memory leak (if it ever works properly)

  • Webview spans at least 1 thread "webcore" that is where the action happens and is also in constant communication with the original UI thread with ... handlers? through the original context? who knows!

  • There are even 2 different webview engines: Kitkat is chromium-based while jelly bean and previous versions use AOSP/WebView. So you have an additional breaking point.

  • The reasons you state are not strong enough imho. WebView is not that slow. If the app you load is, try to optimize it. There are a lot of things you can do for that, like loading the HTML & graphics from internal assets.

rupps
  • 9,712
  • 4
  • 55
  • 95
  • We have done some pretty substantial optimizations, but with network bottlenecks we're still seeing load times of 1-1.5 seconds, and we really want to make it feel as native as possible. We were able to accomplish it in iOS, so I have no choice but to find some way to make it work in Android. If I absolutely have to, I will disable the native select dialog in favor of a JS-based one, but I'd prefer not to just for maintenance purposes. That MutableContext is a fantastic suggestion and I'll definitely try it out. – crimulus Dec 06 '14 at 08:26
  • I am open to other implementation ideas, but somehow I've just got to have the WebView loaded in memory before the activity that actually displays it appears, and I can't seem to find a way to load the WebView in the background. If I could find a way to start an activity without actually showing anything, then just create a way to _show_ that activity at some later point, that would also work, but I haven't been able to figure that out either. Fragments have been suggested, but I still have to have an Activity context, so the same issue is there. – crimulus Dec 06 '14 at 08:30
  • My humble advice, without deeply knowing your app, would be to focus in the root of the problem, and that's those 1.5secs of network bottlenecks. Can't you somehow cache that content? Bring all content to assets? download it then serve it through OnInterceptUrlLoad() ? Any of those solutions will be way more scalable, I'd say WebView is probably the worst View you want to mess its Context with. – rupps Dec 06 '14 at 16:25
  • I agree wholeheartedly, but it's what I have to work with. We definitely need to do the preloading, because even a 500ms delay detracts from the native feel we're trying to accomplish (and when you think about things being served over SSL with at least 2 synchronous calls, 1.5 seconds is pretty reasonable). That being said, it does not appear that the MutableContextWrapper solution works. It's a fantastic idea, but even after implementing it the select options appear below the WebView :( – crimulus Dec 06 '14 at 20:22
  • !!!!!!!!!!!!!!!! I take that back !!!!! It worked :) In my initial setter I was still linking to the activity, not the mutable context. That being said, I am taking your trepidation about memory leaks super seriously, but at least I have a proof of concept to move forward with. Millions of thanks rupps – crimulus Dec 06 '14 at 20:25
  • hey i'm glad it worked! be sure to try it in both WebView's (kitkat / Jellybean), they are totally different beasts. – rupps Dec 06 '14 at 21:07
  • Just curious, were you able to use this method reliably? Or did you observe any bugs / memory leaks? – craigrs84 Apr 16 '15 at 14:38
  • tbh I haven't tested it in-depth, so I don't have proof, however this method loudly screams "memory leak"! read my thoughts on the post. My biggest concern is the WebCore thread that communicates with the UI thread. I can't think of a valid reason to use this, there are hundreds of places where to optimize webview instead of playing with its context. – rupps Apr 17 '15 at 02:28
  • In this case JS based alert, confirm, prompt dialogs are failing to open by producing an error "The context is not an activity" – X-HuMan Nov 20 '15 at 09:58
  • in that case you need to recover the wrapped context, like `Context originalContext=wrappedContext.getBaseContext()` – rupps Nov 20 '15 at 22:58
0

In my App (it's browser) I have the same problem. I don't like to load WebView every time when user back to App. And I've solved this problem partially. I've overridden onBackPressed() on my HomeActivity and use moveTaskToBack(true) instead of super.onBackPressed(). So when user use system back on HomeActivity it does't destroy Activity and all views. It just minimize the App. Visually it's the same behavior but if user try to run App by launch icon, all views already loaded. I know it's temporary solution and all views can be destroyed by system any time but it gives quite good result. And covers a lot of cases for me.

Djek-Grif
  • 1,391
  • 18
  • 18