Android DigitaleKlok widget Redux

Door Wiebbe op zaterdag 20 juni 2009 09:13 - Reacties (6)
CategorieŽn: android, java, Views: 4.739

Het werkt niet :'(

Na mijn post van gister liep ik toch tegen een aantal punten aan waar ik niet tevreden mee was. Misschien heeft een van jullie het wel ook gemaakt, of zag hij meteen het probleem al met zijn leet-code ogen.

De refresh rate van de widget is 60.000 ms. Wat dus precies 1 minuut is. Op zich zou dat natuurlijk genoeg moeten zijn om de klok altijd goed te houden. Maar hier komt het probleem.

Stel nou dat de plugin wordt toegevoegd op 16:00:30. Je zal dan netjes zien staan: 16:00 in het klokje. Maar de timer wordt ook precies om deze tijd gestart, dus na 1 minuut als de timer afloopt zal hij de update geven.

Maar dit is dus het probleem, de "echte" tijd zal om prcies 16:01:00 omspringen en dit weergeven. Maar onze digitale klok laat pas de goede tijd zien als de update eraan komt, dus om 16:01:30 springt hij pas op 16:01!

Nu is dit natuurlijk makkelijk op te lossen door de tijd gewoon op 1 seconde te zetten, of door je Android phone precies om 16:00:00 aan te zetten 8)7. Maar met die eerste oplossing ben je de hele tijd updates aan het doen voor echt iets loos. Ten eerste laten we geen seconden zien, dus lekker loos om het zo vaak te updaten, en als we dat wel zouden doen, kost dit gewoon een stuk meer aan batterij verbruik voor iets wat eigenlijk niks doet!

De oplossing?

Natuurlijk kom ik hier met een oplossing. Het eerste wat ik ging doen is kijken hoe het analoge klokje als widget zijn werk doet. Die update namelijk ook netjes op de goede tijd! Maar de analoge klok widget is een android.widget.

Ja, lekker duidelijk inderdaan 8)7 . Alle "controls" die we in android gebruiken noemen ze widget. Dus je progressbar, TextView en Buttons zijn allemaal widgets. De widgets die we zien op ons homescreen noemen we AppWidgets.

Maar goed, de analoge klok is dus een AppWidget die de Widget AnalogClock laat zien. Dit is op zich best sneaky, want die control wordt op een slimme interne manier afgehandeld zonder requests te verzenden. De update timer staat dan ook op 0! wat dus betekend dat hij nooit een update doet.

Maar dit kan ik niet gebruiken voor een digitale klok, simpelweg omdat de analogclock widget altijd zo'n mooi klokje weergeeft.

Nu zou je denken, "jongen, zeik niet zo en schrijf een nieuwe control die inherit van TextView maar ook meteen een klok is die je net zo mooi update als de AnalogControl". Ja, precies, dacht ik ook. Maar helaas. De RemoteView (waar onze controls op staan in de AppWidget) staat maar een zeer beperkt aantal Widgets toe. Dit is volgens de Google Gurus gedaan om er voor te zorgen dat ze remote kunnen worden gebruikt
It's an intentional limitation. Only views that are implemented by the
framework can be used, and only those that are annotated as safe for
remoting.
I don't know if there is a list provided in the SDK docs, but as of 1.5 the
supported Views are:
AbsoluteLayout
AnalogClock
Button
Chronometer
FrameLayout
ImageButton
ImageView
LinearLayout
ProgressBar
RelativeLayout
TextView
(overgens in deze discussie staat er veel nuttige info voor developers, met opmerkingen door de google dudes: App widgets and remote views

Dus, we zitten met die Widgets. TextView hebben wel al gebruikt, en das niet echt nuttig, AnalogClock is ook niet bruikbaar. En toen kwamen we bij "Chronometer". Dit is echt one weird ass Widget al zeg ik het zelf!

Chronometer

De control lijkt eerst gewoon een soort stopwatch te zijn. Hij heeft de methods start()en stop().

Maar er zit een grote MAAR aan vast. De widget werkt eigenlijk op deze manier:

Je start de timer met, Chronometer.start(). Hij neemt een snapshot van de huidige SystemClock.elapsedRealtime() (wat de huidige uptime is van je gPhone) en gaat daar het verschil van nemen en laat die zien.

Dit is ook meteen het probleem. Stel dat je hem op pause zet met Chronometer.stop(). Je ziet de tijd stoppen (bijvoorbeeld 00:10:00). Als je hem 10 seconden later weer start, dan verwacht je dat hij verder gaat met 00:11:00, maar helaas!

De tijd die hij laat zien is 00:20:00, omdat hij als basis de "snapshot" neemt en aangezien de tijd is doorgelopen en nu dis 10 seconden later is, is die tijd ook groter geworden. Voor een stopwatch is het dus echt een knudde control.

Maar als klok? Gelukkig kan dat wel wat handiger, met wat "maaren" natuurlijk. De tijd die hij als basis neemt is dus de uptime, hoe kan je dit gebruiken om de klok weer te geven?

In principe is de "tijd" niets meer dan het aantal miliseconden sinds het begin van de dag, dus om 20-06-2009 00:00:00.

Stel dat we zelf de start tijd mogen aangeven van de chronometer en vanaf wanneer hij ging tellen. Dan hoeven we simpel we de timestamp van 20-06-2009 00:00:00 op te vissen mee te geven en te starten. Maar uiteraard werkt dat niet voor onze control.

De Chronometer heeft de functie setBase (long base). Met deze functie geeft je het verschil mee ten op zichte van SystemClock.elapsedRealtime() en wanneer hij moet gaan tellen.

Dus als je invoert -60000, dan trekt hij 1 minuut af van de SystemClock.elapsedRealtime() klok en laat die tijd dan zien.

Dus als we dan de goede tijd willen zien, moeten we dus zorgen dat de tijd waarop de chronometer "start" precies om 00:00:00 is, want dan krijgen we PRECIES de goede tijd! Dus elapsed tijd weten we, dus moeten we van de huidige tijd precies het aantal miliseconden tellen. Nou dan dit waarschijnlijk weer een stuk netter, maar ik ken niet alle libraries en dit werkt ;)


Java:
1
long mseconds = c.get(Calendar.HOUR_OF_DAY)  * 60 * 60 * 1000 + c.get(Calendar.MINUTE)  * 60 * 1000 + c.get(Calendar.SECOND)  * 1000 + c.get(Calendar.MILLISECOND);


Hiermee, krijgen we precies het huidige aantal miliseconden van de dag.

We willen dus het verschil tussen de elapsed tijd en de huidige tijd weten, dus trekken we van de elapsed tijd de huidige tijd af:


Java:
1
mseconds = SystemClock.elapsedRealtime() - mseconds;



Nu hebben we dus het aantal miliseconden wat we van SystemClock.elapsedRealtime() zouden moeten afhalen om precies op 00:00:00 uit te komen!

Als we dus dit (negatieve getal dus) mee geven aan de setBase() krijgen we precies de goede tijd te zien!

De gehele code van de DigitaleKlok/java:


Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;
        final Calendar c = Calendar.getInstance();
        
        long mseconds = c.get(Calendar.HOUR_OF_DAY)  * 60 * 60 * 1000 + c.get(Calendar.MINUTE)  * 60 * 1000 + c.get(Calendar.SECOND)  * 1000  + c.get(Calendar.MILLISECOND);
        
        mseconds = SystemClock.elapsedRealtime() - mseconds;

        // Perform this loop procedure for each App Widget that belongs to this provider
        for (int i=0; i<N; i++) {
            int appWidgetId = appWidgetIds[i];

        // Get the layout for the App Widget and attach an on-click listener to the button
            RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
           
            views.setChronometer(R.id.Klok, mseconds, "%s", true);
            
            // Tell the AppWidgetManager to perform an update on the current App Widget
            appWidgetManager.updateAppWidget(appWidgetIds[0], views);

            views.setTextViewText(R.id.Datum,    new StringBuilder()
            .append(DateFormat.format("EE, dd MMM yy", c)));
            
            // Tell the AppWidgetManager to perform an update on the current App Widget
            appWidgetManager.updateAppWidget(appWidgetId, views);
        }



Het voordeel van deze manier is dat we nu eigenlijk nog maar elke uur een update hoeven uit te voeren. De interne afhandeling van de remote view is veel goedkoper dan de BroadcastReceiver voor updates en kost dus minder van de gebruikers batterij.

Het nadeel (of niet) is dat de Chronometer geen format functie heeft. De tijd is dus ALTIJD weergegeven op de manier van: MM:SS of H:MM:SS. Je kan gelukkig wel extra text meegeven.

Dus als we doen: setFormat ("0 %s"), wordt de %s vervangen met H:MM:SS en dus 0 wordt er voorgeplaatst. Met wat snelle ifjes kunnen we kijken of de tijd onder het uur is (dus na middernacht) en dan "00:" er aan vast te plakken of als het onder 10 uur is (met 1 getal dus) "0" er voor te plakken.

De datum kan tot een uur nu achter lopen om middernacht, aangezien de update precies om 11:59 kan vallen. Maar daar maak ik mij een stuk minder zorgen om dat de tijd die 1 minuut achter loopt en je batterij leeg vreet ;)

Volgende: Android, Eieren, Services en Actions? 06-'09 Android, Eieren, Services en Actions?
Volgende: Android homescreen widgets bouwen 06-'09 Android homescreen widgets bouwen

Reacties


Door Tweakers user GZFan, zaterdag 20 juni 2009 09:50

Prachtige blog, mooi geschreven vanuit een probleemstelling, net als je vorige.
Leest gewoon lekker weg! Ga zo door! :)

Door Tweakers user Rubinski, zaterdag 20 juni 2009 11:16

Slim opgelost! :)

Door Tweakers user smokalot, zaterdag 20 juni 2009 17:57

Jammer dat ze bij Android niet goed gekeken hebben naar KDE's plasma, dat is zo opgezet dat applets nooit hoeven te pollen, er wordt een methode aangeroepen als de data verandert.

Maar goed, ik vind Android nu al best lekker werken, en ik verwacht dat er heel veel van dit soort dingen gefixt zullen worden in het komende jaar.

Door Tweakers user Snake, zondag 21 juni 2009 12:54

@smokalot: Jawel, er zijn bepaalde events systemwide waarop je je eventhandler kan hangen :)

Door Tweakers user phizzie, maandag 5 april 2010 15:44

Misschien wat laat, maar wat ik begrepen heb van de strucuur van Android, is dat je met intents en de public en private settings veel 'handgrepen' kunt maken waarop andere software kan inspringen.

Heeft Android basisgegevens als datum en tijd niet in een eenvoudig en toegankelijk pakketje gestopt wat je met je intent om te updaten kunt aanspreken?

Door Tweakers user Wiebbe, maandag 5 april 2010 15:45

Heb je de volgende wel gelezen? :P

rand_muse: Android, Eieren, Services en Actions?

Daar vang ik de Intent af die "TICKED" als de klok geupdate wordt ^^

Reageren is niet meer mogelijk