web-dev-qa-db-fra.com

AsyncTask, faut-il prendre une telle pénalité de performance ...?

Je développe une petite application qui lit dans des pages HTML spécifiques, les reformate puis les affiche dans une vue Web. Si j'exécute mon code dans le fil de l'interface graphique, l'impact sur les performances est presque négligeable par rapport au fait de laisser simplement WebView afficher la page HTML d'origine. Mais si je suis un bon garçon et que je fais ce qui est dit, je suis supposé utiliser une AsyncTask pour exécuter le code en arrière-plan afin de ne pas geler l'interface graphique pendant ces 3-5 secondes, mon code fait son travail . Le problème, c'est que si je le fais, le code prend plus de 10 fois plus de temps à terminer. Une page prend plus de 60 secondes pour s'afficher, ce qui est inacceptable.

En traquant le problème, TraceView me montre que mon AsyncTask est exécuté (avec une priorité par défaut) en environ 10 ms, environ 4 fois par seconde. Je dois définir ma priorité de thread sur MAX_PRIORITY pour m'approcher de temps de chargement acceptables, mais même dans ce cas, cela prend 3 à 4 fois plus longtemps que lorsque j'exécute dans le thread d'interface graphique.

Est-ce que je fais quelque chose de mal, ou est-ce simplement la façon dont cela fonctionne? Et doit-il fonctionner de cette façon ...?

Voici le code compilable comme demandé:

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

Pour exécuter exactement le même code dans ma classe FilterX lorsqu’il est exécuté en tant que AsyncTask ou lorsqu’il est exécuté sur le fil de l’interface graphique, j’ai supprimé tous les éléments de ProgressBar, puis j’ai eu les temps suivants:

  • 30 secondes ou plus pour charger une page avec une priorité de fil par défaut
  • 15 secondes et plus pour charger une page à MAX_PRIORITY
  • 5 secondes et plus pour charger une page lorsqu'elle est exécutée dans le fil de l'interface graphique
29
OppfinnarJocke

Vous n'êtes pas le seul à observer ce comportement. Le ralentissement du facteur 10 est probablement dû au fait qu’Android a utilisé un groupe de contrôle Linux (classe de planification) pour les threads de priorité BACKGROUND ou inférieur. Tous ces threads doivent vivre avec 10% de temps processeur.

La bonne nouvelle est que vous ne devez pas vivre avec les paramètres de priorité de thread de Java.lang.Thread. Vous pouvez attribuer à votre thread une priorité pthread (thread Linux) à partir des définitions dans Android.os.Process. Là, vous avez non seulement Process.THREAD_PRIORITY_BACKGROUND, mais également des constantes pour ajuster un peu la priorité.

Actuellement, Android utilise le groupe de contrôle de thread en arrière-plan pour tous les threads de priorité THREAD_PRIORITY_BACKGROUND ou pire, et THREAD_PRIORITY_BACKGROUND a la valeur 10, THREAD_PRIORITY_DEFAULT est égal à 0 et THREAD_PRIORITY_FOREGROUND a la valeur -2.

Si vous optez pour THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE (aka 9), votre thread sera retiré du groupe de contrôle en arrière-plan avec une limitation de 10%, sans être assez important pour interrompre trop souvent vos threads d'interface utilisateur.

Je pense qu'il existe des tâches d'arrière-plan qui requièrent un peu de puissance de calcul mais qui, dans le même temps, ne sont pas assez importantes pour bloquer de facto l'interface utilisateur (en consommant trop de ressources processeur dans un thread séparé) et Android n'a pour l'instant aucune priorité évidente à affecter. À mon avis, c’est là l’une des meilleures priorités que vous puissiez attribuer à une telle tâche.

Si vous pouvez utiliser un HandlerThread, il est facile de réaliser:

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

Si vous voulez aller avec AsyncTask, vous pouvez toujours faire

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

mais sachez que l'implémentation sous-jacente peut réutiliser le même objet Thread pour différentes tâches, pour la prochaine AsyncTask ou autre. Il semble qu'Android réinitialise simplement la priorité après le retour de doInBackground ().

Bien sûr, si votre interface utilisateur consomme réellement le processeur et que vous souhaitez plus de puissance pour votre tâche en même temps, en la retirant de l'interface utilisateur, vous pouvez définir une autre priorité, pouvant aller jusqu'à Process.THREAD_PRIORITY_FOREGROUND.

37
class stacker

AsyncTask s'exécute avec une priorité inférieure pour vous assurer que le thread d'interface utilisateur restera réactif.

18
Romain Guy

En dépit de la baisse des performances, vous voulez voulez le faire en arrière-plan. Jouez à Nice et d’autres joueront à Nice avec vous.

Puisque je ne sais pas à quoi ça sert, je ne peux pas suggérer d’alternative. Ma première réaction a été qu'il est étrange que vous essayiez de reformater le HTML sur un appareil téléphonique. C'est un phone , pas un quad-core avec beaucoup de RAM. Est-il possible d'effectuer le reformatage dans un service Web et d'afficher le résultat sur le téléphone?

1
Joe Malin

vous devez appeler la chaîne finale str = fx.execute. vous ne devriez pas appeler doinbackground directement à partir du thread ui.

0
user4057066