2013-02-21

Betatester gesucht

Screenshot von TKWeek
Im Laufe von mehreren Jahren hat sich TKWeek von einem kleinen Tool zum universellen Datums- und Kalenderwerkzeug gemausert. Natürlich nutze ich die App selbst sehr intensiv. Die Funktion Tagesübersicht hilft mir dabei, wichtige Informationen eines Tages stets im Blick zu haben. Bislang waren dies Ereignisse und Termine. Jetzt kommen Aufgaben hinzu. TKWeek erfindet hier das Rad nicht neu, sondern bindet Google Tasks ein.

Für den Test suche ich ein paar interessierte Anwender, die die App gründlich testen möchten. Wer Interesse hat, möge sich bitte bei mir melden...

2013-02-19

2013-02-14

Tolles Zubehör

Auf Google Play wird ein wunderschönes kabelloses Ladegerät für das Nexus 4 gezeigt. Dumm nur, dass man es derzeit (Stand 14. Februar 2013) nicht kaufen kann. ;-)

Android ist eine klasse Plattform. Aber was Vertrieb angeht, steht der Suchmaschinenprimus ohne Frage noch ganz am Anfang.

2013-02-02

Praktische Helferlein: AtomicFile

Jedes Android-Release enthält haufenweise neu hinzugekommene Klassen, die die App-Entwicklung vereinfachen könnten, wenn Programmierer sie kennen würden. Natürlich ist es verständlich, dass sich nicht jeder durch den API Difference Report wühlen möchte. Deshalb schaffen es viele praktische Helferlein nie in die Top Ten.
Zwinkerndes Smiley
Das ist schade. Deshalb stelle ich hin und wieder nützliche Klassen vor. In diesem Post soll es um AtomicFile gehen. So richtig neu ist die Klasse zwar nicht. Sie hat aber ihr Dasein vor API-Level 17 im internen Paket com.android.internal.os gefristet.

Die Idee ist, vor Problemen beim Schreiben von Daten zu schützen, indem eine Backup-Datei angelegt wird. Diese bleibt bestehen, bis alle Schreiboperationen erfolgreich ausgeführt wurden. Falls Sie nun sagen, dass das ein übliches Vorgehen ist, so haben Sie mit Ihrem Einwand natürlich Recht. Sauber implementiert, entsteht aber ein ordentliches Stück Boilerplate-Code, den man sich nun sparen kann. AtomicFile steht ab API-Level 17 zur Verfügung, ist aber auch in der v4 Support-Bibliothek enthalten. Googles Implementierung ist relativ einfach gehalten. Die Klasse ist beispielsweise nicht für gleichzeitige Zugriffe von unterschiedlichen Threads ausgelegt. Es ist deshalb die Aufgabe des Entwicklers, dies ggf. durch geeignete Lock-Mechanismen zu verhindern. Wie so etwas aussehen kann, zeigt das folgende Quelltextfragment. Es geht davon aus, das die Nutzdaten in einem byte-Feld vorliegen.

  private synchronized void save(byte[] buffer) {
    File baseName = new File(getFilesDir(), TAG);
    AtomicFile atomicName = new AtomicFile(baseName);
    FileOutputStream fos = null;
    try {
      fos = atomicName.startWrite();
      fos.write(buffer);
      atomicName.finishWrite(fos);
    } catch (IOException e) {
      if (fos != null) {
        atomicName.failWrite(fos);
      }
      e.printStackTrace();
    }
  }

Die Variable TAG ist übrigens folgendermaßen definiert:

  private static final String TAG = MainActivity.class.getSimpleName();

Das ist praktisch, um Logausgaben zu machen, und taugt gleichzeitig als Dateiname. Ist Ihnen übrigens aufgefallen, dass ich im Fehlerfall die Methode failWrite(), normalerweise aber finishWrite() aufrufe? Auf diese Weise steuern Sie, ob Änderungen übernommen oder verworfen werden sollen.

Lassen Sie uns nun die Lademethode implementieren. Sie ist sehr einfach gehalten, lädt durch Aufruf von readFully() alle Daten in ein byte-Array und wandelt das in einen String.

  private synchronized String load() {
    String result = null;
    File baseName = new File(getFilesDir(), TAG);
    AtomicFile atomicName = new AtomicFile(baseName);
    try {
      byte[] buffer = atomicName.readFully();
      result = new String(buffer, Charset.defaultCharset());
    } catch (IOException e) {
      e.printStackTrace();
    }
    return result;
  }

Falls sich alte Java-Hasen übrigens am Kopf kratzen.. String und byte-Arrays, da war doch was, oder? Stimmt, beim Lesen von Byte-Strömen ist ja nicht ohne weiteres erkennbar, welche Zeichenkodierung dem Datenstrom zu Grunde liegt. Deshalb übergebe ich dem Konstruktor als zweiten Parameter Charset.defaultCharset(). Unter Android ist das UTF-8. Um das Ganze abzurunden, hier noch die Zeilen, die load() und save() aufrufen…

    save("Hallo Welt".getBytes(Charset.defaultCharset()));
    String s = load();
    Log.d(TAG, "load(): " + s);

Wie Sie sehen, stelle ich sicher, dass die Zeichenkette in die richtig kodierte Bytefolge umgewandelt wird.