2chのVIP板で連載されてた小説を、Android端末で読みたくなったんですよ。
71667行、1059640文字。UTF-8のテキストで、2.77MBあります。2ch特有の記事ヘッダなどがあるのでサイズは小説一本分よりやや大きめですが、非現実的なサイズではありませんね。
DropBoxで端末にコピーするまでは問題なし。
何のアプリで閲覧するか迷ったので、色々試してみました。
何のアプリで閲覧するか迷ったので、色々試してみた
OI Notepad http://www.openintents.org/en/notepad
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストで強制終了。
AmbleLink Notepad http://www.amblelink.com/al_products.html
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストで強制終了。
TxtPad Lite http://www.patillosoftware.com/
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストで強制終了。ANRの傾向もあるようだ。
pText 日本語版 http://miduhima.blogspot.com/
VIEW(text/plain)インテントを受け取れる。2.77MのUTF-8のテキストを開けるが化けていた。文字コードを指定して開き直すことができる。スクロールにノブが欲しい。設定画面から戻る際にえらく待たされて、ボタンを連打してアプリを閉じてしまった。再度ファイルを開こうとしたら強制終了。あと、ファイルを閉じようとしたら強制終了。
Main Viewer NG http://chuacw.ath.cx/blogs/chuacw/default.aspx
VIEW(text/plain)インテントを受け取れる。2.77MのUTF-8のテキストを開ける。文字色,背景色の設定が欲しい。スクロールにノブが欲しい。
Text Edit http://textedit.paulmach.com/
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストで強制終了。
aPad++ フリー http://im1984.org/
VIEW(text/plain)インテントを受け取れない。ファイルチューザのソート順がデタラメ。2.77MBのファイルを開けたが、スクロールの応答性がかなり悪い。スクロールにノブが欲しい。エディタとしては、タップでカーソル移動ができないのが困る。
aPad++ Professional http://im1984.org/
VIEW(text/plain)インテントを受け取れない。2.7MB程度のテキストだと、開くのが遅すぎて事実上使えない。
縦書きビューワ http://d.hatena.ne.jp/npn2sc1815j/
VIEW(text/plain)インテントを受け取れる。メモリ不足で怒られる。
gEditor http://satoshi.web5.jp/
VIEW(text/plain)インテントを受け取れない。ファイルチューザのソート順がデタラメ。2.7MB程度のテキストで強制終了。
A Text Viewer Pro http://www.kobesoft.info/
VIEW(text/plain)インテントを受け取れる。開くのが速いのはいい。2.7MB程度のテキストだとスクロールが厳しい。行の折り返し表示も欲しい。
メモ帳 http://jp.androlib.com/android.application.jp-aonosoft-android-notepad-FmnB.aspx
VIEW(text/plain)インテントを受け取れる。メモリ不足で怒られる。
青空読手 http://d.hatena.ne.jp/hyukix/
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストを開けたが、文字化けしていた。
(2010/8/22に改善されました。快適に閲覧できます。)
HTMLViewer (?)
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストを開けるが、とても時間がかかる。UTF-8のテキストを開いたのに文字化け。設定画面はないようだ。
QuickOffice
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストを読ませたら、1回目は強制終了した。2回目はロードにかなり時間がかかって諦めた。
ファイル エディター
VIEW(text/plain)インテントを受け取れる。2.77Mのテキストで大きすぎると怒られた。
感想
あれれれれ? ちょっと意外な結果になりました。
テキストを表示するだけだし、文字コードも今やデファクトなUTF-8だし、問題なさそうだと思ったのですが、意外なことに快適に閲覧するのは難しいようです。
作ってみる
3MB足らずのUTF-8のテキストを表示するのは、難しいんでしょうか?
どう考えてもそうは思えないので、実際にテキストビューアを作ってみました。5:12に開始して11:38にマーケットに登録したので、6時間半くらいかかりました。
http://juggler.jp/tateisu/android/
ListView を使って、1項目にテキスト1行を表示するという単純な実装です。ただしテキストは適当な粒度で一時ファイルに分割して、表示に必要な部分だけキャッシュしつつ読み込みます。
ListViewのクセ
android:fastScrollEnabled="true"にした際に android.widget.FastScroller#getSectionsFromIndexer が adapter を BaseAdapter にキャストするので、 スクロール用のつまみを使う場合はBaseAdapterより単純なAdapterを実装することは不可能です。
また、ListView自体は Integer.MAX_VALUE 個の項目を扱えるのですが FastScrollerはそうでもないようです。試してみたところ(Integer.MAX_VALUE>>9)個は大丈夫ですが、(Integer.MAX_VALUE>>8)個ではつまみがどこかに行ってしまいます。足りない場合は1項目あたりの行数を上げてやればなんとかなるわけですが、まあ何もしなくても4.1メガ行は表示できるので十分でしょう。
BaseAdapter#getItem(pos)の実装ではnullを返しても特に問題はありません。行ごとにユニークなオブジェクトを用意すると、それだけでメモリを結構浪費するので避けます。
テキストの保持
バックグラウンドスレッドで読みながら1000行ずつ一時ファイルに書き込みます。このとき、読み出し時に行単位でアクセスするのに必要な情報をあらかじめ書いておきます。行数、文字数、各行の開始位置(文字単位)、各行の文字数、1000行分のテキスト、という感じです。ロード時はこの構造をなるべくそのまま読んで、1000行分のテキストから1行分だけをString#substringで参照します。
一時ファイルを一つ書くたびにUIスレッドに通知をおこない、表示側のスクロール範囲を広げます。
UIスレッドからListAdapter経由で表示テキストの要求がでたら、ファイル単位のキャッシュ数個分をメモリ上で管理します。ListViewの性格上、同時に保持する必要があるキャッシュはせいぜい2-3個で十分です。
Intentフィルタ
アクションがVIEWで、DataTypeがtext/* のIntentを受け取れるようにフィルタを設定します。
未実装の機能
さすがに数時間だと文字コード認識や検索やブックマーク管理までは手が回りませんでした。一時ファイルの削除管理ももう少しなんとかしたいです…。