Z racji tego, że moja aplikacja jest już w fazie testów nie-u-mnie, byłem zmuszony napisać jakiś sposób do zgłaszania błędów – próba odtworzenia błędu u siebie(czy to na emulatorze, czy też telefonie) była bardzo trudna, albo wręcz niemożliwa. Nie sposób było powielić każde warunki(wersję oprogramowania, specyfikację urządzania, konfigurację samej aplikacji, o której zapominałem albo nie byłem w stanie się jej dowiedzieć). Jako iż od jakiegoś czasu staram się nie kombinować z robieniem jakichś pseudo-uniwersalnych i przekombinowanych cudów, zrobiłem to po najmniejszej linii oporu –
raport to zwykły mail(albo nawet nie, ale o tym za chwilę) z najpotrzebniejszymi informacjami(log, informacje o urządzeniu, informację od użytkownika).
Android posiada „w standardzie” klasę do tworzenia dialogów typu tak/nie. Nie jest to może tak proste jak w .NET(używamy MessageBox i już), ale ma to swoje uzasadnienie(wszechobecne klasy, ciut większa możliwość zmian, nieblokowanie wątku głównego, java-like utrudnianie pisania aplikacji itp.). Służy do tego klasa AlertDialog w połączeniu z metodami activity: showDialog(żądamy pokazania dialogu), onCreateDialog(zwracamy w niej klasę dialogu)*.
@Override
protected Dialog onCreateDialog(int id)
{
// Używamy tylko jednego dialogu, nie musimy id sprawdzać.
return new AlertDialog.Builder(this)
.setMessage("Czy chcesz wysłać raport o błędach?").setCancelable(false)
.setPositiveButton("Tak", this.SendMail) // SendMail to implementacja DialogInterface.OnClickListener
.setNegativeButton("Nie", this.DoNotSendMail) // jw.
.create();
}
Samo stworzenie i wysłanie(oddelegowanie do aplikacji wysyłającej) maila jest bardzo proste –
tworzymy odpowiedni intent z Intent.ACTION_SEND, zapisujemy co potrzeba, uruchamiamy activity. Otworzy to domyślną aplikację przypisaną tej akcji. Możemy również wykorzystać Intent.createChooser, by pozwolić użytkownikowi wybrać, jak ma wysłać raport(dzięki czemu może np. wysłać go przez bluetooth czy cokolwiek innego – bardzo przydatne!).
Intent email = new Intent(Intent.ACTION_SEND);
email.setType("text/html");
email.putExtra(Intent.EXTRA_EMAIL, new String[] {"adresy@email.pl"}); // To musi być tablica String[]
email.putExtra(Intent.EXTRA_SUBJECT, "Temat"); // Temat maila
email.putExtra(Intent.EXTRA_TEXT, Html.fromHtml("<p>Jakaś <b>treść</b></p>")); // Przykładowa treść
Uri log = Logger.GetLogFile(); // Możemy pobrać adres do pliku z logiem
if (log != null)
email.putExtra(Intent.EXTRA_STREAM, log); // I dodać go jako załącznik
this.startActivity(Intent.createChooser(email, "Wyślij raport za pomocą:")); // Omawiany chooser

Najbardziej potrzebne było mi zgłaszanie błędów przy niespodziewanym wyjątku – one crashują aplikację, czego nie lubię. Rozwiązanie nie jest takie proste, jakby się mogło wydawać(Thread.setDefaultUncaughtExceptionHandler nie wystarcza). Wszystko jest OK, gdy wyjątek nie poleci z głównego wątku – wtedy bez problemu można otworzyć dodatkową activity, dialog czy cokolwiek innego i spytać się, czy wysłać taki raport(zależało mi na tym). Gdy jest to wątek main sprawa się komplikuje – aplikacja nie może otworzyć niczego z prostej przyczyny – Looper głównego wątku przestał działać i nie da się go wskrzesić, by skontaktował się z użytkownikiem(tylko system może to zrobić).
Rozwiązanie, które udało mi się znaleźć, jest dość ciekawe, a mianowicie wykorzystuje AlarmManager do ponownego uruchomienia aplikacji. Dzięki zastosowaniu intentów nie musimy jakoś oddelegowywać aplikacji do właściwego activity – robi to za nas system, my tylko musimy wskazać o którą nam chodzi. Cały kod sprowadza się na utworzeniu Intentu z naszą aplikacją, PendingIntentu oznajmującego, że chodzi nam o activity i dodania alarmu.
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
{
public void uncaughtException(Thread thread, Throwable ex)
{
Intent i = new Intent(getApplicationContext(), BugReport.class);
PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 0, i, i.getFlags());
AlarmManager mgr = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis(), pi); // Dzięki ustawieniu alarmu na czas aktualny użytkownikowi od razu wyświetli się formatka, ale dla bezpieczeństwa można dodać kilka(set) milisekund(by zdążyć wyjść)
System.exit(1);
}
});
Możemy zaraz potem zamknąć aktualną instancję aplikacji(przez System.exit albo Process.killProcess), a AlarmManager zatroszczy się o ponowne uruchomienie aplikacji(dzięki temu mamy nowy wątek główny). Nie ma większego znaczenia, gdzie ustawimy domyślny handler, ale chyba najbardziej odpowiednie jest Application.onCreate.
Warto dać też możliwość wysyłania raportów, gdy takowy wyjątek nie wystąpił. Gdy mamy wszystko gotowe nie jest to bardzo trudne – wystarczy wystartować wcześniej przygotowaną activity. Warto byłoby też pominąć pytanie się użytkownika, czy chce to zrobić – można założyć, że przemyślał naciśnięcie przycisku „raportuj błąd”. Tutaj sprawa się trochę komplikuje – nie tworzymy activity bezpośrednio. Przekazujemy za to intent, któremu możemy przypisać akcję. Wtedy w metodzie Activity.onCreate, wystarczy sprawdzić tą akcje i zdecydować, co zrobić.
Dołączam też cały kod activity
BugReport, może komuś się przyda(kod używa kilku stałych do zasobów, trzeba je pozmieniać).
* – okazuje się, że Android jeszcze bardziej komplikuje tę sprawę – aktualnie powinno używać się DialogFragment