2012-01-21 10 views
28

Pracuję nad małą aplikacją, która czyta na konkretnych stronach HTML, ponownie je formatuje, a następnie wyświetla w WebView. Jeśli uruchomię swój kod w wątku GUI, wydajność jest prawie nieistotna w porównaniu do tego, że po prostu pozwala to WebView pokazać oryginalną stronę html. Ale jeśli jestem dobrym chłopcem i lubię, jak mi powiedziano, mam użyć AsyncTask, aby uruchomić kod w tle, aby nie zamrozić GUI w ciągu tych 3-5 sekund, w których mój kod wykonuje swoją pracę . Problem polega na tym, że jeśli to zrobię, wykonanie kodu zajmie więcej niż 10 razy. Wyświetlenie strony trwa 60 lub więcej sekund, co jest niedopuszczalne.AsyncTask, czy musi przyjąć takie trafienie karne?

Śledzenie problemu, TraceView pokazuje mi, że moja AsyncTask jest (domyślnie priorytetem) działa w porach około 10 ms, około 4 razy na sekundę. Muszę ustawić priorytet mojego wątku na MAX_PRIORITY, aby zbliżyć się do akceptowalnego czasu ładowania, ale nawet wtedy trwa to 3-4 razy dłużej niż w przypadku uruchamiania w wątku GUI.

Czy robię coś nie tak, czy to tak po prostu działa? I czy to musi działać w ten sposób ...?

Oto kod compilable na żądanie:

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

Aby uruchomić dokładnie ten sam kod w mojej klasie FilterX gdy uruchamiane jako AsyncTask jak wtedy, gdy są wykonywane w wątku GUI, rozebrałem wszystko ProgressBar rzeczy, a potem uzyskać następujące czasy:

  • 30+ sekund, aby załadować stronę w domyślnym priorytetem wątku
  • 15+ sekund, aby załadować stronę na MAX_PRIORITY
  • 5+ sekund, aby załadować stronę kiedy uruchomić w wątku GUI
+3

Gdzie TraceView twierdzi, że spędzasz większość swojego czasu podczas korzystania z asynchronicznego zadania? Przechodzenie od 5 sekund do 60 powoduje, że brzmi to jak coś poszło strasznie źle, po refaktoryzacji kodu. (w razie potrzeby wstawiaj odpowiednie fragmenty) – smith324

+0

Nie ma prawdziwego "ponownego faktoringu". Po prostu usuwam wywołanie execute() i zamiast tego wołam doInBackground() z wątku GUI. Wytnę odpowiednie części i pokażę wam, jego trywialne rzeczy i bardzo wątpię, że to jest jakikolwiek problem. – OppfinnarJocke

+0

Nigdy nie odpowiedziałeś na moje pierwsze pytanie: gdzie TraceView mówi, że używasz swojego czasu? – smith324

Odpowiedz

36

Nie jesteś jedynym, który obserwuje to zachowanie. Spowolnienie według współczynnika 10 jest prawdopodobnie wynikiem działania systemu Android z grupą cgroup (klasa planowania) dla wątków o priorytecie BACKGROUND lub poniżej. Wszystkie te wątki muszą żyć razem z 10% czasu procesora.

Dobrą wiadomością jest to, że nie musisz żyć z ustawieniami priorytetu wątku z java.lang.Thread. Możesz przypisać wątek priorytetowi pthread (wątek systemu Linux) z definicji w pliku android.os.Process. Tam masz nie tylko Process.THREAD_PRIORITY_BACKGROUND, ale także stałe, aby nieco zmienić priorytet.

Obecnie Android wykorzystuje nici cgroup tła dla wszystkich wątków o priorytecie THREAD_PRIORITY_BACKGROUND lub gorzej, a THREAD_PRIORITY_BACKGROUND wynosi 10, podczas gdy THREAD_PRIORITY_DEFAULT oznacza 0 THREAD_PRIORITY_FOREGROUND to -2.

Jeśli pójdziesz do THREAD_PRIORITY_BACKGROUND + THREAD_PRIORITY_MORE_FAVORABLE (aka 9) swój wątek zostanie wyciągnięty z cgroup tle z ograniczeniem 10%, podczas gdy nie jest na tyle ważne, aby przerwać swoje wątki User Interface zbyt często.

Wierzę, że istnieją zadania w tle, które wymagają nieco mocy obliczeniowej, ale jednocześnie nie są wystarczająco ważne, aby de facto blokować interfejs użytkownika (zużywając zbyt dużo CPU w osobnym wątku), a Android nie ma obecnie żadnych oczywistych pierwszeństwo, aby je przypisać, więc moim zdaniem jest to jeden z najlepszych priorytetów, które można przypisać do takiego zadania.

Jeśli można użyć HandlerThread jest to łatwe do osiągnięcia:

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

Jeśli chcesz iść z AsyncTask, można jeszcze zrobić

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

ale należy pamiętać, że realizacja bazowego może ponownie wykorzystać ten sam obiekt Thread dla różnych zadań, dla następnej AsyncTask lub cokolwiek innego. Wygląda jednak na to, że Android po prostu resetuje priorytet po powrocie doInBackground().

Oczywiście, jeśli twój interfejs naprawdę zużywa procesor i chcesz więcej mocy dla swojego zadania w tym samym czasie, zabierając go z interfejsu użytkownika, możesz ustawić inny priorytet, może nawet do Process.THREAD_PRIORITY_FOREGROUND.

+2

Przesyłanie wiadomości z priorytetem wątku, który nie został utworzony, jest złe. Jeśli chcesz kontrolować priorytety wątku, to jest fajne, ale nie używaj 'AsyncTask', ale rozwidź swoje' Thread' lub użyj 'ExecutorService' dla tworzonej puli wątków.Lub, jeśli naprawdę * potrzebujesz semantyki "AsyncTask", stwórz własną 'ExecutorService' i użyj' executeOnExecutor() ', aby włączyć ją na poziomie API 11+ i ustaw swój własny priorytet wątku dla tych wątków. – CommonsWare

+2

@CommonsWare Nie znalazłem żadnego innego obiektu Thread-ish poza 'HandlerThread', który obsługuje jawnie' Process.THREAD_PRIORITY', a 'Thread's również domyślnie ma prio tło. Jak widzę, nie mogę nawet być pewien, że Android nie zmieni priorytetu wątku po drodze zgodnie z jego potrzebami. Więc kiedy nie znalazłem żadnej innej udokumentowanej metody niż dla 'HandlerThread', skorzystałem z wolności, aby zobaczyć wszystkie inne metody manipulacji priorytetami jako równie nieoficjalne. Ale masz rację. –

+0

Przepraszam, błędnie odczytałem coś w twojej odpowiedzi. Chociaż zauważ, że 'HandlerThread' nie ma nic wspólnego z priorytetem, innym niż to, co dziedziczy po standardowej klasie" Thread "Java. – CommonsWare

18

AsyncTask pracuje z niższym priorytetem, aby pomóc upewniając się, że wątek UI pozostają elastyczne.

1

Mimo trafienia wydajności, chcesz zrobić chcesz to zrobić w tle. Graj dobrze, a inni będą bawić się z tobą dobrze.

Ponieważ nie wiem, po co to jest, nie mogę zaproponować alternatywy. Moja pierwsza reakcja była taka, że ​​to dziwne, że próbujesz zmienić format HTML na urządzeniu telefonicznym. To nie jest czterordzeniowy rdzeń z pamięcią RAM. Czy możliwe jest ponowne formatowanie w usłudze sieciowej i wyświetlanie wyniku na telefonie?

+0

Cóż ... to jest kod z serwera forum, który nie ma tapatalk ani żadnego innego takiego wsparcia, więc jedyny sposób, jaki mogę wymyślić to ponowne formatowanie przez telefon. To nudzi mnie, żebym przeczytał to forum w przeglądarce (której BTW nie wydaje się pobierać i renderować w tle), ciągle muszę powiększać i przewijać każdą stronę ręcznie, więc mój kod usuwa - istotne rzeczy (nagłówki, większość kolumn w tabelach, większość obrazów itp.), a następnie wyświetla je ładnie sformatowane w moim WebView. To tylko dla mnie. Korzystasz z usługi internetowej ...? Nie mam najmniejszego pojęcia ... – OppfinnarJocke

0

musisz wywołać ostateczny ciąg str = fx.execute. nie powinieneś dzwonić doinbackground bezpośrednio z wątku interfejsu użytkownika.