12

In a subclass of WebView, I used to have this line in an overridden method of getTitle():

String title = super.getTitle();

It worked well in all versions of Android, until I got to test my app on an Android 4.1 phone, which gave me this warning on that super.getTitle() line:

12-20 21:38:27.467: W/webview_proxy(2537): java.lang.Throwable: Warning: A WebView method was called on thread 'WebViewCoreThread'. All WebView methods must be called on the UI thread. Future versions of WebView may not support use on other threads.

So, I was thinking of working around this new decree by passing it through runOnUiThread():

Activity a = this.getActivity();
a.runOnUiThread(new Runnable() {
    public void run() { 
    String title = super.getTitle();
    }
});

But this code won't even compile because super no longer refers to WebView, but rather to Activity.

Any idea how How to super.getTitle() from the UI thread? (with the constraints described above, in the getTitle() of a subclass of WebView)

rene
  • 41,474
  • 78
  • 114
  • 152
scatmoi
  • 1,958
  • 4
  • 18
  • 32
  • **Where** is the snippet you show with `Activity a = ...` being placed? Are you placing it in the overridden `getTitle()` method of your `MyWebView` class? – Nate Jan 01 '13 at 00:01
  • 1
    Actually, `super` in the anonymous `Runnable` class above points to `Object` :) – Joe Jan 01 '13 at 18:21

4 Answers4

6

Morgan's answer, while it may fix the compile error, is not really a solution to this problem.

First of all, it does nothing to change the call to getTitle() to a different thread. That underlying issue is why Android is giving you the error at runtime.

You say in a comment that

The circumstances are that I am calling it in WebViewClient.onPageFinished(), which happens not to be on the UI thread.

That may be a problem. If you are starting the web request from the UI thread, then onPageFinished() should certainly get called back on the UI thread. Can you explain how you are starting the web request, and why you're doing it that way? The vast majority of the time, you shouldn't be seeing onPageFinished() called in the background, so you may have a problem elsewhere.

(Note: if you think you need to call WebView.loadUrl() in the background to avoid blocking the UI, please see this other answer on that issue)

If you really think you need to start the web request in the background, and you see onPageFinished() called in the background, you need to take care to call getTitle() on the UI thread.

Also, if you are calling it from the onPageFinished() method, then there is no need to use syntax like this:

String title = MyWebView.this.getTitle();

in that method, you are passed the instance of your web view, so just use it directly:

public void onPageFinished (WebView view, String url) {
    String title = view.getTitle();
}

but, as I said, that doesn't address the threading issue. You would need to show us why you are trying to use the page title in that method, but one way to safely use it would be something like this:

public void onPageFinished (final WebView view, String url) {
   view.post(new Runnable() {
      public void run() { 
         String title = view.getTitle();
         // do something with title that affects the UI here
      }
   });     
}

Note that I needed to make the view parameter final in the above code.

Community
  • 1
  • 1
Nate
  • 31,017
  • 13
  • 83
  • 207
  • +1 for a remarkably educational answer but... I ***do*** loadUrl() from the UI thread! So why does `onPageFinished()` get called on a non-UI thread? – scatmoi Jan 01 '13 at 23:31
  • 1
    First of all, I would double/triple check that it's really getting called on the background. Put a breakpoint in `onPageFinished()` and stop there in the debugger, and look at the Debug pane to see that some thread other than Thread-1 is calling the method. If it isn't the main/UI thread, can you show us the code that calls `loadUrl()`? Also, are you implementing `WebViewClient` just to get the `onPageFinished()` callback, or are you doing other things in your `WebViewClient` implementation? Adding that code to your question above could help, too. Thanks! – Nate Jan 01 '13 at 23:34
  • I'll also add that my last code snippet, that uses `view.post()`, should solve the problem, even if `onPageFinished()` **is** getting called in the background. However, I think it would be better to figure out *why* this is happening. As such, that solution is still a *workaround*. – Nate Jan 01 '13 at 23:40
  • Ouch! You're right. I made a mistake: onPageStarted & onPageFinished both get called on the UI thread. It's a `JavascriptInterface` method (called in onPageFinished via `view.loadUrl`) that's on another thread. It's been a while since I looked at the code... my apologies for not being accurate from the start. As for your other question, I am only implementing `WebViewClient` to do [this](http://stackoverflow.com/a/5172952/1088880). The correct solution is probably to `view.post()` the `view.loadUrl()` so that it runs on the UI thread, right? – scatmoi Jan 01 '13 at 23:58
  • 1
    No worries. So, no `runOnUiThread()` or `view.post()` is needed in `onPageFinished()`. But, I would still write the code per my middle code snippet (`String title = view.getTitle();`) as using `MyWebView.this.getTitle()` is really unnecessarily complex, and less robust, in this situation. Also, you apparently still have a problem with what you're doing in your `JavascriptInterface` method. So, probably **that method** should use `post(Runnable)` or `runOnUiThread(Runnable)`. See [this other question](http://stackoverflow.com/q/6758604/119114) – Nate Jan 02 '13 at 00:07
  • I have to re-accept & give *you* the bonus, because I love your answer for its desire to stick to [correct practices](http://stackoverflow.com/a/10558238/1088880) and fixing the *root cause* of problem instead of working around symptoms. Thanks! – scatmoi Jan 02 '13 at 00:13
  • You're welcome. Yeah, if you are using `runOnUiThread()` or `post()` because you're in a JavaScript interface method, then that's not a hack, but a proper solution. – Nate Jan 02 '13 at 00:20
5

I don't have a direct answer to your question but I can suggest a workaround that worked for me when I encountered a similar problem: Find the method that calls the method that calls String title = super.getTitle(); and run it via runOnUiThread().

HTH.

ih8ie8
  • 944
  • 8
  • 23
  • Thanks (+1) but I am still interested in accessing MyWebView's super + its data members. – scatmoi Dec 31 '12 at 23:41
  • Turns out your solution was the correct one from the start, but it didn't provide the explanation *why*, like @Nate explained. – scatmoi Jan 02 '13 at 00:25
2

The direct solution is to do this

String title = MyWebView.this.getTitle();

instead of

String title = super.getTitle();

That being said, it would be interesting to know more about the circumstances under which you are calling this (I.e., why you are calling it in the background). I ask because I am not sure that your approach will do what you want even after you make the code change I mentioned.

Morgan
  • 814
  • 9
  • 10
  • Or even `MyWebView.getTitle()`. – CommonsWare Jan 01 '13 at 18:17
  • 2
    @CommonsWare, I think you would get "Cannot make a static reference to the non-static method getTitle()" in this case. – Joe Jan 01 '13 at 18:33
  • @Morgan, thanks. I will try your solution and report back. Answering your question: The circumstances are that I am calling it in [WebViewClient.onPageFinished()](http://developer.android.com/reference/android/webkit/WebViewClient.html#onPageFinished%28android.webkit.WebView,%20java.lang.String%29), which happens **not** to be on the UI thread. Additional insights? – scatmoi Jan 01 '13 at 20:00
  • 1
    Interesting -- that actually surprises me a little. In any case, you should be good to go with MyWebView.this.getTitle(). Good luck! – Morgan Jan 01 '13 at 20:20
  • @Morgan Why does it surprise you? BTW, your solution `MyWebView.super.getTitle();` works! I now need to understand why? (i.e. the syntax `Classname.super` is not clear to me). Thanks (+1) & accepting. – scatmoi Jan 01 '13 at 20:41
  • 1
    I think it surprised me because it sounds like a UI-ish callback,and often those kinds of callbacks are on the UI thread so that client code (like yours) can take main thread actions easily. – Morgan Jan 02 '13 at 00:23
  • 1
    I will try to find the java language spec for this feature. Think of it this way -- there are two "this" objects in your code's context: the "local" this (the anonymous Runnable subclass) and the "parent" this, your MyWebView. Think of the syntax MyWebView.this as just a way of telling the JRE which one you want to use. – Morgan Jan 02 '13 at 00:29
0

To get rid of this warning, you do the following:

  Handler handler =new Handler();
    handler.post(new Runnable() {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            String URL = "http://www.superlinux.net";

            //Of course the object "webview" is a WebView. 
            //it's better to define it a global variable
            webview.loadUrl(URL);
        }
    });

Therefore, using this way, because webview is UI element, you use a Handler object. Handlers are some kind of threads that runs separately from the creation any UI element.

superlinux
  • 476
  • 1
  • 4
  • 12