Home > android | GAE > Androidアプリのバグ報告システムを考える

Androidアプリのバグ報告システムを考える

MacとかWindowsでアプリが予期せぬ不具合で強制終了した後におもむろに出てくる「バグ報告」。
あれがSimejiにも欲しい。と思って作ってみたら予想以上に役に立ったので、ここでそのシステムを公開します。
アプリ開発者は絶対入れた方がいいですよ。ユーザの協力が得られればアプリの安定性向上に役に立つ事間違いなしです。

動作概要
catchしなくてよい例外

JavaにはNullPointerExceptionなどのcatchしなくてもclass load validationを素通りできる例外があります。
バグの多くはそういった例外を考慮しないことのようです。
なので、今回はそういった例外の「IndexOutOfBoundsException」を発生させます。

ボタンをタップすると例外が発生します。

oobBtn.setOnClickListener(new View.OnClickListener(){
    public void onClick(View v) {
    int index = 5;
    String[] strs = new String[index];
    String str = strs[index];//ここでIndexOutOfBoundsException
}});

IndexOutOfBoundsExceptionも例外処理を記述しなくてもコンパイルエラーにならない例外です。
この仕組みのおかげでプログラムが書きやすくなっていますが、バグも入りやすいので要注意です。

例外が発生すると

ボタンをタップすると例外が発生するのですがcatchされずにアプリが強制終了します。

どこでこの例外が発生したのかが分かると、多くの問題は解決可能です。
これを捕捉するシステムを考えるのが本エントリの目的です。

catchしない例外を補足する

Javaにはcatchしなかった例外を補足するThread.UncaughtExceptionHandlerという仕組みが元々備わっています。
今回はコレを利用します。

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Context context = getApplicationContext(); //修正 @2010/06/29
    Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler(context)); //修正 @2010/06/29
}

*アプリにつき1回だけハンドラを登録するだけでOKです。スレッドは関係ありません。(追記: 2010/01/23)
MyUncaughtExceptionHandlerの引数にActivityを渡すとメモリリークする可能性があります。(修正: 2010/06/29, via @95kugo
onCreateメソッドの中でUncaughtExceptionHandlerを登録しておきます。
このハンドラは独自に拡張したMyUncaughtExceptionHandlerです。
どこにもcatchされなかった例外は、最終的にこのハンドラに渡されますので、
捕捉できなかった例外をハンドラ内で処理します。

public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread th, Throwable e) {
        //catchされなかった例外は最終的にココに渡される
    }
}

catchされなかった例外は、最終的にuncaughtExceptionメソッドの引数に渡されコールバックされます。
そのため、このメソッド内にバグ報告の処理を書きます。
引数のThrowableから、例外発生点のスタックトレースを取り出します。

StackTraceElement[] stacks = e.getStackTrace();

開発者にとって、例外発生経路と発生点はバグ修正するにあたり、とても役立つ情報です。
この情報(バグ情報)をサーバなどに送信し、開発者に通知します。
以上がAndroidにおけるバグ報告システムの概要でした。とても簡単です。

続いて、Simejiで使っている具体的なシステムをご紹介します。
あくまで一例なので、他に良い方法があるかもしれません。
良いアイデアがあれば是非教えて下さい。コメント、Twitter待ってます^^

Simejiのバグ報告システム
設計
  1. catchしていない例外が発生した場合、そのスタックトレースを外部記憶装置(SDカード等)に保存
  2. アプリ起動時にSDカード内にバグ情報ファイルが存在する場合は、その内容を送信

設計方針の理由は単純です。なんらかの理由でアプリが終了しようとしているので、
スタックトレースなどの情報をアプリ内部に持たせるのは不可能(もしくは崩れるかもしれない)なのでSDカードに保存します。
また、同じ理由で、次の起動時にファイルをチェックし、ファイルが存在すればバグ情報を送信するようにしました。

スタックトレースをSDカードに保存する

まずは、catchされなかった例外が発生した時に、そのスタックトレースをSDカードに保存します。

public void uncaughtException(Thread th, Throwable t) {
    //catchされなかった例外処理
    try {
        saveState(t);//ここでスタックトレースを保存
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}
 
private void saveState(Throwable e) throws FileNotFoundException {
    StackTraceElement[] stacks = e.getStackTrace();//スタックトレース
    File file = BUG_REPORT_FILE;//保存先
    PrintWriter pw = null;
    pw = new PrintWriter(new FileOutputStream(file));
    StringBuilder sb = new StringBuilder();
    int len = stacks.length;
    for (int i = 0; i < len; i++) {
        StackTraceElement stack = stacks[i];
        sb.setLength(0);
        sb.append(stack.getClassName()).append("#");//クラス名
        sb.append(stack.getMethodName()).append(":");//メソッド名
        sb.append(stack.getLineNumber());//行番号
        pw.println(sb.toString());//ファイル書出し
    }
    pw.close();
}

例外発生時のスタックトレースはBUG_REPORT_FILEに保存しています。
BUG_REPORT_FILEの中身は以下の通りです。

private static File BUG_REPORT_FILE = null;
static {
    String sdcard = Environment.getExternalStorageDirectory().getPath();
    String path = sdcard + File.separator + "bug.txt";
    BUG_REPORT_FILE = new File(path);
}

「/sdcard/bug.txt」というファイルに書出しています。
外部ストレージが/sdcardにマウントされるかは実装次第なので、ちょっと煩わしいですがEnvironmentを経由させておきます。

これで、catchされなかった例外が発生したスタックトレースを保存できました。
次に、アプリ起動時に、この情報をサーバに報告する部分を説明します。

バグ情報を送信する

アプリ起動時に、先ほどのバグ情報ファイルが存在するかをチェックし、
存在するならその内容をサーバに送信し、存在しないなら無視してアプリを起動します。

public void onStart(){
    super.onStart();
    //前回バグで強制終了した場合はダイアログ表示
    MyUncaughtExceptionHandler.showBugReportDialogIfExist();
}
 
public static final void showBugReportDialogIfExist() {
    File file = BUG_REPORT_FILE;
    if (file != null & file.exists()) {
        AlertDialog.Builder builder = new AlertDialog.Builder(sContext);
        builder.setTitle("バグレポート");
        builder.setMessage("バグ発生状況を開発者に送信しますか?");
        builder.setNegativeButton("Cancel", new OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                finish(dialog);//ダイアログの消去とファイルの削除
            }});
        builder.setPositiveButton("Post", new OnClickListener(){
            public void onClick(DialogInterface dialog, int which) {
                postBugReportInBackground();//バグ報告(別スレッドでファイルを削除)
                dialog.dismiss();
            }});
        AlertDialog dialog = builder.create();
        dialog.show();
    }
}

*SDカード内のファイル操作をマルチスレッドを無視した実装になっていましたので修正しました。(修正:2010/01/23 via. @mokkouyou
ユーザの許可無くサーバに情報を送信するのはevilに感じるので、
ダイアログを表示し、送信の許可をユーザに求めます。

Postボタンがそれです。
PostボタンがタップされるとpostBugReportInBackgroundメソッドでバグ情報をサーバに送信します。

private static void postBugReportInBackground() {
    new Thread(new Runnable(){
        public void run() {
            postBugReport();
            BUG_REPORT_FILE.delete();
        }}).start();
}
 
private static void postBugReport() {
    List<NameValuePair> nvps = new ArrayList<NameValuePair>();
    String bug = getFileBody(BUG_REPORT_FILE);
    nvps.add(new BasicNameValuePair("dev", Build.DEVICE));
    nvps.add(new BasicNameValuePair("mod", Build.MODEL));
    nvps.add(new BasicNameValuePair("sdk", Build.VERSION.SDK));
    nvps.add(new BasicNameValuePair("ver", sPackInfo.versionName));
    nvps.add(new BasicNameValuePair("bug", bug));
    try {
        HttpPost httpPost = new HttpPost("http://foo.bar.org/bug");
        httpPost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
        DefaultHttpClient httpClient = new DefaultHttpClient();
        httpClient.execute(httpPost);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

HTTP通信は、ある程度の処理時間がかかりますので、別スレッドで実行するようにしています。
見た目上のレスポンス性能は重要です。
getFileBodyメソッドはBUG_REPORT_FILEの中身をStringで取得しています。
スタックトレースの他に、デバイス名やSDKのバージョンなど、
バグが発生した個体情報も追加しておきます。
特に、アプリのバージョン番号「sPackInfo.versionName」は重要なので追加しておいた方が良いです。

sPackInfo変数は以下のようにして取得しています。

public MyUncaughtExceptionHandler(Context context) {
    try {
        //パッケージ情報
        sPackInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
    } catch (NameNotFoundException e) {
        e.printStackTrace();
}

また、バグ報告の送信の有無に関わらず、BUG_REPORT_FILEを消しておきます。
このファイルの存在がトリガーになってバグ報告ダイアログが表示されるので、
一度表示した後はファイルを消しておきます。

private static void finish(DialogInterface dialog) {
    File file = BUG_REPORT_FILE;
    if (file.exists()) {
        file.delete();
    }
    dialog.dismiss();
}

以上でhttp://foo.bar.org/bugにPOSTリクエストでバグ情報の送信が完了しました。
次にサーバ側です。
Google App Engine(GAE)を使って、バグ情報を格納し、開発者へメール連絡する仕組みをご紹介します。

GAEでバグレポート

データを格納してメールする程度の簡単な処理なので、
GAEは、記述量の少ないPythonで実装します。
データ構造は送信される内容のままです。

class BugData(db.Model):
    device = db.StringProperty()#device name
    model = db.StringProperty()#model name
    sdk = db.StringProperty()#sdk name
    version = db.StringProperty()#version number
    bug = db.TextProperty()#stacktrace
    create = db.DateTimeProperty(auto_now_add=True)

あとは、POSTリクエストを受けて、このデータベースに登録するだけです。

class BugReportHandler(webapp.RequestHandler):
 
    def get(self):
        self.get_or_post()
 
    def post(self):
        self.get_or_post()
 
    def get_or_post(self):
        dev = self.request.get("dev")
        mod = self.request.get("mod")
        sdk = self.request.get("sdk")
        ver = self.request.get("ver")
        bug = self.request.get("bug")
 
        #insert a new element
        db = BugData(device=dev, model=mod, sdk=sdk, version=ver, bug=bug)#row
        db.put()#save
 
        #report with email
        mail.send_mail(sender="dev@gmail.com", to="dev@gmail.com", subject="Bug Report", body=bug)
 
        self.response.out.write('Success!')

HTTPリクエストBODYから各要素(devやbugなど)を取り出し、
db.putメソッドでデータベースに登録しています。

スタックトレースの内容をmail.send_mailで開発者にメール送信しています。
これにより、開発者はブラウザでチェックしなくてもメールでバグの発生を知ることができます。
より利便性を高めるには、同じバグ報告をメールしないようにフィルタリング処理を追加するなどが考えられます。

以上が、AndroidアプリとGAEを使ったバグ報告システムでした。
バグの無いアプリが一番良いのですが、バグの無いプログラムは無いとも言われます。
バグとうまく付き合っていく方法として本システムはとても役立っていますので、
開発者の皆さんは参考にして頂き、ご自身のアプリを育てていって下さい。

おわりに

去年(2009年)の忘年会から、このネタをBlogにアップして欲しいと言われ続けて、今頃ようやく公開しました。
遅くなってスミマセン。これからも、こういった開発コネタをアップし、開発者のサポートができればと思います。
また、デ部でも、code snippetなどのノウハウを溜める仕組みができたらイイなぁ…
小さなソースコードを溜めていけるWebシステム(CMS?)を構築できる技術者を絶賛募集中。手伝って下さい><

Androidソースコード

Androidアプリ部分のソースコードを公開しておきます。
自由にご利用下さい。
BugReport.zip

内容は下記の通りです。

BugReportActivity.java
package com.adamrocker.android.bugreport;
 
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
 
public class BugReportActivity extends Activity {
 
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Context context = getApplicationContext(); //修正 @2010/06/29
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler(context));//修正 @2010/06/29
        setContentView(R.layout.main);
        Button oobBtn = (Button)findViewById(R.id.oob_btn);
        oobBtn.setOnClickListener(new View.OnClickListener(){
			public void onClick(View v) {
		    	int index = 5;
		    	String[] strs = new String[index];
		    	String str = strs[index];//ここでIndexOutOfBoundsException
			}});
    }
 
	public void onStart(){
		super.onStart();
		//前回バグで強制終了した場合はダイアログ表示
		MyUncaughtExceptionHandler.showBugReportDialogIfExist();
	}
}
MyUncaughtExceptionHandler.java
package com.adamrocker.android.bugreport;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
 
public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
	private static File BUG_REPORT_FILE = null;
	static {
		String sdcard = Environment.getExternalStorageDirectory().getPath();
		String path = sdcard + File.separator + "bug.txt";
		BUG_REPORT_FILE = new File(path);
	}
 
	private static Context sContext;
	private static PackageInfo sPackInfo;
	private UncaughtExceptionHandler mDefaultUEH;
	public MyUncaughtExceptionHandler(Context context) {
		sContext = context;
		try {
			//パッケージ情報
			sPackInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
	}
 
	public void uncaughtException(Thread th, Throwable t) {
		try {
			saveState(t);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		mDefaultUEH.uncaughtException(th, t);
	}
 
	private void saveState(Throwable e) throws FileNotFoundException {
		StackTraceElement[] stacks = e.getStackTrace();
		File file = BUG_REPORT_FILE;
        PrintWriter pw = null;
        pw = new PrintWriter(new FileOutputStream(file));
        StringBuilder sb = new StringBuilder();
        int len = stacks.length;
        for (int i = 0; i < len; i++) {
            StackTraceElement stack = stacks[i];
            sb.setLength(0);
            sb.append(stack.getClassName()).append("#");
            sb.append(stack.getMethodName()).append(":");
            sb.append(stack.getLineNumber());
            pw.println(sb.toString());
        }
        pw.close();
	}
 
	public static final void showBugReportDialogIfExist() {
		File file = BUG_REPORT_FILE;
		if (file != null & file.exists()) {
			AlertDialog.Builder builder = new AlertDialog.Builder(sContext);
			builder.setTitle("バグレポート");
			builder.setMessage("バグ発生状況を開発者に送信しますか?");
			builder.setNegativeButton("Cancel", new OnClickListener(){
				public void onClick(DialogInterface dialog, int which) {
					finish(dialog);
				}});
			builder.setPositiveButton("Post", new OnClickListener(){
				public void onClick(DialogInterface dialog, int which) {
					postBugReportInBackground();//バグ報告
					dialog.dismiss();
				}});
			AlertDialog dialog = builder.create();
			dialog.show();
		}
	}
 
	private static void postBugReportInBackground() {
		new Thread(new Runnable(){
			public void run() {
				postBugReport();
				File file = BUG_REPORT_FILE;
				if (file != null && file.exists()) [
					file.delete();
				}
			}}).start();
	}
 
	private static void postBugReport() {
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        String bug = getFileBody(BUG_REPORT_FILE);
        nvps.add(new BasicNameValuePair("dev", Build.DEVICE));
        nvps.add(new BasicNameValuePair("mod", Build.MODEL));
        nvps.add(new BasicNameValuePair("sdk", Build.VERSION.SDK));
        nvps.add(new BasicNameValuePair("ver", sPackInfo.versionName));
        nvps.add(new BasicNameValuePair("bug", bug));
        try {
        	HttpPost httpPost = new HttpPost("http://foo.bar.org/bug");
            httpPost.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));
            DefaultHttpClient httpClient = new DefaultHttpClient();
            httpClient.execute(httpPost);
        } catch (IOException e) {
            e.printStackTrace();
        }
	}
 
	private static String getFileBody(File file) {
		StringBuilder sb = new StringBuilder();
		try {
			BufferedReader br = new BufferedReader(new FileReader(file));
			String line;
			while((line = br.readLine()) != null) {
				sb.append(line).append("\r\n");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return sb.toString();
	}
 
	private static void finish(DialogInterface dialog) {
		File file = BUG_REPORT_FILE;
		if (file.exists()) {
			file.delete();
		}
		dialog.dismiss();
	}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.adamrocker.android.bugreport"
      android:versionCode="1"
      android:versionName="1.0">
    <application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="false">
        <activity android:name=".BugReportActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
 
    </application>
    <uses-sdk android:minSdkVersion="3" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>
GAEソースコード

GAEのソースコードは下記の通りです。

app.yaml
application: bug-store
version: 1
runtime: python
api_version: 1
 
handlers:
- url: /bug
  script: bugs.py

/bug/.*となっていたのを修正しました(at 15 Jan, 2011)

bugs.py
from google.appengine.ext import db, webapp
from google.appengine.ext.webapp import util
from google.appengine.api import mail
 
class BugData(db.Model):
    device = db.StringProperty()#device name
    model = db.StringProperty()#model name
    sdk = db.StringProperty()#sdk name
    version = db.StringProperty()#version number
    bug = db.TextProperty()#stacktrace
    create = db.DateTimeProperty(auto_now_add=True)
 
class BugReportHandler(webapp.RequestHandler):
 
    def get(self):
        self.get_or_post()
 
    def post(self):
        self.get_or_post()
 
    def get_or_post(self):
        dev = self.request.get("dev")
        mod = self.request.get("mod")
        sdk = self.request.get("sdk")
        ver = self.request.get("ver")
        bug  = self.request.get("bug")
 
        #insert new element
        db = BugData(device=dev, model=mod, sdk=sdk, version=ver, bug=bug)
        db.put()
 
        #report with email
        mail.send_mail(sender="developer@gmail.com", to="developer@gmail.com", subject="Bug Report", body=bug)
 
        self.response.out.write('Success!')
 
def main():
  application = webapp.WSGIApplication([('/bug', BugReportHandler)],
                                       debug=False)
  util.run_wsgi_app(application)
 
 
if __name__ == '__main__':
  main()
このエントリをはてなブックマークに登録 Deliciousにブックマーク
関連のありそうなエントリ

Comments:6

benishouga 10-02-16 (火) 1:14

本記事を参考に開発者皆で使えるバグのPOST先を作ってみました。
まだ試験運用ですが、アグレッシブな方おりましたら、是非ご利用くださいませ。
http://aexceptions.appspot.com/

そして改めて、良記事を書いてくださった
adamrockerさんに感謝です!

Yutaka.Nakadouzono 10-05-06 (木) 0:16

すばらしい情報です。
この情報のおかげでほとんどのソフト開発効率が抜群に
伸びると思います。すばらしい貢献です。
大変ありがとうございます。

通りすがり 10-08-06 (金) 17:24

「バグレポート google app engine」でぐぐってたらここが一番先頭に出ました。(本当は、bugzillaを間借りさせてくれる
ところがないかなあと思ったんです。。)

たまたま、私もAndroidの開発をしていますが、これはぜひ
導入したいと思います。ありがとうございました。

とおりすがり 10-08-11 (水) 22:54

Context context = getApplicationContext();
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler(context));

上のコードですが、以下のURLのようなエラーが出ています。
thisを渡したほうが良いのではないかと思います。

http://tech.shantanugoel.com/2010/07/08/badtokenexception-android-dialog-getapplicationcontext.html

sarapapa 10-09-04 (土) 20:48

大変有用な情報ありがとうございます。
例外キャッチとGAE連携するあたり、ぜひ活用したいと思います。

ところで、saveState()メソッドですが、以下のようにシンプルにできそうです。

private void saveState(Throwable e) throws FileNotFoundException {
File file = BUG_REPORT_FILE;
PrintWriter pw = null;
pw = new PrintWriter(new FileOutputStream(file));
e.printStackTrace(pw);
pw.close();
}

あと、showBugReportDialogIfExist()メソッドですが、上の投稿の現象が発生したため、引数にActivityを渡すように変更したらうまくいきました。

public static final void showBugReportDialogIfExist(Context context) {
File file = BUG_REPORT_FILE;
if (file != null & file.exists()) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);

ボビン 11-02-02 (水) 10:41

有益な情報ありがとうございます。
ソースのzipを解凍し、10/06/29の修正分を反映させて実装してみました。
結果、ハンドラーにてエラーを捕らえることができましたが、その後アプリの応答がなくなります。スマートフォンの右端の戻るボタンも受付ません。制御を戻すには何かが抜けているのでしょうか?

同じような現象は出ませんか?

Comment Form
Remember personal info

*
To prove that you're not a bot, enter this code
Anti-Spam Image

Trackbacks:6

Trackback URL for this entry
http://www.adamrocker.com/blog/288/bug-report-system-for-android.html/trackback/
Listed below are links to weblogs that reference
Androidアプリのバグ報告システムを考える from throw Life
pingback from Twitter Trackbacks for throw Life - Androidアプリのバグ報告システムを考える [adamrocker.com] on Topsy.com 10-01-23 (土) 10:57

[…] Topsy Retweet Button var topsy_style = “small”; var topsy_order = “count,retweet,badge”; var topsy_url = “http://www.adamrocker.com/blog/288/bug-report-system-for-android.html”; Add Topsy Retweet Button to your Blog or Web Site. WordPress  Web Sites […]

pingback from links for 2010-01-24 « 個人的な雑記 10-01-25 (月) 7:03

[…] throw Life – Androidアプリのバグ報告システムを考える (tags: android) […]

pingback from Android端末のシェアやはりXperia強し!がんばれ « BPS株式会社 開発ブログ Beyond Perspective Solutions LTD. 10-07-14 (水) 21:52

[…] バグ報告システムとは何ぞやという方はこちらの記事をどうぞ。 […]

pingback from ACRAを導入しました | DailyTimer.net Blog 11-02-27 (日) 6:30

[…] throw Life – Androidアプリのバグ報告システムを考える […]

pingback from アプリ異常終了時のエラーを開発者に送信する | GE Android Blog 11-04-16 (土) 11:04

[…] アプリの強制終了が発生した際にそのときのエラー情報を送信する処理を考えます。 throw Life – Androidアプリのバグ報告システムを考えるが元ネタです。 ここで確認しているのは次の点です。 ・catchされない例外を捕捉する ・捕捉した例外を処理する ・例外情報をテキストファイルとしてSDカードに出力する ・次回起動時にダイアログで不具合情報の送信確認を行う ・GAEで不具合情報をメールで通知する ●catchされない例外を捕捉する まずはActivityにcatchされない例外の対応を記述します。 onCreateメソッドなどの早い段階で Thread.UncaughtExceptionHandlerを設定してやります。 […]

pingback from Android端末のシェアやはりXperia強し!がんばれ | TechRacho 11-05-19 (木) 15:47

[…] バグ報告システムとは何ぞやという方はこちらの記事をどうぞ。 […]

Home > android | GAE > Androidアプリのバグ報告システムを考える

Twitter
Search
Feeds
Meta

Return to page top