Wechsel zwischen ActionBar Tabs - Wie das Einfrieren der UI verhindern?

  • Hallo,


    ich arbeite gerade an einer App deren MainActivity über ActionBar-Tabs insgesamt 5 Seiten/Fragments verwaltet. Das Ganze arbeitet ähnlich wie die "Apps" Activity/Seite in der Play-Store App von Google. Dort gibt es auch eine Reihe von Tabs (Kategorien, Startseite, Bestseller, etc.) und man kann über einen Klick auf den zugehörigen Tab oder durch links/rechts Swipes zwischen den Fragments wechseln.


    Wie bekomme ich es hin, dass der Wechsel zwischen den Tabs/Fragments ebenso flüssig und reibungslos läuft wie in der PlayStore App?


    Genau wie bei der PlayStore App haben auch die Tabs/Fragments in meiner App dynamischen Inhalt der erst von einem Server geladen werden muss bevor dieser angezeigt werden kann. Das Problem ist, dass bei meiner App der Wechsel zwischen den Tabs
    extrem hakt. Wechselt man also zu einem neuen Tab dauert 1-2 Sekunden bis dieser wirklich angezeigt wird.


    Dabei wird wird das Laden der Daten natürlich NICHT im UI-Thread ausgeführt sondern ist über einen AsyncTask in den Hintergrund verlagert. Dennoch bekomme ich keinen flüssigen Seitenwechsel hin.


    Die Tabs sind bei mir alle gleich aufgebaut:

    Code
    RelativeLayout
        	ProgressBar 
        	ContentRoot (z.B. eine RelativeLayout)


    Wenn die View geladen wird, ist zunächst nur der ProgressBar sichtbar um zu zeigen, dass etwas passiert. Gleichzeitig wird ein AsyncTask gestartet, dass zwei Dinge übernimmt:

    • Die Daten werden geladen
    • Die zugehörigen UI wird erstellt, also z.B. eine List für alle Kontakte die vom Server geladen wurden, eine ImageView für jedes geladen Bild oder was auch immer. Die Elemente werden natürlich noch NICHT zur UI hinzugefügt denn ein Zugriff darauf ist aus einem Hintergrundthread nicht möglich. Statt dessen werden die Elemente nur erstellt und zu einem gemeinsamen Container Layout (z.B. einem LinearLayout) hinzugefügt

    Sobald der Task fertig ist passiert im UI-Thread nur noch zwei Sachen:

    • Die erzeugte ContainerView wird zum ContentRoot hinzugefügt.
    • Der ProgressBar wird ausgeblendet und der ContentRoot wird eingeblendet

    Der Code hierzu sieht so aus:


    Obwohl alle anderen Aktionen in den Hintergrundthread verlagert wurde und die Aktivität im UI-Thread auf das absolute Minimum reduziert wurde verläuft der Tab-Wechsel nicht flüssig. Warum?


    Dabei kommt es scheinbar nicht darauf an, ob die vorbereiteten UI-Elemente in der ContainerView besonders komplex sind. Auch wenn die ContainerView nur relativ wenige Elemente enthält (z.B. nur ein paar Buttons oder eine ListView mit Kontakten) kommt es bereits zu diesen Hängern.


    Das Problem tritt in zwei verschiedenen Varianten auf, je nach dem ob eine Tab-Seite zum ersten Mal oder wiederholt aufgerufen wird.

    • Beim ersten Besuch: onCreateView wurde ausgeführt und der LoadTask läuft noch. Die Seite wird also angezeigt und der ProgressBar ist sichtbar. Das funktioniert ohne Probleme. Ist der LoadTask aber fertig verläuft der Wechsel vom ProgressBar zum ContentRoot hakelig. Der ProgressBar bleibt für 1-2 Sekunden stehen und erst dann wird der Inhalt angezeigt.
    • Beim wiederholten Besuch: Die View wurde zwischenzeitig durch onDestroyView() freigegeben, der LoadTask ist aber schon komplett. Beim erneuten Besuch der Seite stehen die Daten also direkt in onCreateView() zur Verfügung und der Task muss nicht neu gestartet werden. In diesem Fall dauert einfach der Wechsel zu dieser Seite 1-2 Sekunden.

    Insgesamt ist also keine flüssige Navigation zwischen den Seiten möglich. Warum? Wie könnte ich die Aktivität im UI-Thread noch weiter reduzieren? Ist das überhaupt noch möglich?
    Die PlayStore App arbeitet scheinbar genauso und ermöglicht dabei einen komplett flüssigen Wechsel zwischen den Seiten? Wie löst man das also?
    Vielen Dank!

  • Eventuell machst Du in der Methode showActivity() zu viel Kleinkram, der das Ganze verzögert.

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

  • Nein, dass kann es nicht sein. Der Name "showActivity()" ist an dieser Stelle ggf. auch etwas missverständlich. Es geht dabei NICHT darum eine Activity anzuzeigen, sondern den ProgressBar ein- und den Inhalt auzublenden:


    Code
    private void showActivity() {
    	activityProgressBar.setVisibility(View.VISIBLE);
    
    
    	contentRoot.setVisibilty(View.GONE);
    }


    Mehr passiert dort nicht.


    Ich habe das ganze mal gemessen: Wenn ich als Content nur eine ListView verwende (andere UI Elemente sind also nicht vorhanden) dauert der Seitenwechsel bereits 850ms. Das ist keine Ewigkeit aber dennoch eine spürbare Verzögerung. Dabei wird die ListView und der zugehörige Adapter im Hintergrund erstellt. Erst wenn dies gelaufen ist, wird die ListView in showData() zur UI hinzugefügt. Mehr passiert nicht. Die ListView ist dabei auch nicht übermäßig komplex. Die ListItems bestehen jeweils nur aus zwei TextViews wovon eine zusätzlich ein Drawable enthält.

  • Miss die Zeiten des Ganzen mal ohne den ganzen Web-Hickhack.
    Und dann überleg noch einmal ganz genau, in welcher Methode Du so viel Zeit verbrätst.


    Der von Dir gepostete (und offenkundig unvollständige) Code ist es jedenfalls nicht.

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

  • Die Verzögerung entsteht ganz eindeutig durch die UI Interaktion in showData(), genauer durch das Hinzufügen der ContainerView zum ContentRoot. Lasse ich dies weg, wechseln die Seiten ohne jede (erkennbare) Verzögerung. Füge ich dort einige wenige UI Elemente hinzu kommt es zu der beschriebenen Verzögerung.

  • Aber showData sollte doch nur ausgeführt werden, wenn die Daten korrekt geladen wurden. Oder?
    Überhaupt, Du erstellst die Views im Hintergrund, also außerhalb des UI Threads, und willst sie dann in den UI Thread hängen.
    Das sieht für mich nach einem falschen Vorgehen aus.


    Wenn Du es jetzt noch so implementiert hast, dass die Views aus einem Layout via LayoutInflater gezogen werden, ist es durchaus verständlich, dass da Dinge länger dauern.


    Eventuell muss da auch noch ein Kontext rumgereicht werden – das sieht mir alles suboptimal aus.


    Wozu machst Du das so?


    Statt ein und dasselbe Fragment je Pager mit unterschiedlichen Dingen zu befüllen erstell Dir doch unterschiedliche Fragmente je Pager.
    Also beispielsweise ein ListFragment für alle Infos und ein Fragment mit GridView für alle Bilder.
    Das baust und verdrahtest Du dann alles im UI Thread und Du musst dann nur noch die zu den speziellen Views gehörigen Adapter aus dem AsyncTask mit den anzuzeigenden Infos füttern.

    Je mehr Käse, desto mehr Löcher.
    Je mehr Löcher, desto weniger Käse.
    Daraus folgt: je mehr Käse, desto weniger Käse.


    »Dies ist ein Forum. Schreibt Eure Fragen in das Forum, nicht per PN!«

    Einmal editiert, zuletzt von Marco Feltmann ()

  • Nachdem ich nun mehrere Tage mit der Optimierung verbracht habe und das Problem dennoch nicht komplett beheben konnte, habe ich eine Lösung gefunden:


    Startet man die App auf dem Gerät direkt und nicht über den Debugger läuft alles butterweich und flüssig... Super... Es hat also scheinbar nichts mit dem Code sondern nur mit Overhead durch das Debugging zu tun. Ärgerlich so viel Zeit umsonst investiert zu haben. Aber immerhin habe ich etwas dazu gelernt und der Code ist jetzt auch super optimiert :P

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!