29

I'm developing a small app that reads in specific html-pages, re-formats them and then shows them in a WebView. If I run my code in the GUI thread, the performance hit is close to negligible compared to simply letting the WebView show the original html-page. But if I'm a good boy and do like I'm told, I'm supposed to use an AsyncTask to run the code in the background so as not to freeze up the GUI during those 3-5 seconds my code does its job. Problem is... if I do so, the code takes more than 10 times as long to finish. A page takes 60+ seconds to show, which is unacceptable.

Tracking down the problem, TraceView shows me that my AsyncTask is (at default priority) run in roughly 10 ms chunks, around 4 times per second. I need to set my thread priority to MAX_PRIORITY to get close to acceptable loading times, but even then it takes 3-4 times longer than when I run in the GUI thread.

Am I doing something wrong, or is this just the way it works? And must it work this way...?

Here's compilable code as requested:

package my.ownpackage.athome;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class AndroidTestActivity extends Activity
{   
    WebView webview;
    //...

    private class HelloWebViewClient extends WebViewClient 
    {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) 
        {
            AndroidTestActivity.this.fetch(view, url);
            return true;
        }
    }

    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // To allow to connect to the web and pull down html-files, reset strict mode
        // see http://stackoverflow.com/questions/8706464/defaulthttpclient-to-androidhttpclient
        if (android.os.Build.VERSION.SDK_INT > 9) 
        {
            StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
            StrictMode.setThreadPolicy(policy);
        }

        // webview init etc...

        fetch(webview, "http://www.example.com");   
    }

    // This one calls either the AsyncTask or does it all manually in the GUI thread
    public void fetch(WebView view, String url)
    {
        //** Use these when run as AsyncTask in background - SLOW! 
        //** Takes 30+ seconds at default thread priority, with MAX_PRIORITY 15+ seconds
        // AsyncTask<Void, String, String> fx = new FilterX(url, view, this);   
        // fx.execute();    // running as AsyncTask takes roughly ten times longer than just plain load!    

        //** Use these when not running as AsyncTask - FAST! takes ~5 seconds
        FilterX fx = new FilterX(url, view, this);
        fx.onPreExecute();
        final String str = fx.doInBackground();
        fx.onPostExecute(str);
    }
}

class FilterX extends AsyncTask<Void, String, String>
{
    WebView the_view = null;
    // other stuff...

    FilterX(final String url, final WebView view, final Activity activity)
    {
        the_view = view;
        // other initialization
        // same code in both cases
    }

    protected void onPreExecute()
    {
        // same code in both cases
    }

    protected String doInBackground(Void... v)
    {
        // same in both cases...

        return new String();    // just to make it compile
    }

    protected void onPostExecute(final String string)
    {
        the_view.loadUrl(string);
        // same in both cases...
    }
}

To run exactly the same code in my FilterX class when run as AsyncTask as when run on the GUI thread, I stripped all ProgressBar stuff, and then I get the following timings:

  • 30+ seconds to load a page at default thread priority
  • 15+ seconds to load a page at MAX_PRIORITY
  • 5+ seconds to load a page when run in the GUI thread
Tamás
  • 47,239
  • 12
  • 105
  • 124
OppfinnarJocke
  • 1,038
  • 2
  • 9
  • 21
  • 3
    Where does TraceView say you are spending most of your time when using async task? Going from 5 seconds to 60 makes it sound like something went horribly wrong when you refactored your code. (post relevant snippets as necessary) – smith324 Jan 21 '12 at 18:33
  • There's no real "re-factoring" going on. I just remove the call to execute() and instead call doInBackground() from the GUI thread. I'll snip out the relevant parts and show you, its trivial things and very much doubt that it this is any problem. – OppfinnarJocke Jan 21 '12 at 18:49
  • You never answered my first question: where does TraceView say you are using your time? – smith324 Jan 22 '12 at 02:57
  • @OppfinnarJocke So you pretty much have two guys from **Google** telling you **not** to do what you're doing - I'd go with Joe's advice and try to do the reformatting elsewhere. Just a thought. – Marvin Pinto Jan 22 '12 at 17:46
  • @smith324 Sorry, I missed that question and focused on the "re-factoring" (which I don't really do, I only change a few lines of code). Looking at my code with TraceView, the biggest time consumer is android.view.ViewGroup.drawChild() at 11.9% exclusive CPU%. More detail here http://stackoverflow.com/questions/8933722/how-to-understand-android-sdk-profile-traceview – OppfinnarJocke Jan 22 '12 at 21:21
  • @OppfinnarJocke The inclusive time is important also! But either way its hard to say whats going on from my position. Most likely its you String manipulations + garbage collection that is harming you. You can always try pulling the content out of the HTML and placing it in a collection and have a ListView display it, that should perform better anyway. – smith324 Jan 23 '12 at 05:20

4 Answers4

37

You're not the only one observing this behaviour. The slowdown by factor 10 is probably a result of Android using a Linux cgroup (scheduling class) for threads of priority BACKGROUND or below. All these threads have to live with 10% CPU time altogether.

The good news is you don't have to live with the Thread priority settings from java.lang.Thread. You can assign your Thread a pthread (Linux thread) priority from the definitions in android.os.Process. There, you not only have Process.THREAD_PRIORITY_BACKGROUND, but also constants to adjust the priority a bit.

Currently, Android uses the background thread cgroup for all threads with priority THREAD_PRIORITY_BACKGROUND or worse, and THREAD_PRIORITY_BACKGROUND is 10 while THREAD_PRIORITY_DEFAULT is 0 and THREAD_PRIORITY_FOREGROUND is -2.

If you go for THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE (aka 9) your thread will be lifted out of the background cgroup with the 10% limitation, while not being important enough to interrupt your User Interface threads too often.

I believe there are background tasks which need a bit of computational power but which are at the same time not important enough to de facto block the UI (by consuming too much CPU in a separate thread) and Android currently has no obvious priority to assign to these, so in my view, this is one of the best priorities you can assign to such a task.

If you can use a HandlerThread it's easy to achieve:

ht = new HandlerThread("thread name", THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE);
ht.start();
h  = new Handler(ht.getLooper()); 

If you want to go with AsyncTask, you can still do

protected final YourResult doInBackground(YourInputs... yis) {
    Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE);
    ...
}

but be aware that the underlying implementation may reuse the same Thread object for different tasks, for the next AsyncTask, or whatever. It seems that Android simply resets the priority after doInBackground() returns, though.

Of course, if your UI really consumes CPU and you want more power for your task at the same time, taking it away from the UI, you can set another priority, maybe up to Process.THREAD_PRIORITY_FOREGROUND.

class stacker
  • 5,357
  • 2
  • 32
  • 65
  • 2
    Messing with the priority of a thread that you did not create is bad form. If you want to control thread priorities, that's cool, but then don't use `AsyncTask`, but fork your own `Thread`, or use an `ExecutorService` for a thread pool that you create. Or, if you *really* want `AsyncTask` semantics, create your own `ExecutorService` and use `executeOnExecutor()` to opt into it on API Level 11+, and set your own thread priority on those threads. – CommonsWare Apr 13 '13 at 13:31
  • 2
    @CommonsWare I have not found any other Thread-ish object apart from `HandlerThread` which supports setting a `Process.THREAD_PRIORITY` explicitly, and `Thread`s also magically have background prio by default. As I see it, I can't even be sure that Android won't change a Thread's priority along the way in accordance with its needs. So when I found no other documented way than for `HandlerThread`, I took the freedom to see all other priority manipulation methods as equally unofficial. But you have a point there. – class stacker Apr 13 '13 at 16:00
  • My apologies, I was misreading something in your answer. Though note that `HandlerThread` has nothing to do with priority, other than what it inherits from the standard Java `Thread` class. – CommonsWare Apr 13 '13 at 16:18
  • 1
    @CommonsWare No problem. -- Regarding `HandlerThread`, I was referring to its constructor [HandlerThread(String, int)](http://goo.gl/n5Mcf) which explicitly takes a `Process.THREAD_PRIORITY`. This is the only place I know of where I can ask Android officially to use a specified `Process.THREAD_PRIORITY` -- Regarding `AsyncTask`, I have observed (APIs 8 to 17) that every `doInBackground()` starts with `Process.THREAD_PRIORITY_BACKGROUND` so I concluded Android tolerates prio changes in that method. – class stacker Apr 14 '13 at 08:39
18

AsyncTask runs at a lower priority to help making sure the UI thread will remain responsive.

Romain Guy
  • 97,993
  • 18
  • 219
  • 200
1

Despite the performance hit, you do want to do this in the background. Play nice, and others will play nice with you.

Since I don't know what this is for, I can't suggest an alternative. My first reaction was that it's odd that you're trying to reformat HTML on a phone device. It's a phone, not a quad-core with oodles of RAM. Is it possible to do the reformatting in a web service and display the result on the phone?

Joe Malin
  • 8,621
  • 1
  • 23
  • 18
  • Well... this is code from a forum server, that does not have tapatalk or any other such support, so the only way I can think of is to do the reformatting on the phone. It bugs the *** out of me to read that forum in a browser (which BTW does not seem to do its fetching and rendering in the background), I constantly have to zoom and scroll each page manually, so my code strips away non-essential stuff (headings, most columns in tables, most images, etc) and then displays that nicely formatted in my WebView. It's only for myself. Using a web service...? I haven't the faintest clue... – OppfinnarJocke Jan 22 '12 at 21:27
0

u need to call final String str = fx.execute. you should not call doinbackground directly from ui thread.

user4057066
  • 295
  • 3
  • 14