お知らせ

スパムコメントの頻度が高くなったので、一時的にコメントを閉鎖しております

まぁスパム以外のコメントはほとんどないので、困る人も居ないかと
どうしても連絡が必要な方がいましたら、GooglePlayの拙作アプリのページから「デベロッパーにメールを送信」でメールを送って下さい

 

ラベルの設定と検索

マーケットのコメントにて、ラベル検索ができない報告を受けた
しかし、自分の端末では正常に動作してるので、報告者がどんな状況になってるのか把握できない状態

ちょっとラベルの設定とか検索とかは使い方が紛らわしいと自分でも思う所があったので、マニュアルページに補足ページを追加してみた
http://android.undo.jp/wp/?page_id=316

最も紛らわしいのは、ラベル設定画面にある検索ボタンかな、とか思う
この画面の検索ボタンは、リストを絞り込み表示するためのボタンで、ラベルが大量に増えた際に目的のラベルを手早く探すためにつけた機能なんだけど、書籍検索画面からアクセスすると、このボタンで書籍が検索できるかのようにも見える
ちょっと表現をかえるなりした方が良いかなぁ

ちなみに、技術的にはラベル管理用テーブルにラベルを登録し、書籍IDとラベルIDのペアを登録するマッピング用テーブルがあって、検索時はマッピングテーブルに対してID指定で検索を行っている
書籍名での検索とかだと、特殊な文字とか文字コード違いで、意図しない検索結果になる可能性も考えられるけど、IDは数値なのでそうした曖昧さは無いかと思う
そうなると、手持ちの環境で動作してる機能が他の環境で動かない場合
・操作を間違ってる
・DBが壊れてる
・端末固有の問題
くらいが考えられるかな?
で、端末固有だと、その端末を持ってないとどうにもできないけど、ラベル検索だけピンポイントで動かない、てのも考えにくい

次の更新で、しおり検索も付けようかと思ってたのだけど、技術的には同じような仕組みになるので、ラベル検索に問題があるなら潰しておきたいのだけど……
報告あってから間が開いてしまったので、もう見てないかもしれないなぁ

絶賛放置中

微妙に仕事が忙しく、最近アプリ開発は放置ぎみとなっております
いくつかクラッシュレポートも来てるんですが、外部ライブラリ上で発生してるものだったりで修正が難しいため、どうしたものかと思案中
GW開けくらいから、また時間作れると思うので、そしたら頑張ろう(予定)

で、放置してみると、アップデートがないせいで順位が落ちたりで検索にかかりにくくなるのか、以前に比べてインストール数の伸びが悪い
他のアプリでもそうだけど、やはりアップデート直後は普段より伸びが良くなる
インストール数を稼ぎたいなら、地道にアップデートを繰り返すのが重要だろうか
まぁ俺はプログラムの勉強のネタとして出来たものを公開してるだけなので、あまり増えてもサポートの手間ばかり増えて面倒なので、現状くらいがちょうど良いかなぁ、なんて思ったりもするけど

んで。少し話しが変わって

お手本としてるPerfect Viewerが、いつの間にか自動着色を取り入れていた
ウチのアプリを見て組み込んだのかしら?とか思うのは自惚れですかね
着色のアルゴリズム的に同じものを使ってるように見えるけど、やはりGPLの表記が見当たらないのが少し気になる所ではあるが、定番化してても新しい物を見つけては取り入れて行く姿勢はすばらしい

まぁマーケット縛りはあるけどGoogle本家がビューワー出してる現状、競合ソフトとしては差別化のために色々やらんといけないんだろうなぁ
てな訳で、いつになるか・本当にやるかどうかは分からんけど、現状で予定してる追加機能をメモ

  • バーコード読み取り
    バーコード情報を読み取るのはライブラリでできるので、書籍の背表紙画像等にあるバーコードからISBNを読み取って、AmazonあたりのAPIで検索、書籍情報を取得してDBに登録
    検索のためにインターネット権限が必要になるのがネックで保留中
  • 各種フォーマット対応
    ePub、青空文庫あたりには対応したいなぁ
    自分で仕様読むのは面倒なので、良いライブラリがあれば、て所だけど
  • HTMLビューワーの公開
    直接アプリには関係ない事だけど
    2.3系ではJavaScriptでマルチタッチを取得出来ないので、現状ではタッチイベントはJavaで取得してJavaScriptに渡してる
    少し操作系に変更が必要だけど、タッチイベントもJavaScriptで書いてしまえば、PC含めHTML5対応なブラウザがあれば動くものになるので、そのバージョンを作って公開したい
    主要処理はJQueryを使ってるので、JQueryプラグイン化までできたら、きっとどこかの誰かの役にたつのでは無いかとか思ったり
  • Air版の作成
    これまた機能とか関係なく、個人的な興味本位で
    ちょっと最近弄る機会が出来たので、Airでも同じものを作ってみたい
    Javaより大きい画像が扱えるようなら、本気で現行バージョンから差し替えても良いかなぁ、とか

なにやら高評価?

PDF2Imageが、思いのほか評価が高いです
有効/合計インストール数も本日の値で516/886と、定着率で50%を軽く超えてて、この手のツール系としては及第点と言えるのでは、と喜んでおります

しかし、ひとこと言わせて欲しい
コメントで、寄付したい、なんて方もいらっしゃいますが、このアプリはそんな大層なものではございません!
アプリの中核となるMuPDFはオープンソースで公開されているもので、アプリでは画面に表示するかわりにレンダリング結果をファイルに書き出してるだけのものです
もしもアプリに寄付したいと言うのなら、MuPDFのプロジェクトに寄付して下さい

そもそも、自炊リーダーでなんとかPDF対応させるためにでっちあげただけのアプリでして
一体どんな用途で使われてるんでしょうかね?
自炊でPDF出力してる人ならPCを持ってるハズで、PCなら同様のツールでより高機能・高速で処理可能なハズ
だいたい、スマホなら普通にPDFが見れるだろうし、わざわざスマホ上で変換作業する用途が思いつかない……
まぁPDFの中の一部だけ使いたい、て事はあるんだけど、それをやるには使いにくいUIだと思うんだよね

て感じで思いつき
プレビュー中のページを1枚だけ直接出力する機能があったら、需要あるかね?
自炊リーダーの方では、現在のスキルでできるネタが尽きてきてるので、別の方向で何か更新をしたいと思ってるんだが

新ビューワー公開

今日は自宅にNexus7置いてきたので、公開は帰宅後Nexus7でのテストが終わってから、になるけれど

機能的には、既に他の記事で書いた通り、特別新しい機能はなし
単にJava(Activity&ImageView)からHTML&JavaScript(WebView)に移植しただけ
しかしメモリ管理がWebViewまかせになったため、これまでリサンプリングしないと読み込めなかったサイズの画像も原寸のまま読み込めるようになった
ストアのレビューに書かれている、メモリ不足やにじみ、に関する問題もこのビュワーであれば解決する、ハズ

懸念事項としては、WebViewがどの程度の速度になるか、という点
特に古い端末で、動作が遅くて使い物にならん、とかならなければ良いんだけど……
まぁWebViewが遅かったのは1.6とかの頃までで、今は随分改善されてまともになってるとは思うのだけど
手持ちの端末でしかテストできない訳で、こればかりはユーザーの反応を待つしかない

そんな訳で、現状では念のため、既存のビューワーも残して設定から切り替えて使えるようにしてあります
(メイン画面のメニュー→全体設定→ビューワーのタイプ)
将来的には既存のビューワーは廃止して、Native版を組み込みたいと思っているけど、これまた前言通り基礎勉強からやってる段階なので、いつになる事やら

ターゲット変更

自炊リーダーのマーケットコメントにて、メモリ不足で困ってる報告があった
こちらも色々と工夫はしてるのだけど、Dalvik使ってる以上は限界がある
本体にどれだけメモリが余っていても、アプリが使えるメモリは概ね24MB程
しかし、例えばフルHD画像をそのままメモリに展開しようとすると、それだけで8MBくらい必要
自炊用の画像なんか、ドキュメントスキャナで取り込むだろうけど、適当に300dpiくらいで読み込んでも、フルHDサイズなんて軽く超える
自分の使ってる設定でも、B6判サイズのコミックスで1500前後x2300前後のサイズなので、フルHDの1.5倍は行ってる事になる訳だ
アプリそのもののメモリも必要なので、それだけで一度に2枚も読み込めない

そこで、問題の先送りではあるけれど、largeHeap設定を有効にしてみた
largeHeapは使用できるメモリを増やす設定で、最大で128MBくらいまで対応してるらしい
が、この設定はAndroid3系以降でのみ有効なので、アプリのサポート対象である2.3系では使えないorz
まぁそれでも対応する端末であれば効果はあるハズだ

しかし、この設定を追加するだけで、ちょっとしたトラブルに遭遇した
ターゲットSDKバージョンを10から12に変更しただけなのに、突然NoSuchMethodErrorが発生して強制終了するように
調べてみると、どうやら3系から追加されたメソッドと利用してる抽象親クラスのメソッドが衝突したためのようだ
具体的には、ドラッグ&ドロップで入れ替えれるリストを実装するために、ListViewのかわりにAdsListViewを使っていたのだけど、3系からAdsListViewにsetAdapter()メソッドが追加されたせい
今まではAdsListView#setAdapter()がなかったのでsetAdapter()を呼べばListView#setAdapter()が呼ばれていたが、3系からメソッドが追加されたために呼び先が勝手にAdsListView#setAdapter()にされてしまった、というのが原因

SDKのバージョンアップで抽象親クラスで問題起こすようなバージョンアップはやめてくれ……
検索して、同じトラブルの報告と解決策が出て来たから良かったようなものの、そうでなければ、俺くらいのスキルじゃ原因が分からんまま途方に暮れる所だよ!
ちなみに、解決策は、該当するsetAdapter()の利用個所でクラスをListViewに明示的にキャストしてやるだけ
自分の場合、同じ処理をGridViewからも使ってたりしたので、ListViewとGridViewの振り分けとかも必要になって、色々と面倒だった

とりあえずこれで、3系以上ではメモリ問題は緩和されるハズ
……なんだけど、現在WebView版ビューワーを組み込み中なので、公開は少し先になります
困ってる方には申し訳ないですが、今しばらくお待ち下さい

プラグイン類の更新予定

ストアのコメントで要望を受けたので、自動着色とPDF2Imageで、出力の際に新規フォルダを作れるようにする予定

まぁやっぱ必要だよね、とはリリース前から思ってたのだけど、フォルダを作れるのに削除できないのは片手落ちな気がして実装しておかなかった機能なのよね
(間違った名前で作っちゃった時とか消せないと困るし)
かといって、フォルダの削除やリネームをつけると、微妙にやっかいなのよね
それこそ、間違ったフォルダを削除しちゃったりした時なんかに、責任が降りかかる恐れが……
もちろん、そんなのは利用者責任だけども、世の中には色々な人がいるからねー

てな訳で、フォルダを作るだけの機能を追加する予定
出力先選択時に、直接「フォルダに書き出す」ボタンと、「サブフォルダを作って書き出す」ボタンをつけて、サブフォルダを作る場合はフォルダ名入力ダイアログを出す感じで
実際にフォルダが作られるのは書き出し処理を実行する時に

実装自体はすぐにできるのだけど、アプリを公開するのに必要な認証キーが会社PCに入れてあって自宅環境になかったりするので、バージョンアップは週明けまでお待ちください

新ビューワー実験中

AndroidのJavaアプリでは、Dalvikが使えるメモリが小さくて、高解像度な画像をそのまま読み込む事ができず、リサンプリングで荒くしたものしか表示できない
スマホの画面サイズくらいなら多少荒くしても目立たないのだけど、拡大するとどうしても荒さが目立つ
そうでなくとも、自動着色やらを適用するとエフェクト処理のために使用可能なメモリが半分になって、さらに荒くなるし

これを改善するため、現在NDKの勉強中なんだけど、以前も言ったようにC系統の知識に乏しいので、アプリに組み込めるようになるまで相当時間がかかりそうな気配
NDK関連の参考書やサイトを見ても、既にCの知識がある人がAndroidでアプリを組むための内容ばかりで、逆にAndroidを題材にCを学ぼう、的な文献がほとんどないんだよねぇ
なので、まずは別途Cの勉強をした上で、改めてNDKに、という遠回りを強いられている……
まぁ勉強のネタとしてアプリを作ってる訳で、遠回り自体は個人的に問題ないんだが、既にリリース済みのアプリなので、あまりバージョンアップの間隔が開くのもよろしくない

しかし最近、関連するけど別件でフォーラムに質問を投げた時、思いがけない所からヒントを得た
アプリ内でWebページを表示する標準機能としてWebViewというのがあるのだけど、これは別にインターネット上のページを見るだけでなく、アプリ内に用意したローカル上のHTMLファイルを表示する事もできる
で、この機能を使ってimgタグのソースにSDカード上の画像ファイルを渡してやったら、Javaアプリではメモリ不足で落ちるような画像でもあっさり表示する事ができた!

既存の処理をJavaScriptに移植する必要はあるけど、今からCを覚えるより楽だし早い
とりあえず拡大縮小とドラッグでのスクロールまで組んでみたけど、手持ちの端末では処理速度的な問題は今の所感じられないレベル
JavaScriptなら、画像ギャラリー系の各種ライブラリも色々あるし、表示に関しては色々手抜きした上で機能強化もできるかもしれない
問題点はタッチ操作系とマウス操作系の互換だけど、タッチイベントはJavaで処理してから表示系に引き渡せば行ける感じ

まぁアプリ以上に端末ごとの互換性問題とか出そうだし、ゴテゴテ機能を追加していけば処理速度に問題も出てくるかもしれないけど
なんとなく希望が見えて来た感じだ

デバッグ例

エラーレポートが届いたので、バグ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時間で千円弱もの料金は出せないが、仕事でやってたり趣味でも採算取れるアプリを出してる人には便利なんじゃなかろうか?

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

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