custom textView .setText() - UI thread

M

miriki

Neues Mitglied
0
Moinsens!

Nachdem ich hier schon länger lesend im Forum unterwegs bin, hab ich mich jetzt mal angemeldet, um auch mal selbst eine Frage reinstellen zu können:

Ich versuche gerade, eine eigene Klasse zu erstellen, die von einer textView (genau genommen
AppCompatTextView) abgeleitet ist. Diese soll sich selbständig (z.Z. per Timer) aktualisieren. Leider geht .setText() nicht aus dem Widget heraus. Es kommt zum Fehler: "android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views."

Also... Ich habe...
a) Eine "public class MainActivity extends AppCompatActivity" (API 19, soll noch auf meinem alten Note II laufen)
b) Eine "class MrkTextClockWidget extends AppCompatTextView"

In a) wird dann die textView erzeugt und gesetzt:
Code:
private void createLayoutAndWidgets() {
    LinearLayout ll;
    LayoutParams lp;
    MrkTextClockWidget t;
    ll = new LinearLayout( this );
    ll.setOrientation(LinearLayout.VERTICAL);
    lp = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT );
    ll.setLayoutParams( lp );
    t = new MrkTextClockWidget( myContext );
    lp = new LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT );
    t.setLayoutParams( lp );
    ll.addView( t );
    setContentView( ll );
} // createLayoutAndWidgets

wird vom onCreate() aufgerufen:

Code:
@override
protected void onCreate( Bundle savedInstanceState ) {
    super.onCreate(savedInstanceState);
    myContext = getApplicationContext();
    // displayMessage( "onCreate" );
    createLayoutAndWidgets();
} // onCreate

In b) rufen die 3 Varianten des Constructor dann init() auf:
Code:
private void init() {
    Calendar c;
    Date n;
    Timer t;
    TimerTask tt;
    Date ft;
    long p;
    t = new Timer();
    c = Calendar.getInstance();
    n = c.getTime();
    tt = new TimerTask() {
        @override
        public void run() {
            updateWidget();
        }
    };
    ft = n;
    p = 1000;
    t.scheduleAtFixedRate( tt, ft, p );
} // init

Und dann die eigentliche Routine, die das Widget aktualisieren soll:
Code:
private void updateWidget() {
    Calendar c;
    Date n;
    DateFormat f;
    c = Calendar.getInstance();
    n = c.getTime();
    f = android.text.format.DateFormat.getDateFormat( myContext );
    setText( f.format( n ));
}

Tja, aber wie im Betreff und eingangs beschrieben, klappt das so nicht. Und ich krieg einfach die Syntax nicht hin, wie es laufen soll. Im Web finde ich bislang nur Beispiele, wie eine Timer-textView im "main" läuft. Ich brauch das aber zwingend als eigene Klasse (und damit wohl auch eigenen Thread, soweit ich das verstanden habe). Diese textView Geschichte ist nur anfängliche Spielerei. Ich brauche später etwas aufwändigere Widgets, die so selbständig wir nur irgend möglich arbeiten. Ich will eigentlich im "main" nur noch das Widget setzen und ggf. ein paar Properties setzen, mich aber ansonsten nicht mehr drum kümmern müssen.

Hilft mir hier jemand auf die Sprünge? Ist ja vielleicht echt nur 'ne Kleinigkeit. Aber ich dreh mich hier langsam im Kreis.

Gruß, Michael
 
Zuletzt bearbeitet:
Hallo Michael,

herzlich willkommen im Forum.

a) Bitte sei so nett und formatiere deinen Source mit dem Code-Tag - Dann ist er auch besser zu lesen :)
b) zu deiner Frage : Du musst die Anzeige im UI-Thread ausgeben

Code:
  runOnUiThread(new Runnable()
        {
            @Override
            public void run()
            {
               setText (.....
            }
        });
 
swa00 schrieb:
a) Bitte sei so nett und formatiere deinen Source mit dem Code-Tag - Dann ist er auch besser zu lesen :)
Das hätte ich gerne gemacht, ich fand den nur nicht. Aber manuell scheint {code} .. {/code} ja zu gehen, wenn ich mir das Zitat von Dir so ansehe. Hinter welchem der Icons in der Leiste über dem Editor versteckt sich "code"?

Aber vielen Dank erstmal für Deine Antwort!

b) zu deiner Frage : Du musst die Anzeige im UI-Thread ausgeben
Das hab ich schon so verstanden und auch mehrmals in anderen Beiträgen gelesen. Aber es paßte irgendwie nie zu meinem Widget. Ich glaube, das "runOnUiThread" gehört zum Fragment und deswegen kann ich es nicht benutzen. Aber so ganz verstanden habe ich das nicht.

Wo soll ich denn
Code:
runOnUiThread(new Runnable() {
    @Override
    public void run() {
        setText (.....
    }
});
genau einsetzen? Meine "update"-Routine sieht jetzt aus wie:
Code:
private void updateWidget() {
    runOnUiThread( new Runnable() {
        @Override
        public void run() {
            Calendar c;
            Date n;
            DateFormat f;
            c = Calendar.getInstance();
            n = c.getTime();
            f = android.text.format.DateFormat.getDateFormat( myContext );
            setText( f.format( n ) );
        }
    });
}
Und jetzt krieg ich nur die Meldung in der IDE: "Cannot resolve method 'runOnUiThread( anonymous java.lang.Runnable )'"

Gruß, Michael
 
Zuletzt bearbeitet:
swa00 schrieb:
uns ich nicht dein gesamter Code bekannt.
Naja, aber die wesentlichen Teile doch wohl schon, oder?

Natürlich musst Du runOnUIThread vom Context ableiten.
Ah, ok, jetzt kommen wir der Sache näher. Den Context übergebe ich beim Erstellen an das Widget. Dieser wird lokal gespeichert:
Code:
class MrkTextClockWidget extends AppCompatTextView {

private Context myContext;

public MrkTextClockWidget( Context context ) {
    super( context );
    myContext = context;
    init();
} // constructor

[...]

Gruß, Michael
 
Moin, swa00!
Natürlich musst Du runOnUIThread vom Context ableiten.
Schau dir dazu mal die offizielle Doku an

Also ich schrieb ja schon, glaube ich, daß ich auf der Seite auch schon war. Tatsächlich gehört die Seite zu meinen primären Lesezeichen. Aber: Ich sehe / verstehe absolut nicht, wie mir das dort weiterhilft.

Dort ist jetzt von runOnUIThread oder Context überhaupt nicht mehr die Rede. Dafür geht's mit einem mHandler los, der (bei mir) auch wieder nicht aufgelöst werden kann. Und von dem mHandler hab ich in einigen StackOverflow-Artikeln auch schon was mitbekommen, aber ebenfalls nicht umsetzen können.

Und ich will ja eigentlich auch nicht eine komplette Thread-Verwaltung über irgendeinen Pool haben. Ich will einfach nur, daß mein Widget sich selbst aktualisiert, ohne daß im "main" irgendwas berücksichtigt werden muß. Ist das denn wirklich so schwierig?

Gruß, Michael
 
Hallo Michael,

du hast doch bereits deinen Context in MyContext zugewiesen.

runOnUIThread ist ein Objekt aus Diesem
Ergo hast du schlichtweg mal MyContext.runOnUIThread probiert ?


Und genau das meinte ich Eingangs mit : " Der komplette Code fehlt" die Deklaration von MyContext war nicht bekannt .
Deshalb können wir auch nur rätseln , wo dein Fehler liegt .

Ggf. musst du von MyContext eine temporäre final Variabel deklarieren

So ganz nebenbei : Dein TimerThread ist eher "OldSchool" und wird dir Probleme bereiten.
Nimm lieber einen ordentlichen AsyncTask in einem separatem Objekt
 
Moin, @swa0!
swa00 schrieb:
Ergo hast du schlichtweg mal MyContext.runOnUIThread probiert ?
Jo, hatte ich, mit nach wie vor der genau gleichen Fehlermeldung. Beim myContext hatte ich auch private / public und final / static ausprobiert, aber ebenfalls keine Änderung.

Ggf. musst du von MyContext eine temporäre final Variabel deklarieren
Wo? Innerhalb des Runnable?

Dein TimerThread ist eher "OldSchool" und wird dir Probleme bereiten.
Ja, auf lange Sicht wird der wahrscheinlich durch irgendwas mit AlarmManager und setExact() ersetzt. Die Widgets sollen später Sensor-Werte darstellen und ggf. Alarm schlagen. Da hilft es mir nicht, wenn Android den ganzen Kram in den Schlafmodus legt. Allerdings muß ich mit der API aufpassen. Wie oben geschrieben: Der Kram soll noch auf dem alten Note II mit API 19 laufen.

Gruß, Michael
 
Hallo miriki,

new Runnable() erzeugt ein anonymes Objekt. An dieser Stelle mag Java das nicht.

Versuche es mal mit:

Code:
Runnable aRunnable = new Runnable() {
    @Override
    public void run() {
        Calendar c;
        Date n;
        DateFormat f;
        c = Calendar.getInstance();
        n = c.getTime();
        f = android.text.format.DateFormat.getDateFormat( myContext );
        setText( f.format( n ) );
    }
};

runOnUiThread(aRunnable);

Denn Code habe ich nicht ausprobiert, denke aber dass die Lösung es das Problem behebt
 
Hi, Markus!

markus.tullius schrieb:
new Runnable() erzeugt ein anonymes Objekt. An dieser Stelle mag Java das nicht.

Ich hab meinen Code mal dahingehend geändert. Das brachte leider am eigentlichen Problem noch keine Änderung. Das "runOnUiThread" konnte nach wie vor nicht aufgelöst werden. Nichts desto trotz laß ich das so, wie von Dir vorgeschlagen. Es sieht einfach irgendwie besser aus.

Das entscheidende Problem aber war:

swa00 schrieb:
du hast doch bereits deinen Context in MyContext zugewiesen.
runOnUIThread ist ein Objekt aus Diesem
Ergo hast du schlichtweg mal MyContext.runOnUIThread probiert ?

Damit hat er mich doch echt auf's Glatteis geführt. ;-)

runOnUiThread ist eben kein Objekt aus "diesem", sondern aus der Activity. Ich übergebe aber einen Context an den Constructor. Ich brauchte einfach nur den Context auf eine Activity-Variable casten und schon lief's.

Im init():
Code:
myContext = context;
myActivity = (Activity) myContext;

Entscheidende Beiträge aus dem Netz dazu:
How do we use runOnUiThread in Android?
Getting activity from context in android

Gruß, Michael
 
Hallo Michael,

sorry für das "Glatteis" , aber woher sollte ich erkennen ob dein onCreate aus der Activity oder woher auch immer stammt ?



Und da wir Helfenden i.d.R. Dies schon länger (hauptberuflich) tun:

Auch Markus hat nicht auf Anhieb erkennen können , wie die Zusammenhänge sind.
Ich sehe also kein Glatteis , sondern eher eine Antwort auf Teilinformation.

Sei bitte so lieb und poste beim nächsten Male nicht nur Fragmente, sondern ALLES.
Ich hatte es schon Eingangs erwähnt und sorgt auch nicht für zahlreiche Nachfragen und Vermutungen

Viel Erfolg
 
Zuletzt bearbeitet:
Danke für die Rückmeldung. Statt des casten kannst du auch MainActivity.this.runOnUIThread() schreiben. Ob die Lösung von mir elegant ist, darüber kann man streiten. ;)

Noch ein paar Bemerkungen zu deinen Code:
1. Variablennamen wie ll, t, n usw. sind nicht das gelbe von Ei. In einen halben Jahr weiß du nicht, was die Abkürzungen bedeuten. Liebe linearLayout o. timer usw. Das macht den Code lesbarer.
2. Befasse dich mal mit Threads,
3. So wie der Code da steht, hat deine App ein Speicherleck (Memoryleak). Wenn du ein Timer (einen Thread), muss du ihn auch wieder schließen (timer.cancel(): timer = null) , sonst räumt der GC den Timer nicht aus den Speicher. Und es fehlt eine Fehlerbehebung (try and catch).
[doublepost=1523137549,1523137141][/doublepost]PS: Ich glaube das war kein Glatteis, sondern schon der richtige Ansatz. Da ich den Code nicht komplett kenne, aber ich bin mir fast sicher, das der Methodenaufruf setText() der eigentliche Übeltäter ist. Probier mal MainActivity.this.setText(). Würde mich nicht wundern, wenn es dann funktioniert.
 
  • Danke
Reaktionen: swa00
Hi, Markus!

Statt des casten kannst du auch MainActivity.this.runOnUIThread() schreiben.
Das werd ich heute abend vielleicht noch ausprobieren.

1. Variablennamen wie ll, t, n usw. sind nicht das gelbe von Ei. In einen halben Jahr weiß du nicht, was die Abkürzungen bedeuten.
Solche Murk-Namen benutze ich nur bei "quick and dirty" prototypes und auch dann meist nur in so 5-zeiligen Subs. Globale halte ich schon etwas aussagekräftiger.. In der "Produktiv"-Umgebung bin ich da aber auch insgesamt eine ganze Ecke sauberer.

2. Befasse dich mal mit Threads,
Das habe ich unter Delphi, VB.Net, Python und Java eigentlich schon weitestgehend hinter mir. Ich brauch Threads selten, aber wenn, komm ich damit eigentlich schon klar. Aber unter Android sieht die ganze Sache doch immer wieder ganz anders aus. Der ganze Kram um Activity, Context, Fragment, Widget, ... Da sind noch einige Begriffe, die ich vom Verständnis noch nicht so ganz drin habe.

3. So wie der Code da steht, hat deine App ein Speicherleck
Ah, guter Hinweis... Daran hab ich jetzt tatsächlich noch gar nicht gedacht. Da muß ich mir auch noch überlegen, _wo_ ich da das passende Aufräumen einbaue.

PS: Ich glaube das war kein Glatteis, sondern schon der richtige Ansatz.
Das Glatteis war "Context" statt "Activity". Wenn ich eine "Context"-Variable habe, werde ich darauf eben nie nicht ein runOnUiThread basieren lassen können. Deswegen ja das "cannot resolve" die ganze Zeit. Daß ich aber einfach den Context auf eine "Activity"-Variable casten kann, der Hinweis fehlte.

Und ich schrieb eingangs, daß ich eine Activity habe, von der im onCreate() / init() ein Widget erstellt wird, welches den Context übergeben bekommt (nicht explizit geschrieben, aber stand so im Code).

Aber ist ja alles gut, letztendlich hab ich's ja hinbekommen.

Jetzt muß ich nur noch herausfinden, wieso der MediaPlayer immer nur das 1. mal (Minuten-Chime) funktioniert. Aber das ist eine andere Geschichte... ;-)

Gruß, Michael
 
Hi Michael,

Android setzt massiv auf Threads. Die Activities laufen in Threads, man sieht sie nur nicht. In deinen Fall läuft also ein Thread in einem Thread.

Um sauberen Code zu produzieren braucht man den Lifecycle. Hier bekommt man die Möglichkeit, den Code aufzuräumen.
 
  • Danke
Reaktionen: swa00
Hallo Michael,

Das habe ich unter Delphi, VB.Net, Python und Java eigentlich schon weitestgehend hinter mir. Ich brauch Threads selten,

Ich stamme aus der C/C++ Ecke und Du musst bei Android komplett umdenken .
Das hat auch nichts mehr mit Java ansich zu tun, sondern ist lediglich nur die Umsetzungsform

Und wie Markus schon treffend bemerkt hat :

90% der Runtime sollten unter Android inr Threads laufen.
Aufeinanderfolgende "Spaghettifunktionen" a la Delphi, VB und Phyton wird nicht funktionieren.

Du musst auch immer bedenken , dass dir Android jederzeit Werte von Variablen verändern
in den Ablauf eingreifen und auch das gesamte Programm beenden kann.

Deshalb auch besonders ein Auge auf Lifecycle, GC und Listener/Callbacks & Threads werfen.

Jetzt muß ich nur noch herausfinden, wieso der MediaPlayer immer nur das 1. mal (Minuten-Chime) funktioniert. Aber das ist eine andere Geschichte... ;-)

Auch hier vermute ich stark , dass du das Ganze nicht asynchron eingepflegt hast.
 
Zuletzt bearbeitet:

Ähnliche Themen

W
  • waltsoft
Antworten
3
Aufrufe
722
waltsoft
W
K
Antworten
10
Aufrufe
1.508
swa00
swa00
W
  • waltsoft
Antworten
4
Aufrufe
938
waltsoft
W
Zurück
Oben Unten