デバッグ例

エラーレポートが届いたので、バグFIX版Ver1.3bを公開

せっかくなので、エラー時にどんなレポートが届いて、そこからどのようにデバッグしたかを書いてみようかと思う
掲示板とか見てると、ログの見方も知らずに開発に挑戦してる初心者の方も多いようで、でもログの見方とか解説してるようなサイトもあまりないし、具体例を書いておけば誰かの役に立つかも知れない、とか思って

ユーザーがエラーレポートを送信すると、デベロッパーコンソール(GooglePlayに公開するための開発者向けサイト)には以下のようなレポートが届く

android.database.sqlite.SQLiteConstraintException: error code 19: constraint failed
at android.database.sqlite.SQLiteStatement.native_executeInsert(Native Method)
at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:113)
at jp.undo.android.jisui_reader.BookshelfDBAdapter.insertBookData(BookshelfDBAdapter.java:269)
at jp.undo.android.jisui_reader.FilerActivity.searchPicFiles(FilerActivity.java:936)
at jp.undo.android.jisui_reader.FilerActivity.searchPicFiles(FilerActivity.java:902)
at jp.undo.android.jisui_reader.FilerActivity.searchPicFiles(FilerActivity.java:902)
at jp.undo.android.jisui_reader.FilerActivity.searchPicFiles(FilerActivity.java:902)
at jp.undo.android.jisui_reader.FilerActivity$3.run(FilerActivity.java:787)
at java.lang.Thread.run(Thread.java:856)

開発経験があれば分かるだろうけど、これは例外が起きた時にLogCatに出力される内容と同じものだ

最初の1行を見ると、今回のエラーは「SQLiteConstraintException」が発生した、て事らしいので、ちょっと検索してみる
要約するとデータベース操作において制約に引っかかった場合の例外らしい

次に、どの制約に引っかかったのか調べるため、エラーの発生場所を調査
レポートは、エラー発生個所から順に、どこから呼ばれたか?てのを追跡してくれている
この時に注目するのは、自分で書いたソースが最初に登場する行だ
自分で書いたもの意外の箇所は、ネイティブ(OS等が管理してる場所)なので弄りようがない
なので、エラーの原因はネイティブ処理の「呼び方が悪かった」訳で、そこを修正する必要がある
今回のレポートを辿って行くと、4行目に自分のアプリのパッケージ名(今回の場合は自炊リーダーの「jp.undo.android.jisui_reader」という名前)が見つかる
エラーの発生個所は、BookshelfDBAdapterクラスのinsertBookDataメソッド内で、具体的にはBookshelfDBAdapter.javaというフィアルの269行目、という事になる

該当箇所を見てみると、書籍情報を管理するテーブルに書籍のデータをINSERTする処理を行っていた
つまり、発行したINSERT文が、このテーブルに設定した制約にひっかかった、てのが今回のエラーの原因て事になる

で、テーブルに設定されてる制約を調べると、「PRIMARY KEY」と「UNIQUE」が設定されているカラムがあった
PRIMARY KEYはレコードを特定するためのIDとして使うためのもので、AUTOINCREMENTも設定されててINSERT時にも値を省略して、自動で割り振られるようにしているので、こちらが引っかかるとは思えない
UNIQUEの方は書籍のファイル(フォルダ)のパスを格納するためのカラムで、こちらなら操作次第で登録済みの値がやってくる可能性がある

しかし、この処理を呼び出す箇所を一通り調べてみたが、必ず事前にINSERTに渡す予定のパスで検索をかけて、登録済みかどうかのチェックが行われていて、登録済みのパスでINSERT処理が呼ばれる事がないようになっていた

普通に考えると、本来あり得ない動作をされている訳で、具体的なINSERT文とその時のデータベースが無いと、これ以上の原因調査はできない
調べた中で可能性があるとすれば、検索時とINSERT時で使用してる文字列のエスケープ処理の違い
インジェクション対策のため、外部から渡された値でSQL文を組み立てる時は文字列に対するエスケープ処理を行うのだけど、検索時は「DatabaseUtils.sqlEscapeString」を使い、INSERT時は「SQLiteStatement.bindString」を使っていた
もし、この二つのメソッドに同じ文字列を与えても違う結果を返すケースがあり得るなら、この現象も納得できるけど……

これ以上の調査は、ネイティブ処理のソースを読んでいかないと出来ないので、今回はここで追跡を断念
次善策として、今回の修正ではINSERT処理で「SQLiteConstraintException」例外をキャッチするようにして、重複登録を回避するようにしておいた
ネイティブ側にバグがあるとは思えず(可能性はゼロではないが、自分の書いたソースにバグがある方が遥かに可能性が高いと思われ)、何か穴がないか引き続き調査は行う予定

……オチ的に、デバッグの具体例として出すには不適切な例だったかも知れないが、まぁ流れ的なものは分かってもらえたかと

テスト環境

なんか知り合いから教えてもらった
Remote TestKitとかいうサービス

リモートで実機をレンタルして、リモートで操作できるらしい
俺は趣味でやってるので、1時間で千円弱もの料金は出せないが、仕事でやってたり趣味でも採算取れるアプリを出してる人には便利なんじゃなかろうか?

ま、機種依存が激しく実機でテストしたい機能に限って操作できない感じではあるので、微妙な気がするけど
特定端末でクリティカルな問題の問い合わせとかに対応するためになら良さそうだ

どうせなら、リモートでなく、店舗内で良いから実機を直接レンタルできるサービスってないかね?
ネカフェみたいな業態で、店内にある端末を使えるような
まぁ多様な端末弄りたいのなんて開発者の類いしかいないから、企業向けなサービスしか出てこないかな……

色々対応

自炊リーダーVer1.3を公開
今回は色々やった気がする

  • SD入れ替え支援
    まず、アプリ終了時、データベースのバックアップをSD上に作成するようにしました
    アプリ起動時、データベースとバックアップのバージョンを比較して、差異があればSDが変更されたと判断
    SD変更時はバックアップから復元するか初期化するか選択できるようにしました
    新しいSDにした時、データベースを初期化して、そのSD用にデータベースを登録・バックアップしておけば、次回入れ替え時は簡単にデータベースを復元できます
    ※作業フォルダを全てのSDで同じパスにしておくと、よりスムーズです
  • 検索条件の保存機能追加
    検索画面でメニューを表示すると、検索条件のセーブとロードのメニューが出ます
    何度も同じような検索をする場合、入力した内容を保存しておけば、次回からロードするだけで入力を省略できます
    保存時は名前を付けておけるので、何を検索するか分かるように名前をつけておくと良いでしょう
  • 一括操作を追加
    本棚画面でメニューを表示すると、各種一括操作を行う事ができます
    一括操作は、その時本棚画面に表示されている書籍全てに対して操作ができます
    主に、検索した結果に対して一括で指定した本棚に登録等を行う事を想定しています
  • ファイル選択画面でスクロール位置を記憶
    今まで、ファイル選択画面では、フォルダを移動すると常に先頭から表示されていました
    同じ階層を行ったり来たりしたい場合、毎回スクロールするのが面倒だったので、スクロール位置を記憶するようにしました

まぁ色々と使う上での細かい補助機能、てな感じでしょうかね
もちろん、この他にバグ修正なんかもしたりしてますが

 

エラーレポート

マーケットのレビューに、初コメントを頂いたYO!

「使えない ファイル選択したらいきなりアプリが落ちます。まだ一度も使えてません。」

orz
申し訳ない
せっかく報告を頂いたので対処したい所なんですが、エラーレポートが届いてないので、原因はおろか現象が分からない状況
本名登録は私個人のポリシーに反するのでGoogle+の利用規約に同意できず、コメント投稿者本人様と直接コンタクトできない状況なので、ここを見てるとは思えないけどこちらで報告をさせてもらいます
(デベロッパーコンソールのコメント返信機能はいつ実装されるんだろう?)

アプリでエラーが起きた時、「レポートを報告する」みたいなダイアログは表示されないだろうか?
ここで報告を送ってもらわない限り、開発側ではエラーの内容について把握できません
(エラーが起きてる事さえ分からない)
このレポートには、どのプログラムのどの場所でどんなエラーが出たか、という情報しか含まれません
ユーザー側の情報は機種名しか分からないので、プライバシー問題とか気にせず、出来る限りレポートを送信してもらえると、開発側としても助かります
ついでに、どの画面でどんな操作をした時、どんな現象が発生したのか、という報告を開発者に伝えてもらえると、修正が迅速に行われると思います

 

で、前述の通り、今回のコメントに該当しそうなレポートが届いていないので、コメントから状況を推測してるんですが、情報が少なすぎで対処が難しい
・ファイル選択したら
これはファイル選択画面上で、ファイルをタップしたら、だろうか?
フォルダなら平気なのか?特定のファイルだけでなるのか?
それともメイン画面の「ファイル選択」ボタンを押したら、だろうか?
「まだ一度も使えてない」との事なので、こっちの可能性が高いかな
・いきなりアプリが落ち
このとき、何かダイアログとか出てないだろうか?
「予期せず終了しました」みたいなダイアログが出ていたら、単純にアプリ側のバグなので、どの画面で何をどうしたら、さえ伝われば修正可能と思われます
ダイアログも出ず、ただアプリが終了(ホーム画面に戻ったとか)という場合だと、他の常駐サービスとの相性等もあって、アプリ側より環境依存の可能性が高くなります
(タスクキラーに引っかかったり、ウィルス対策ソフトにブロックされたり)

この件のコメント投稿者様に限らず、エラーが発生したら可能な限り詳細な情報をレポートしてあげて下さい
普通のプログラマーならエラーを放置なんてしたくないハズなので、修正に必要な手掛かりさえあれば、必ず対処してくれると思います
(開発に飽きて放置したままのアプリだと無理かもしれませんが)
自分も含め、Androidのアプリ開発者は個人でやってる人も多く、テスト端末を用意するのにも限界があります
せっかくの便利なアプリがエラーで動かないのは勿体ないと思うので、積極的にご協力いただけると幸いです

……とりあえず、コメントくれた方がNexus7だったので、テスト用に4系タブレットも欲しかったのでNexus7を購入しといた
ブツが届いたら色々と試してみるんで、しばらくお待ち下さい

ラベル機能追加

自炊リーダーVer1.2公開
今回の機能追加は、ラベリングの対応と、それに伴う検索機能の強化です

おそらくAndroid使いの人なら大抵Gmailとか使った事あるだろうけど、Gmailにあるラベル機能みたいなものを追加しました
まぁこのアプリの場合、本棚が既にラベル機能の変形版みたいな作りになってるんだけどね

検索機能の強化は、今までの定型検索(タイトル別とか)を廃止して、入力欄を設けて自由に検索できるようにしました
既存の定型検索は、検索画面で表示グループを指定する事で同じ事ができます

これでようやく、書籍情報をデータベース保存にしたのが活きて来たかな?
入力する手間はあるけど、一度設定してしまえば、比較的自由に検索できる
DB化して必要な時に検索して取り出す、というスタイルは、あまり競合アプリにはない特徴になったかな、とか思う

ついでに、書籍情報に評価の項目を追加しました
評価は★の数で設定するレーティングです
個人利用で評価の項目はあまり意味が無い(評価は自分の頭の中にあるから)ようにも思うけど、このアプリは自分の勉強用でもあるので、色んな標準ウィジェットを使ってみたかったからつけてみましたw
まぁ文字通り評価として使わず、★の数でカテゴライズするという使い方もできる訳で
(例えば、★1は娯楽物、★2は技術書、とかいった使い方)

これからも、思いついたら登録できる項目は増やしていくかと思うけど、とりあえず次回は入力支援を充実させてみようかと画策中
例えば、ラベル管理画面で既に導入してあるけど、入力欄に対するサジェスト機能を他の項目にも、とか
検索結果で表示された書籍一覧をそのまま全て本棚に登録、とか
本棚内の書籍に対してラベルの一括登録、とか
検索結果と本棚・ラベルの一括登録を組み合わせれば、本棚管理も少しは楽になるんじゃないかなぁと予想しております
またUIから考えないといけないので、機能の割りには公開まで時間がかかるかもしれませんが