ラベル プログラミング の投稿を表示しています。 すべての投稿を表示
ラベル プログラミング の投稿を表示しています。 すべての投稿を表示

2012/07/25

Ubuntu 12.04 LTSでmrubyを試してみた

組み込み用途に特化した軽量版Rubyの実装として、mrubyがリリースされています。

これの使いどころを考えてみたのですが、Tech-Onのインタビュー記事では、一例としてC言語で記述が難しい処理をmrubyにオフロードするとありました。

個人的には静的なコードはC言語で書くだろうと考えていて、動的なランタイムの変更をmrubyでユーザーに開放するんだろうなと思っています。 具体的には設定ファイルの記述やプラグインをmrubyスクリプトで作成することになるでしょう。

この使い方だけならluaなんかでも良いんですが、操作可能な要素をクラス単位でまとめる事ができるのは、実際のところ名前空間があるかどうかの違いぐらいしかありませんが、実用上の使い勝手は表面的な違い以上のものがあると感じています。

まぁ、ここら辺はいまのところ信念の問題なのですが、今回はmrubyを使って設定要素をクラスにまとめる場合を想定して、ちょっとしたサンプルを作ってみる事にしました。

テストした環境とmrubyのバージョンは次のようになっています。 特定のmrubyのバージョンには依存していないと思いますが、Webで調べたコードの中の関数の引数の取り方が違うものもあったので、念のため書いておきます。

  • OS: Ubuntu 12.04 LTS x86_64版
  • コンパイラ: gcc 4.6.3
  • mruby git commit: 8b6f6faf1e3771c04a1e2a58b1bbc84fe7d5c1e2

ちなみに、commit:のハッシュを使って、このテストしたmrubyと同じソースコードをbranch名"20120723.083824"で入手するには次のようにします。

$ git clone https://github.com/mruby/mruby.git
$ git checkout -b 20120723.083824 8b6f6faf1e3771c04a1e2a58b1bbc84fe7d5c1e2

mrubyのバージョンや日付によってメソッドの呼び出しシグネチャが違う事がありますが、だいたいそのまま読めると思います。

参考資料

参考にしたのは、主に手探りでおぼえるmruby その1:クラスを定義する、メソッドを定義するで、RICOHの方がまとめた記事もありましたがコードがGPLだったりするので、考え方などを参考にするに留めています。

とりあえずmrubyについては使い方よりも、生み出された背景や、中間コンパイラとしての挙動について、mrbcコマンドの動きを抑えておくのがお勧めです。

実証実験に参加した各種企業が公開しているドキュメントが参考になるでしょう。

mrubyのサイズ

x86_64環境でライブラリをenv COMPILE_MODE=release makeで作成すると、libmruby.a, libmruby_core.aの各ファイルはそれぞれ790KB前後のサイズになります。(strip後は470KB前後)

ruby-1.9.3-p194のコードをビルドすると、librucy-static.aのサイズはstrip後も2.1MBほどになります。

ライブラリの全てのシンボルとリンクする分けでもないので、静的にリンクした実行ファイルのサイズはもっと小さくなるはずですですが、CRubyのライブラリサイズの大きさは少し大き過ぎるなぁと感じるところです。

mrubyに求める機能

既に説明していますが、アプリケーションが提供するクラスを主体として、そのオブジェクトを組み込みメソッドを組み合せて操作できるところがメリットだろうと思っています。

普通のアプリケーションでは設定ファイルに固定文字列しか書けないのが普通ですが、これはセキュアだとは思うものの、記述できる語彙が少な過ぎるとも感じています。

luaはちょっとローレベルに過ぎる印象があってオブジェクト的に構造体の中に値と操作用関数をまとめる事はできますが、その環境をセットアップするのは少し面倒な印象です。

今回のゴール

mrubyを通してユーザーは日時情報や外部ファイルに記述されている内容などの情報に応じて振舞いを変化させる事ができるようになるといいなぁというわけですが、サンプルなので今回の範囲は一組のsetter/getterをラップしてみようと思います。

具体的に何をするのか、図にしてみた

図にしてみると次のようなプログラムを作成してみる事になります。

C言語レベルではConfigクラスに相当する設定可能な要素を構造体で定義します。

アプリからは直接構造体を操作せず、操作用のsetter/getterメソッドをConfigControllerクラスとしてまとめています。mrubyからはWrapperクラスを通してConfigControllerファイルでまとめているメソッドにアクセスしています。

実際のコード

いろいろ大風呂敷を広げましたが、ここから先はしょぼいコードが並びます。

ただGoogleで検索にヒットしたページは、やはりmrubyにコードをオフロードする仕組みやmrbcを使って中間コードを実行する方法に特化していたりしたので、もうちょっと泥くさい使い方を考えてみました。

conf.h, conf.c

実際にはConfとConfControllerに相当する機能はconf.c, conf.hにまとめました。 でも何かするわけではなくて、デバッグモードかどうかを動的に切り替えられるというだけです。

内容にはまったく意味がないんですが、気にしないでください。

conf.hファイル

#ifndef YA_CONF_H
#define YA_CONF_H 1

#include <mruby.h>

struct _conf {
  int is_debug;
} conf;

int is_debug(void);
void set_is_debug(int d);

#endif

conf.cファイル

#include "conf.h"
int is_debug(void) {
  return conf.is_debug;
}
void set_is_debug(int d) {
  conf.is_debug = d;
}
wrapper.h, wrapper.c

wrapper.hファイル

#ifndef YA_WRAP_H
#define YA_WRAP_H 1

#include <mruby.h>

struct RClass* yamrb_class;

mrb_state* yamrb_init(void);
mrb_value yamrb_is_debug(mrb_state* mrb, mrb_value self);
mrb_value yamrb_is_debug_equal(mrb_state* mrb, mrb_value self);

#endif

wrapper.cファイル

#include "conf.h"
#include "wrapper.h"

#include <mruby.h>
#include <mruby/numeric.h>

mrb_state* yamrb_init() {
  mrb_state* mrb = mrb_open();
  yamrb_class = mrb_define_class(mrb, "YaConf", mrb->object_class);
  mrb_define_method(mrb, yamrb_class, "is_debug", yamrb_is_debug, ARGS_NONE());
  mrb_define_method(mrb, yamrb_class, "is_debug=", yamrb_is_debug_equal, ARGS_REQ(1));

  return mrb;
}

mrb_value yamrb_is_debug(mrb_state* mrb, mrb_value self) {
  mrb_value ret = mrb_fixnum_value(is_debug());
  return ret;
}

mrb_value yamrb_is_debug_equal(mrb_state* mrb, mrb_value self) {
  mrb_int arg_debug;
  int argc = mrb_get_args(mrb, "i", &arg_debug);
  if(argc == 1) {
    set_is_debug(arg_debug);
  }
  return self;
}
main.c
#include <stdio.h>
#include "conf.h"
#include "wrapper.h"
#include <mruby.h>
#include <mruby/proc.h>
#include <mruby/compile.h>

mrb_state *mrb;
int gen_code_num;
mrbc_context *mrbc_ctx;

void init() {
  mrb = yamrb_init();
  FILE *fp = fopen("main.rb","r");
  mrbc_ctx = mrbc_context_new(mrb);
  struct mrb_parser_state* st = mrb_parse_file(mrb, fp, mrbc_ctx);
  fclose(fp);
  gen_code_num = mrb_generate_code(mrb, st->tree);
  mrb_pool_close(st->pool);
}

int main(int argc, char** argv) {
  init();
  
  // first run
  printf("current is_debug: %d\n", is_debug());
  mrb_run(mrb, mrb_proc_new(mrb, mrb->irep[gen_code_num]), mrb_nil_value());

  printf("current is_debug: %d\n", is_debug());
  set_is_debug(1);
  printf("new is_debug: %d\n", is_debug());

  // second run
  mrb_run(mrb, mrb_proc_new(mrb, mrb->irep[gen_code_num]), mrb_nil_value());

  // close
  mrbc_context_free(mrb, mrbc_ctx);
  mrb_close(mrb);
}

main.rb

print "-- begin --\n"
y = YaConf.new()
p y.is_debug()
y.is_debug = 2
p y.is_debug()
print "----\n"

Makefileファイル

INC = ./src/include
LIB = ./src/lib

main: conf.o wrapper.o main.c
	gcc -std=gnu99 -I. -I$(INC) -o main main.c conf.o wrapper.o -L$(LIB) -lmruby -lm

conf.o:	conf.c conf.h
	gcc -std=gnu99 -I. -I$(INC) -c conf.c 

wrapper.o: wrapper.c wrapper.h
	gcc -std=gnu99 -I. -I$(INC) -c wrapper.c 

気になったこと

Rubyの拡張ライブラリの知識は、いろんな意味で役に立ちますが、README.EXT.jaに対応するまとまったドキュメントがないので、いろいろ混乱するかもしれません。

FIX2INTなどの型変換マクロがない

Rubyの拡張ライブラリでは標準的なC言語の型との変換はマクロで実現していましたが、 mruby.hでは静的な関数が準備されています。

  • static mrb_value mrb_fixnum_value(mrb_int)
  • static mrb_value mrb_float_value(mrb_float)
  • などなど

mrb_intは標準のint型とtypedefされているだけで、直接に代入できます。 mrb_floatはmrbconf.hで定義されていますが、手元では8byteで通常はdouble型に紐付くようです。 文字列はmruby/string.hに定義されているような、組み込みString型用の関数を使う事になります。

mruby.hに定義されていなければ、操作対象の型に応じてmruby/以下のヘッダーファイルを眺める事になります。

mrb_generate_code()が返すint型の番号を管理する方法が欲しい

これはmrubyが準備する話しではないのですが、今回は処理が単純なのでmrbも全体で共有するような作りにしました。少し複雑になってきても、必要な都度、必要なコードをmrb_run()で呼びますが、この場合には対象のコードをmrb_generate_code()の戻り値で指定する必要がでてきます。

この管理方法が、まぁ対象のアプリに依りますが、どうなるかなぁと思っているところです。

まとめ

rubyはいろいろ機能が増えすぎて特定のバージョンとコードを密接に管理する必要があるので、mrubyはシンプルに保って欲しいなぁと思っています。まぁコンパイル時のオプションで調整することもできる部分もありますが。

C言語でアプリを組む時に機能の一部をオフロードする目的だと内蔵クラスや機能が多い方が楽になるわけですが、アプリケーションのプラグイン的にユーザーに開放する場合には、むしろ機能や組み込みクラスはないぐらいの方がセキュアになります。

mrubyは現状ミニマムで、これからスタンダード、フルといった主にクラスが追加される形のバージョンが出てくる事になっています。 とはいえ、ミニマムしかない現状でアプリの拡張をmrubyで行なおうとすると物足りない印象はあるので、前倒しでミニマウなmrubyが拡張されるような可能もあるのかなぁと思っています。 クラスセットについては、決まっているようですけれど、環境としてはまだまだ機能の追加が続いていますしね。

どうなるか、まだよくわかりませんが、アプリのコアエンジンとして安定してくれるといいなぁと思います。

2012/07/11

Google Chrome拡張を{ "manifest_version": 2 }に対応させてみる

気がついたらGoogleから「マニフェストのバージョンが2になったから対応よろしくね」、という旨のメールが届いていた。 まぁ8月中旬から新規の登録受付を中止して、年内には更新の受け付けができなくなるスケジュールが引かれていたので、 使うだけのユーザーなら影響がでるのは来年以降ですね。

今回はmanifest.jsonファイルを変更して気になった点をまとめていきます。

ちなみに変更は動作確認が終り次第、gitoriousにあるコード(Japan Postal Code Search, Open PinnedTab Link, etc...)に反映させます。

参考にしたページ

Googleのガイドはとてもまとまっているので、あまり他のサイトを調べる必要性を感じないのですが、 さすがに今回はStackOverflowなどのサイトにお世話になりました。

とはいえ、まず抑えておかなければいけないのはGoogleのmanifest.jsonの"manifest_version"の説明ページです。

ここで、全体のスケジュールと全体の変更の概要が書かれています。 しかし、ここだけでは具体的にアプリケーションにどんな変更が必要か把握するのは難しかったです。

具体的な変更作業

とりあえず作成しているOpen PinnedTab LinkJapan Postal Code Searchの2つは、比較的簡単なアプリケーションです。

このアプリケーションを修正した際に必要な修正は以下のようになりました。 まだ稼働確認が終っていないので、追加で必要なものもありそうですが、とりあえずまとめます。

HTML中にonclick, onload属性は書けなくなった

scriptタグを使ったjQueryの$(function() {...});表記などは、そのまま使えますが、(x)HTML上のタグにあるonclick="..."やonload="..."の表記を埋め込む事ができません。

基本的なコードはGoogle ChromeガイドのContent Security Policy (CSP)ページに記載されています。

onloadについは書かれていませんが、onclickと同様にdocument.addEventListener('DOMContentLoaded', function () { ... };の...部分にonloadで行なっている関数呼び出しなどをコピーすれば動きます。

ここで問題になったのはJavaScriptのコード中で、明示的に文字列としてonclick属性を追加している場合でした。

変更前のコード:文字列としてonclick属性を追加している例

...
td4_span.setAttribute("onclick","showMapImg('" + center + "')");
...

変更後のコード:addEventListenerに書き換えた例

...
td4_span.addEventListener('click', function(){showMapImg(center);});
...

元々変更後のように組むべきだったとは思います。 とはいえ文字列で解決するのは単純で簡単だったので、使っている方もいそうです。

動的なアクションはすべからくaddEventListener関数で指定する事になると思われます。

background処理をhtmlからjsファイルへ移動

manifestのbackgroundで指定する対象がhtml("page":文字列)とjavascript("scripts":文字列配列)の2つが準備されています。

これまではhtmlで記述していたので、manifest.jsonを書き換えて、そのまま流用すれば良いと思っていたのですが、どういう分けか、うまく動きませんでした。

変更前:version 1のmanifest.jsonから抜粋

...
  "background_page": "background.html",
...

元々background.htmlは本文のない、scriptタグの中に処理が記述されているだけだったので、その中身をjsスクリプトファイルに分割して、manifest.jsonの表記を書き換えました。

変更後:version 2のmanifest.jsonから抜粋

...
  "background": { 
     "scripts": ["scripts/background.js"]
  },
...

jsファイルを指定した場合でも、内部では空のhtmlファイルが生成されて組み込まれているだけのようなので、今回の挙動はおかしいと思います。 おそらくhtmlファイルのまま移行できるように作られているはずなので、早晩このワークアラウンドは不要になるでしょう。

まとめ

いろいろ書こうと思ったのですが、うまく動かない処理を発見したりして別の記事にまとめる事にして、とりあえず今回はここまでで終りです。

今回取り上げなかったのですが、外部サイトからのJavaScriptファイルのロードなどもデフォルトではできなくなっています。 オプションページのためにjQueryのコードをCDNからダウンロードしたいような場合にも対応が必要そうです。

単純なmanifest.jsonの書き換えで対応できるアプリケーションは少ないのではないでしょうか。

とはいえ、変更内容を確認する限り、機能が制限されているものはないので、既存の"manifest_verison":1アプリは全て、ちゃんと書き換えれば動くはずです。

この「ちゃんと」という部分が曲者ですが、今回の経験からは、新ルールに矯正された事によってアプリケーションのコード全体は良くなっていると思います。

Chrome拡張のAPIについてはいろいろ疑問に思うところもあったので、これから始める方々には、面倒は増えるでしょうが、よりよいコードになっているとは思います。

これがどれくらい面倒かは…、なんともいえませんが、少なくとも既存の開発者は、どこに問題があるか調べるだけで面倒そうです。

2012/06/29

バージョンアップを続けるAndroidアプリでのDBスキーマの更新方法

さて、AndroidアプリケーションにはSQLite3への接続を管理するためのクラスとしてandroid.database.sqlite.SQLiteOpenHelperクラスがあります。

通常はこのクラスを継承して、子クラスを自分のアプリケーションで定義するわけですが、今回はスキーマをバージョンアップするためのonUpgradeメソッドの書き方についてです。

よくあるサンプルは直前のバージョンと自分のバージョンを比較して、バージョンアップ用のコードを定義していますが、アプリを使うユーザーが次のようなシナリオに遭遇する場合を想定しているでしょうか。

  • Version 1.0のアプリをダウンロードして使う
  • Version 2.0がリリースされるが、ユーザーは更新を行なわなかった
  • Version 3.0がリリースされ、ユーザーはアプリを更新した

アプリは毎回のバージョンアップで、ALTER TABLE ... ADD COLUMN ...を行なっているとします。

SQLiteOpenHelperクラスを紹介するWebサイトはいろいろあったのですが、こういう使い方を説明しているところはなかったのでまとめる事にしました。

まぁ良く考えれば分かる事ですが、いまのところの自分のベストプラクティスをメモしておきます。

解決するべき課題

前提として毎回ユーザーはアプリを更新しない、 Version 1.0からVersion 20.0へアップグレードしても、アプリケーションの内部データは一貫性を保ちたい、という要求があるとします。

これ自体は日本的というか、こういうのは諦めて、onUpgradeでDROP TABLE 〜 CREATE TABLEを行なってデータを初期化してしまうのも、まま、見ることです。

ここでは敢えて、この課題を解決する方法を目指します。

定石1:バージョン毎にスキーマやデータを変更するメソッドを定義する

DBのバージョンは1から始まりますが、この時のDB定義はonCreate(SQLiteDatabase db)メソッドの中でdbオブジェクトにSQLを発行して定義します。

バージョン2からは例えば、次のようなメソッドを用意してあげます。 これは直前のバージョン1からのアップグレードだけを前提にしています。

ExifPMで使用しているversion 2、3用メソッドの例

    private void updatedb2(SQLiteDatabase db) {
        Log.d(this.toString(), "upgrading db 1 to 2");
        StringBuilder sql = new StringBuilder();
        ...
        db.execSQL(sql.toString());
    }
    private void updatedb3(SQLiteDatabase db) {
        ...
    }

定石2:onCreate、onUpgradeメソッドに一度記述した内容は消してはいけない

もちろんこれは内容を変更しないという意味ではありません。

定石1で作成したバージョン毎のメソッドを追記する操作だけを許可するというルールです。

ExifPMで使用しているonUpgradeメソッドの全体


    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if(oldVersion < 2 && 2 <= newVersion) {
            updatedb2(db);
        }
        if(oldVersion < 3 && 3 <= newVersion) {
            updatedb3(db);
        }
    }

よくみる例はここで、if (oldVersion == 1 && newVersion == 2) {...}みたいにしていますが、(oldVersion, newVersion) = (1,3)の組み合せもあるわけで、まぁそれは破綻するわけです。

ちなみにonCreateメソッドの方は初期化時に1回しか呼ばれないためもっと簡単です。

    @Override
    public void onCreate(SQLiteDatabase db) {
         ...
         updatedb2(db);
         updatedb3(db);
    }

念のためにコンストラクタの書き方

SQLiteOpenHelperのサブクラスとしてSQLDBHelperというクラスを定義していて、この中で親クラスのコンストラクタを呼び出し、DBバージョンを指定しています。

コンスタクタの例


    public SQLDBHelper(Context context) {
        super(context, "appdb", null, 3);
    }

実際にはバージョン番号はアプリケーション全体で定数を管理しているクラスの中でfinal static intで定義されていますが、こんな感じでSQLDBHelperを使う側はDBバージョンを意識する必要がなくなっています。

さいごに

この方法でDBバージョン Nに対して、N-1との差分を記述していって、場合によってはデータメンテナンスも行ないます。

まぁ初期化した方が楽だったりもするんですが、ユーザーデータをできるだけ開発側の理由で、消したくないという思いもあるので、こういう方法を採用しています。

あんまりこういう事が検索に引っかからないのは、もっと良い方法があるからなのか、かなり気になっています。 なにか理由があるのかなぁ…。

リーダブルコードを読んでみて気になったこと

オライリー(O'Reilly)から出版されたリーダブルコードについてです。 ほとんどいいがかりみたいですが、読んで気になったところをメモしておきます。

これから文句みたいなことを書きますが、この本はいろいろな発見があって、Web専業といった特定の業務に特化していて、いろいろなシステムに触れる機会が少ないような方には特にお勧めです。

最近Androidアプリを作ってJavaばかり触っているので、特にこの観点から気になった点をまとめました。

「関数の戻り値にretvalを使うな」について

型のないいわゆるLLと呼ばれるようなスクリプト系言語を扱う場合には、tmp/retvalのような名称は使うべきではないと強く思います。

しかし型のある言語の場合には、メソッドの先頭で戻り値のオブジェクトを格納する変数を用意しておき、最後にreturn ret;とするのは悪い習慣だとは思いません。

問題なのはメソッドの戻り値になるオブジェクトを格納する変数を、メソッドの途中に宣言をする事です。 読み手にゴールを提示せずに書き始め、そのゴールを途中にそっと忍び込ませるような真似は、決してするべきではないと思います。

C言語のように全ての変数をメソッド/スコープの先頭で宣言するべきとは思いませんが、処理の全体に関連する変数、定数の宣言はメソッドの先頭で行ない、それが処理全体の主人公であるという見通しを読者に伝えるべきです。

  public String findUniqueNameFromIndex(int index) {
     String ret = "";
     ...
     return ret;
  }

すべてのメソッド定義でこのような形式に沿った記述をしていれば、その処理がやや冗長になっても途中でターゲットを見失わずに済みます。

処理の途中で使用する変数は、そのスコープにもっとも近い場所で宣言をすることで、読者にそのスコープに関連する事を強く印象づける事ができると考えています。

大切な事は、全ての記述を特定の形式に収める事で、読者の興味、関心を他に移さずにコードに集中させる事です。

Syntactic Sugarであるswitch文を排していない

読んだ限りではswitch文について言及している部分はありませんでした。 まぁこれは趣味な部分も含んでいるので、こうでなきゃいけないというコンセンサスは得られないと思います。

ただ、switch文は単一スコープの中に記述する形式になっているので、一度あるcase文の中で宣言した変数は、他の個所で使う事はできません。break文を忘れて、意図せず変数に代入が行なわれて気がつかずに特定の処理でバグが入るといった可能性があります。

一般にswitch文はif-else文で置き換えが可能で、スコープが明確に分かれているif-else文を使うのが個人的にはお勧めです。少なくともbreak文を抜かすような真似はif-else文ではできません。

まぁswitch文は便利ですし、組み込み系のプログラミングで8bitの入力を処理するのに255個のcase文があるコードを見た事もあるので、環境によって流儀はあると思いますが、あえてswitch文は止めようといっておきます。

まとめ

リーダブルコードは読者を意識したコーディングを行なうための、とても良いガイドになっていると思います。

昔から特定の言語を対象にした、「べき・べからず本」というのはありましたが、どうもTipsが命令的で柔軟性にやや欠けるような印象がありました。

リーダブルコードのテーマは昔から出版されている本と比較して、とても斬新というわけではありません。

しかし、押し付けようとせずに、プロ・アマ問わずに誰でも読める記述になっている事はお勧めのポイントとして、とてもとても高く評価しています。

この本は誰が読んでも参考になりますが、できれば10代で、この本を読んで、最初から読み手を意識したコーディングをする方が増える事を願っています。

最後に、深夜に書き始めたせいか、いつもどおり、そしていつもにも増して上から目線になってしまいました。 ごめんなさい。

おまけ:自分が書くコードはリーダブルか (小学生読書感想文調)

私は、ずっとこういう事をテーマにしているはずなのに、いまだに最初からリファクタリングが不要なコードを書く事ができていません。

これは小説家が何枚も原稿用紙を破るように、当たり前の事なんだと思ってきました。 でも、リーダブルコードを読んで、プロの人達はこんな事はしなくても経験を積めばリーダブルで高可用性なコードが最初から書けて、こんな素晴しい本まで書けるようになるんだと知りました。

私は以前に同じようなコードを書いた事がなければ、いろいろ設計らしき事をしてから作り始めても、一つのメソッドが100行くらいになってしまう事は、たまに起ります。

1つのスコープでそんなに長いコードを書いているので、tmpという変数名を使ってしまうと、後でtmp2のような変数名を使わなくてはいけなくなるので、結果として説明的な変数を使うようになります。

それから、処理の内容をできるだけ分割して、意味のある単位で外部のメソッドとして括り出して、その処理に責任を負うのに相応しいクラスに処理を移します。

そんな作業を何回も繰り返して、やっとそれなりに適当な粒度のメソッドに分割されたコードを作る事ができた、と思う事ができるようになります。

そして、いくつかの責務を負っているクラスを分割したくなります。 この衝動はどうしても抑える事ができないのですが、よくよく考えずにやってしまって、最終的にgit checkout -fをしなければいけなくなる時があります。

きっとプログラミングが仕事の人達は、最初から見通しをつけて空のメソッドをどんどん書いて、後から、その中に処理を書いていく事ができるんだと思います。何年も仕事をしていて、いまだにこんな事をしているのは自分だけに違いないと思うと情けないけれど、負けずに頑張っていきたいと思います。

この記事で取り上げた品々

2012/06/27

CursorLoaderとAsyncTaskLaoderを使ったアプリのソースコードを公開してみた

最初は「Android 3.0以前でのFragmentとLoaderによるプログラミングのすゝめ」というタイトルにしたのですが、説明的すぎたので止めました。

さて、本屋さんでは、あんざい ゆきさんの本などを除いて、初心者向けのAndroid本のほとんどで、FragmentやLoaderについての記述がある本をみることはありません。

初心者向け本が備えるべき要件をここで書くつもりはないので、何が初心者向けなのかは議論の余地があると思いますが、網羅的な本を読むよりは、多少偏ってもアプリケーションを一つ作って、その周辺知識を増やしていく方が学習方法としては効果的じゃないかなと考えています。

その点では良い土台が必要なんじゃないかなと思うわけですが、そのうちAndroid 2.3を対象にしたアプリケーションでもFragmentやLoaderを使ったプログラミングを推奨する「やり直し本」が出てくるんじゃないかなと期待しているんですが、どうするべきなんでしょうね。

Android 1.6の頃はThreadを生成して、Handlerオブジェクトに処理を実行させるコードを書いた事もあったかもしれませんが、アプリケーションを見通し良く開発していくためには、新しい方法を取り入れて進んでいくしかないと思っています。

互換ライブラリを使ったアプリケーションの開発

CursorLoaderはContentProviderに対するwrapperのように使えば良いのですが、そういう説明をみなかったので実際にアプリケーションを作ってみました。

アプリケーションのベースは以前作成した「日本の郵便番号検索 Free」で、郵便番号の3桁+4桁の数字のみを入力するようにして、内部のコードはシンプルに保っています。

作成したアプリケーションのスクリーンショット

内部構造や使う仕組みは一部は流用していますが、ほぼ新規に作成しています。 ログの出力用メソッドに対する工夫など、役に立ちそうな部分は極力反映したつもりです。

このアプリケーション「Yamaneko」のソースコードはApache License 2.0でGithubにて公開してます。

アプリケーションの導入や内部構造について

使うためのEclipseの操作方法はスクリーンショットを交えて説明しているので、内部構造の説明を含めてgithubのwikiを参照して下さい。

データフロー概要

実際にアプリケーションを作って感じたこと

「動けばいい」というのではなくて、内部構造をシンプルに保ちつつ見通しのよいコードを書くために、普通はフレームワークを開発します。

Android 3.0やCompatibility Packageで提供されている新しいクラスは、そういう新しい(抽象度は低めかもしれませんが)フレームワークに相当する処理で、開発元が公式に提供しているものとなります。

その点では独自になにか仕組みを作るよりは、馴染むまでに時間がかかるかもしれませんが、できるだけ使った方が良いスタイルを強制できる事になります。

初心者向けの雛型アプリケーション

GNU公式のGNU Helloアプリのソースコードを雛型に開発をしようとして、その完成度の高さのあまりに挫折した人も多いのではないでしょうか。

まぁ、GNU Helloはほとんどネタですけれど、Androidでも似たようなアプリケーションが必要なのかもしれません。

本屋さんでAndroidの初心者向け開発本を眺めた印象では、初心者向けの良い雛型アプリケーションを提供する本がないなぁという印象を受けました。

内部構造を知るためにAndroidのframeworks/base/coreディレクトリにあるソースコードを見るのは普通だと思いますが、アプリケーションを開発するための土台としては、何か参考になるか聞かれて「GNU Helloみてみたら」みたいな定番があった方が便利じゃないでしょうか。

ApiDemoアプリも良いんですけれど、単品の機能だけなので、連携が分からないところがネックかなと思っています。

非同期処理のための他の方法との比較

メインUIやContentProvider, Serviceプロセスをブロックしないためには、HandlerやAsyncTaskクラスを使えば、AsyncTaskLoaderを使う必要は必ずしもありません。

仕組なくともProgressViewでアップデートするためにはAsyncTaskLoaderは適切ではなくて、AsyncTaskクラスの方が便利でしょう。

今回、CusrorLoaderとAsyncTaskLoaderを使った印象は、自前でAsyncTaskを管理してContentProviderの内部からタスクを起動するよりも、AsyncTaskLoaderでネットワークアクセスのみを扱い、ListViewへの出力はCursorLoaderと平行に起動できるのは内部構造としては見通しが良く、管理もしやすかったです。

外部のWebサービスから結果を取得して表示するアプリケーションでは、今回の構造がいまのところベストかなと思います。

表示するデータをContentProviderに管理させる構成について

直感的には自分のアプリケーションで表示するデータをDBに入れるだけではなくて、そのためにContentProviderのサブクラスを作成して、AndroidManifest.xmlに登録するというのは冗長に感じます。

とはいえ、twitterのタイムラインのように、ListViewやGridViewに表示する項目数が想定できないような場面では、データベースを中継してCursorオブジェクトでListViewやGridViewに表示する方法がベストです。

多少面倒でも、SimpleCursorAdapterとCursorLoaderを組み合せるのは良い方法だと思います。 パフォーマンスが懸念される場面でListAdapterを使うというのは、いまのところ納得していません。

また、副次効果としてSQLiteDatabaseオブジェクトをContentProviderクラスだけが扱うことになるのは良い構造だと思います。

さいごに

自分のコードが最善だとは到底思えないので、アプリケーションのコードを公開するのは気が重いです。

Androidアプリのソースコードは、あまりみないので、公開しようかなと思ったのですが、 Apache License 2.0やGPLv3などのライセンス別にまとめるサイトがあってもいいかもしれません。

いろいろ叩かれるとしても、それを乗り越えられるなら、コードを隠しても良い事はほとんどないですからね。

次に勤める会社があるなら、その会社との契約に縛られない限りは、 まとまった処理単位での動きが確認できるアプリケーションのコードはこれからも公開していきます。

2012/06/23

Pythonスクリプトの出力をリダイレクトしたらエラーになった

このブログサイトに登録してあるキーワードを出力するスクリプトをgdataライブラリを使って、pythonで書いているのですが、出力結果をファイルにキャッシュしておこうとしてラップしているbashスクリプトに../list_labels.py | tee labels.txt を追加したところ、次のようなエラーが発生しました。

  File ".../python/list_labels.py", line 133, in run
    self.list_labels()
  File ".../python/list_labels.py", line 125, in list_labels
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-7: ordinal not in range(128)

問題になったのは次のようなコードの中で、print labelをしているところです。

問題になった該当コード周辺

...
    l = []
    for label in set(labels):
      l.append(label)
      pass
    for label in sorted(l):
      print label
      pass
    pass

Googleでとりあえず検索してみた結果

いくつかの方法がありましたが、sys.setdefaultencodingにencodingを記述するといった方法がありました。

import sysをしてからdir(sys)でみると、少なくとも手元のpython 2.7.3にはそんなメソッドは定義されていません。

次にヒットしたのはunicodeに文字列を変換するものですが、どちらかというとスクリプト中に埋め込んだutf-8文字列を画面に出力するための方法を解説しているもので、リダイレクトの時に問題が発生している事については触れられていませんでした。

pythonのバージョンに依るのかもしれませんが、この他の方法もいまいち決定的なものはありませんでした。

unicodeに変換するのではなく、リダイレクトの時の正しいencodeを指定する

解決策は次のようにencode()関数を追加して'utf-8'に変換しました。

変更後の該当コード個所の抜粋

    l = []
    for label in set(labels):
      l.append(label)
      pass
    for label in sorted(l):
      print label.encode('utf-8')
      pass
    pass

リダイレクトの時にLANGなどをみて標準的なlocaleに変化してくれれば良いのですが、リダイレクトの時にencodeが設定されないというのは、かなり悩ましい挙動です。

暗黙の挙動だから不定になるのは当然という意見もありそうですが、リダイレクトの時に挙動が切り替わるのは、やっぱり謎仕様です。rawデータとして何の処理もせずにbyte列を掃き出すなら、まだ分かるんですけどね。

ともかく無事に終わったので部屋の整理をして今日は休もうと思います。

Android In-app Billingのサンプルを自前アプリに組み込んでみた

AndroidのIn-app Billingは、無料アプリケーションの中で課金アイテムを販売するための仕組みです。 Application Licensingが有料アプリケーションのみを対象としているのと対照的です。

無料と有料とを別々に販売する方法は、アプリ内広告のためのライブラリを省くなどなど、アプリケーションサイズを小さくすることなどはできますが、その反面、コードのメンテナンスやテストはいろいろと面倒だったりします。

無料アプリをそのままにして、有料アプリを頻繁にバージョンアップさせる方法もありますが、 あまり良いインタフェースではなさそうに思えたので、今回は単一のアプリケーションイメージを無料で配布して、 その中で一部機能のロックを解除するツールを販売する方法にしてみました。

このIn-app Billingのサンプルアプリケーションを動かすのも、Androidプログラミング自体が始めての場合には いろいろと面倒そうですが、手順はいろいろ出回っていて、そもそもDev Guideの手順が一番充実していたりするので、 今回はこのサンプルのコードを自分のアプリケーションに組み込んだ時のログを残す事にしました。

Dungeonsアプリについて

Android Dev. Guideに従って作業を進めると、Google PlayにAPKファイルをアップロードするところまでは簡単に進むと思います。

アップロードしてからすぐに課金アイテムの作成などを行なう事ができるようになりますが、 アプリケーションから正しく処理ができるようになるまでは30分ほどかかりました。

うまく動かない場合には、アイテムを増やしたりするよりも、しばらく時間を置くのが良さそうです。

非署名アプリからの購入テスト

一度署名アプリをGoogle Playに登録して、課金アイテムを登録すると、emacsからアプリを起動した時のように署名をしていないapkから起動したアプリも正常に動き、"android.test.purchased"の購入などのテストができるようになります。

作業の進め方 (方針)

Dungeonsアプリを下敷に、自分のアプリケーションからアイテムの購入ができるようにする方法を考えます。

今回はDungeonsをライブラリに変更せずに、自作アプリケーション配下のパッケージに導入します。

ManagedアイテムとUnManagedアイテム

一回購入すると購入者情報と紐付いて、アプリケーションをアンインストールしようが購入履歴がGoogleに保存されて、再インストールしたアプリにも引き継がれる、それがManagedアイテムです。

よくよくアプリ課金で問題になるコインや武器といった繰り返し購入可能なアイテムはUnmanagedと呼ばれていますが、今回は対象としては考えません。

課金(Billing)機能の組み込みについて

基本的にはDungeonsアプリのDungeons.javaファイルを除いて、全てのファイルを自分のアプリケーションにコピーします。

Eclipse上のPackage Explorerでは次の画像のように、自分のアプリケーションのパッケージの中にbillingサブパッケージを追加して全てのクラスをコピーしてきました。

Eclipse上のPackage Explorerの様子

この他にsrcフォルダに "com.android.vending.billing" パッケージを作成し、IMarketBillingService.aidl ファイルをコピーしておきます。

また"com.example.dungeons.util"パッケージのBase64.javaとBase64DecoderException.javaもコピーしてきます。

参照関係はEclipseのエラーを確認しながら修正していきます。

Dungeonsアプリとの差分について

参照関係を解決しても、Dungeonsアプリ固有のUIを操作している部分は削除する事になります。

パッケージ名やコメント内の重要ではない部分を省いた差分は、概ね次のようになります。

BillingService.java

-    class RequestPurchase extends BillingRequest {
+    public class RequestPurchase extends BillingRequest {

-    class RestoreTransactions extends BillingRequest {
+    public class RestoreTransactions extends BillingRequest {

Security.javaの差分 (セキュリティ上の理由から省略)

-            String base64EncodedPublicKey = "...";
+            String base64EncodedPublicKey = "...";

RequestPurchaseをpublicに変更したのはパッケージnet.yadiary.android.exifpc.billingの外部からアクセスする必要があるからです。

Dungeonsでは単一のパッケージの中に含まれるので意識しない部分ですが、それを除いてもかなり使い周せるように考えられて作られていると思います。

自分のアプリからbillingパッケージにコピーしたクラスを使う

いよいよメインの作業に入っていきますが、Dungeons.javaを参考にしていきます。

購入アイテムのリストを作成する

クラス変数としてCATALOGのエントリを作成します。 今回のアプリでは画面に表示する名称は別途管理するので、このCatalogEntryの第二引数は実際には使っていません。

mBillingServiceインスタンスは外部から操作する必要があるのでアクセスできるよう、getterのみ定義しています。

MainActivityのライセンス関連変数


        /*
	 * ライセンス用設定
	 */
	private static final String TAG = "ExifPM";
	private static final String DB_INITIALIZED = "db_initialized";

	private Handler mHandler;
	private BillingService mBillingService;
	public BillingService getBillingService() {
		return mBillingService;
	}
	private ExifPMPurchaseObserver mExifPMPurchaseObserver;

	/**
	 * カタログ情報はここにまとめ、各Fragmentが参照する
	 */
	public static final CatalogEntry[] CATALOG = new CatalogEntry[] {
			// primary selling tools
			new CatalogEntry("exifpm_purchases_item", R.string.billing_item_hiddenads, CatalogEntry.Managed.MANAGED),
			// debug items
			new CatalogEntry("android.test.purchased", R.string.billing_item_hiddenads, CatalogEntry.Managed.MANAGED),
			new CatalogEntry("android.test.canceled", R.string.billing_item_hiddenads, CatalogEntry.Managed.MANAGED),
			new CatalogEntry("android.test.refunded", R.string.billing_item_hiddenads, CatalogEntry.Managed.MANAGED),
	// please add other stuffs under this line.
	};
	private PurchaseDatabase mPurchaseDatabase;
onCreateメソッドでのライセンス関連設定

onCreateメソッドでのライセンス関連設定


                mHandler = new Handler();
		mExifPMPurchaseObserver = new ExifPMPurchaseObserver(mHandler);
		mBillingService = new BillingService();
		mBillingService.setContext(this);
		ResponseHandler.register(mExifPMPurchaseObserver);
		if (mBillingService.checkBillingSupported(null) == false) {
                        // 必要に応じてエラーメッセージを表示する
			Toast.makeText(this, R.string.billing_toast_notsupported, Toast.LENGTH_LONG).show();
		}

最後のToast文は課金がサポートされていない事を通知するためのメッセージです。 Emulatorなどで実行すると、ここでメッセージが表示されるはずです。

onDestroyでの後始末

課金サービスに限らずServiceに定義したインスタンスはunbindしないと、バックグラウンドで稼働してバッテリーを消費します。 定義されているだけではServiceは起動しませんが、BillingServiceのようにアクセスされ、起動したServiceは、いわゆるlong-runningプロセスとしてシステムが管理します。 GCが動くとか妄想は止めて、必ず停止するようにしましょう。

onDestroyメソッド全体


        @Override
	protected void onDestroy() {
		mBillingService.unbind();
		super.onDestroy();
	}
PurchaseObserverサブクラスの作成

Activityクラス内にPurchaseObserverのサブクラスを定義します。

ここでは変数として宣言されていた、mExifPMPurchaseObserverを作成していきます。 参考までに作成されたExifPMPurchaseObserver全体は次のようになっています。

ExifPMPurchaseObserverクラス全体 (一部省略)


	private class ExifPMPurchaseObserver extends PurchaseObserver {
		public ExifPMPurchaseObserver(Handler handler) {
			super(MainFragmentActivity.this, handler);
		}
		@Override
		public void onBillingSupported(boolean supported, String type) {
			if (type == null || type.equals(Consts.ITEM_TYPE_INAPP)) {
				if (supported) {
					restoreDatabase();
				}
			} else if (type.equals(Consts.ITEM_TYPE_SUBSCRIPTION)) {
				// This type is not essential of this application
			} else {
				// not supported state, do nohing.
			}
		}
		/**
		 * キャンセル、リファンド通知はこのメソッドのpurchaseStateで判断する
		 */
		@Override
		public void onPurchaseStateChange(PurchaseState purchaseState, String itemId, int quantity, long purchaseTime, String developerPayload) {
			if (purchaseState == PurchaseState.PURCHASED) {
				if (itemId.equals(CATALOG[0].sku) || itemId.equals(CATALOG[1].sku)) {
					if (adView != null) {
						adView.setVisibility(View.GONE);
					}
				}
			} else if (purchaseState == PurchaseState.CANCELED) {
			        // do nothing
			} else {
				// refunded state
				if (itemId.equals(CATALOG[0].sku) || itemId.equals(CATALOG[2].sku) || itemId.equals(CATALOG[3].sku)) {
					AppConfig.isFreeEdition = false;
				}
			}
		}

		@Override
		public void onRequestPurchaseResponse(RequestPurchase request, ResponseCode responseCode) {
			if (responseCode == ResponseCode.RESULT_OK) {
				if (Consts.DEBUG) {
					Log.i(TAG, "purchase was successfully sent to server");
				}
			} else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
				if (Consts.DEBUG) {
					Log.i(TAG, "user canceled purchase");
				}
			} else {
				if (Consts.DEBUG) {
					Log.i(TAG, "purchase failed");
				}
			}
		}

		@Override
		public void onRestoreTransactionsResponse(RestoreTransactions request, ResponseCode responseCode) {
			AppConfig.sendMessage("called with ResponseCode=" + responseCode);
			if (responseCode == ResponseCode.RESULT_OK) {
				if (Consts.DEBUG) {
					Log.d(TAG, "completed RestoreTransactions request");
				}
				// Update the shared preferences so that we don't perform
				// a RestoreTransactions again.
				SharedPreferences prefs = getPreferences(Context.MODE_PRIVATE);
				SharedPreferences.Editor edit = prefs.edit();
				edit.putBoolean(DB_INITIALIZED, true);
				edit.commit();
			} else {
				if (Consts.DEBUG) {
					Log.d(TAG, "RestoreTransactions error: " + responseCode);
				}
			}
		}
	}

ここではコンストラクタを除くと4つのメソッドが定義されています。 全てのメソッドの基本的な構造はDungeonsクラスから、そのまま引き継いでいます。

前半のonBillingSupportedとonPurchaseStateChangeは

後半のonRequestPurchaseResponseとonRestoreTransactionsResponseはアプリケーションの動きに応じて、例えばリセットされたアプリケーション起動時にステータスを回復したタイミングで「購入情報を更新中です」みたいなメッセージを表示する事ができます。

restoreDatabaseの動き

バッサリ省略したrestoreDatabaseメソッドは、キャッシュクリアされたアプリを起動した場合などに、購入済み情報を取得するためのメソッドです。

実際の取得処理はmBillingServiceのrestoreTransactionメソッドを呼び出します。

実際の購入処理

このActivityの管理下にあるFragmentから実際の購入処理を呼び出す事になります。

ボタンなどをクリックした時にActivityのBillingServiceインスタンスに対して、 requestPurchaseメッセージを送信します。

この処理は簡単なので省略します。

まとめ

課金処理を追加する事自体でアプリケーションコードは、ほとんど増えませんし、パーミッションも明示的なcom.android.vending.BILLING 1つだけなので、良いソリューションだと思います。

反面、課金処理は簡単ですが、ゲームなんかでコインを購入させるのは、どうかなぁと思います。

単純に時間短縮のためにコインを購入オプションがあって、時間さえかければ先に進めるようなものは良いのですが、ゲームバランスが極端に悪いものは遊んでいてあまりおもしろいとは思いません。

じゃぁなんで課金機能を追加したのかと言われれば、日本の法律では寄付の受付は禁止ですから、アプリケーションを気に入ってもらったり、このブログが参考になったりした場合に、代りにアイテムを購入して頂ければ幸いです。

2012/06/22

HVGA,タブレット両対応の郵便番号アプリをViewPagerとFragmentで作りかえてみ た

以前作成した郵便番号アプリはSlidingDrawerを使って同一レイアウトXMLに全てのViewを記述しつつ、 入力部分の描画を切り替えるようにしていました。 タブレットではSlidingDrawerを使わずにViewを配置する事で、画面サイズの違いに対応していました。

今回はViewPagerを使う事で、SlidingDrawerのツメ部分の描画が省略されるなど、若干シンプルになりました。 タブレットではViewPagerを使わずにレイアウトXMLを記述しています。 まったくの同一コードという分けにはいかないので、MainActivity側でViewPagerインスタンスが入手できない事を検出して、Fragmentへのアクセス方法を切り分ける事で、同一コードで対応しました。

今回はViewPagerならではの部分についてまとめていきます。

ViewPagerを使用した画面イメージ Tablet用画面イメージ

対応するバージョン、使用したAPI等々

今回のアプリケーションはAndroid 1.6以降、Android 4.0までをターゲットにしています。 検証のために次のような機器を使用しています。

  • Acer Liquid MT (2.3 800x480)
  • Iconia Tab A500 (3.2 1280x800)
  • Sony Xperia Mini Pro (4.0 320x480)

内部ではFragmentを使うために、android.support.v4パッケージを使用しています。 参考までにActivityクラスのimport文は、次のようになっています。

Activityクラスのimport文抜粋

import net.yadiary.android.actionbarcompat.ActionBarActivity;
...
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.widget.CursorAdapter;
...

ViewPagerがある場合とない場合の切り替え方法

レイアウトXMLにViewPagerの記述があれば、findViewById(R.id.pager)のようなメソッドでインスタンスが取得できるはずです。

課題はFragmentのインスタンスにどのようにアクセスするのかという事です。 通常はFragmentManagerインスタンスを経由して、Fragmentにアクセスしますが、 ViewPagerが設定するFragment Tag名は外部からは(一応)分かりません。

Activity(実際にはFragmentActivityをベースにしたActionBarActivityの子クラス)中のコードは次のようになっています。

onCreateメソッドの抜粋

	protected void onCreate(Bundle state) {
		super.onCreate(state);
		setContentView(R.layout.main);
		viewPager = (ViewPager) findViewById(R.id.pager);
		if (viewPager != null) {
			myAdapter = new MyAdapter(getSupportFragmentManager());
			viewPager.setAdapter(myAdapter);
		}

viewPagerの中に配置するFragmentはMyAdapterクラスのgetItem(int position)メソッドの中でインスタンスを生成しています。 ここら辺はオフィシャルのFragmentPagerAdapterリファレンスを参照してください。

FragmentにアクセスするためのサポートメソッドをActivity中に追加しています。 ViewPagerを使う場合は、アダプターのinstantiateItem(viewPager, position)を使用しています。

FragmentはレイアウトXMLで記述したのでR.id経由で指定していますが、 ViewPagerのインスタンスがnullの場合に、必要な場面は想像できませんが、動的にFragmentを定義する事もできます。

Activityクラスに追加したFragment取得用サポートメソッド

	public Fragment getFragment(int position) {
		Fragment ret = null;
		if (viewPager != null) {
			ret = (Fragment) myAdapter.instantiateItem(viewPager, position);
		} else {
			FragmentManager fm = getSupportFragmentManager();
			if (position == PAGER_PAGE_INPUT) {
				ret = fm.findFragmentById(R.id.fragmentInput);
			} else {
				ret = fm.findFragmentById(R.id.fragmentListView);
			}
		}
		return ret;
	}
レイアウトXML中でのViewPager, Fragmentの記述方法

android.support.v4パッケージを使っている事で、レイアウトXMLの具体的な書き方は、Android 3.0以降に対応したものとは少し変わっています。

ここら辺の書き方で困る場合もありそうなので、該当個所の抜粋だけ載せておきます。

android.support.v4のViewPager, FragmentレイアウトXML記述例

...
    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="0.9" >
    </android.support.v4.view.ViewPager>

...

        <fragment
            android:id="@+id/fragmentInput"
            android:name="net.yadiary.android.jpostal.InputFragment"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >
        </fragment>

...

要素名をパッケージで指定したり、大文字が小文字だったり、知っていれば何でもないんですけどね。

まとめ

ViewPagerを使う事で画面はシンプルになりましたが、ActionBarを使っているので20〜30ピクセルほどは以前よりも画面を占有するようになりました。

とはいえ、Fragmentに分けた事で内部構造は分割統治が可能になりシンプルになりました。

以前のコードはサポートクラスに処理を切り出したりはしていましたが、ステータス管理の面からは巨大なActivityクラスでした。 android.support.v4パッケージとFragmentを使う事で、互換性を維持しつつ、よりレイアウトXMLを中心としたアプリケーション開発ができるでしょう。

正直なところFragmentを始める前は、解説書を読んでもどういう風に処理を分けたらいいのか、イメージを持つ事が難しかったです。

まずは新規で簡単なアプリを作って慣れてから、古いアプリのActivityをFragment対応にする場合には、レイアウトXMLに対応する新しいFragmentクラスを作って、Activityの処理をFragmentクラスに移すようにするべきでしょう。

いろいろなデバイスに対応するのは面倒ですが、参考になれば幸いです。

2012/06/18

ActionBarCompatをライブラリ化して自分のアプリに組み込んでみる

[2012年6月29日追記] Google IO 2012でActionBarの互換ライブラリがリリースされる予定だとの発表がありました。 この内容を利用する前に、公式の互換ライブラリがリリースされていないかご確認下さい。

自作アプリケーションからActionBarを使おうとした場合には、API Level 11(Android 3.0)以降でないと対応していません。 不特定多数に配布するアプリケーションの作成する際には、普通はAndroid 1.6か2.1以降、どんなに悪くても2.3以降の対応として、まだAPI Level 11以降のみをターゲットにしたアプリケーションを作成する機会は少ないのではないでしょうか。

Android 3.0以前に対応したActionBarの実装について検索をすると、SDK以下のsamples/android-15/ActionBarCompat/にAPI Level 4(Android 1.6)以降に対応したActionBar互換アプリケーションがある事がわかります。

最初はioschedアプリもActionBarのような動きをするので参考にしようとしたのですが、面倒になったので改めて探してctionBarCompatアプリに辿りついたのでした。

このアプリケーションを全面的にコピーする方法は試されているようでしたが、 便利そうだったので、これからいくつかのプロジェクトで使う機会もあるだろうと思ったので、ライブラリとして自分のアプリケーションから参照を追加するようにしました。

作業環境

基本的にはv4サポートライブラリを使って、Android 2.3端末のAcer Liquid MTの実機で確認していきます。

Android 3.2(Honeycomb)環境としては、Acer Iconia Tab A500を使って確認します。

Android 4.0(ICE)環境は、Xperia Mini Proを入手しようとしていますが、いまのところはエミュレーターで行ないます。

ActionBarCompatをライブラリ化する流れ

作業ステップはおおまかに次のようになります。

  • ActionBarCompatサンプルから、自分のworkspace以下に新しいプロジェクトを作成
  • プロジェクトのパッケージ名を変更
  • srcフォルダのパッケージ名をプロジェクトのパッケージ名に変更
  • MainActivityなどの不要なファイルを削除
  • (android.support.v4.app.Fragmentを使う場合のみ) ActionBarActivityクラスをFragmentActivityのサブクラスに
  • プロジェクトのプロパティからAndroid欄の"is library"にチェックをつけライブラリプロジェクトに変更
  • 自作のアプリケーションのプロパティからライブラリに追加
  • 自作アプリケーションのActivityをActionBarActivityのサブクラスに変更
  • 自作アプリケーションに見栄えに合せたActionBarの背景色などの変更

The ActionBar Result Image

ActionBarCompatサンプルから、自分のworkspace以下に新しいプロジェクトを作成

新規プロジェクトの作成する時にはオプションの中からCreate project from existing sampleを選択すると、サンプルプロジェクトのコピーをデフォルトのworkspace以下に作成する事ができます。

ActionBarCompatサンプルはAPI Level 15 (Android 4.0.3)を選択すると表示されます。

プロジェクトのパッケージ名を変更

このままではパッケージ名がcom.exampleeから始まってしまうので、自分のドメインに変更してしまいます。

この作業は、まずプロジェクトフォルダを右クリックして、Android Toolsの中にある"Rename Application Package"メニューを選択して行ないます。

この時に、次のステップにあるsrcフォルダの中にあるパッケージ名を先に変更してしまうとEclipseが競合を解決できなくなるので注意してください。

srcフォルダのパッケージ名をプロジェクトのパッケージ名に変更

おなじみのsrcフォルダ直下にあるパッケージフォルダを右クリックして"Refactor"から"Rename"を選択します。

この時入力するパッケージ名は先ほど行なったApplication Packageの名前と同じにしておきます。

MainActivityなどの不要なファイルを削除

この時点でMainActivityを選択して実行すると、サンプルを動かすことができるはずです。 問題なくビルドできているプロジェクトをライブラリプロジェクトにしていきます。

resフォルダの中にあるlayout/main.xml, menu/main.xmlは不要なので削除しておくか、自作アプリケーションのプロジェクトに移動するなどして、ActionBarCompat以下には配置しないようにします。

また2つのmain.xmlとMainActivity.javaを削除すると、values/strings.xmlの内容もほとんど不要になります。

values/strings.xmlからapp_nameを残して、他のエントリを削除します。

(android.support.v4.app.Fragmentを使う場合のみ) ActionBarActivityクラスをFragmentActivityのサブクラスに

互換パッケージのFragmentを使う場合には、ActivityのサブクラスからはFragmentManagerのインスタンスにアクセスできないので、FragmentActivityを使用します。

この場合には、ActionBarActivityクラスの親クラスをActivityからandroid.support.v4.app.FragmentActivityに変更します。

あるいはActionBarActivityクラスファイルをコピーして、ActionBarFragmentActivityのようなクラス名にした上で、FragmentActivityの子クラスとしてもいいかもしれません。

将来的にActivityとFragmentActivityを使い分ける事があるのなら、こちらの方がお勧めです。

ActionBarActivity変更個所の抜粋

public abstract class ActionBarActivity extends FragmentActivity {
    final ActionBarHelper mActionBarHelper = ActionBarHelper.createInstance(this);
プロジェクトのプロパティからAndroid欄の"is Library"にチェックをつけライブラリプロジェクトに変更

ここまで作業を進めて、エラーがなければライブラリにして、他のプロジェクトから参照できるようにします。

is Libraryをセットした画面キャプチャ

自作のアプリケーションのプロパティからライブラリに追加

ここまできて、自作アプリケーションのプロジェクトフォルダに移動します。

プロパティから参照するライブラリにActionBarCompatを指定します。

Libraryを追加した画面キャプチャ

自作アプリケーションのActivityをActionBarActivityのサブクラスに変更

extends Activityextends FragmentActivityと書かれているところを、extends ActionBarActivityに変更します。

ActionBarCompatのvalues/menu/main.xmlを参考にして、通常のmenuリソースを作成して、ActionBarActivityのサブクラスでonCreateOptionsMenu(Menu)メソッドでメニューを構成します。

ここでndroid:showAsAction="always"が指定されたメニューはActionBarにアイコンが表示されます。 "never"を指定すれば、別途メニュー(Android 3.0以降ならContext Menu)に文字とアイコン付きで表示されます。

Menuの作成方法については、これぐらいの点を気にすれば、他は特に変わった点はありません。

自作アプリケーションに見栄えに合せたActionBarの背景色などの変更

メニューの作成方法は通常通りですが、この時点ではまだ、ActionBarらしくはみえません。 ここからさらに必要な作業をまとめると次のような項目があります。

  • 自作アプリケーションのAndroidManifest.xmlのMainActivityに ユニークなStyle名を設定
  • 自作アプリケーションのresフォルダに、values/styles.xmlを作成
  • ActionBarCompatプロジェクトからvalues-v11, values-v13フォルダを自作アプリケーションのresフォルダにコピー

カスタマイズの方法について

ここではタイトルバーの色と文字色を変更するまでの流れをまとめておきます。

テーマの有効化

まずはAndroidManifest.xmlに、ActionBarCompatのAppThemeとは違う名前のテーマを設定します。

@style/JPAppThemeを指定した例

<activity
  android:name=".MainActivity"
  android:label="@string/app_name"
  android:theme="@style/JPAppTheme" >
  <intent-filter>
      <action android:name="android.intent.action.MAIN" />
      <category android:name="android.intent.category.LAUNCHER" />
  </intent-filter>
</activity>

この変更をすると、全体が白色ベースのThemeに変更され、ActionBarも太く、それらしく表示されるはずです。

ActionBarの背景色と文字色を変更する

変更には、全体をコピーするのではなく、ActionBarCompatで定義されている@style/AppThemeをparentとして差分を指定して、必要な個所だけを変更するようにします。

まずタイトル文字色を指定するリソースをvalues/colors.xmlに定義します。

values/colors.xml全体

<resources>
    <color name="jpactionbar_title_color">#efefef</color>
</resources>

次にAndroid 1.6〜2.3のアプリケーション用に背景色と文字色を変更します。

values/styles.xmlファイル全体

<resources>
    <style name="JPAppTheme" parent="@style/AppTheme">
        <item name="android:windowTitleBackgroundStyle">@style/JPActionBarCompat</item>
        <item name="actionbarCompatTitleStyle">@style/JPActionBarCompatTitle</item>
        <item name="android:textColor">@color/jpactionbar_title_color</item>
    </style>
    <style name="JPActionBarCompat" parent="@style/ActionBarCompat">
        <item name="android:background">#283255</item>
        <item name="android:textColor">@color/jpactionbar_title_color</item>
    </style>
    <style name="JPActionBarCompatTitle" parent="style/ActionBarCompatTitleBase">
        <item name="android:textColor">@color/jpactionbar_title_color</item>
    </style>
</resources>

ActionBarCompatのサンプルでは、parentにTheme.Lightを指定したいたので、色については白色地に黒色が基本になっています。

AndroidManifest.xmlで指定したThemeについては、parentを@style/AppThemeから継承するか、他のandroid:styleのThemeから継承するかはケース毎に違うと思います。

android:styleのThemeをparentに指定する場合には、ActionBarCompatのstyles.xmlを参考にしてください。

続いてvalues-v11/styles.xmlを編集します。

values-v11/styles.xmlファイル全体

<resources>
    <style name="JPAppTheme" parent="@style/AppTheme">
        <item name="android:actionBarStyle">@style/JPActionBar</item>
        <item name="android:textColor">@color/jpactionbar_title_color</item>
    </style>
    <style name="JPActionBar" parent="@style/ActionBar">
        <item name="android:background">#283255</item>
        <item name="android:textColor">@color/jpactionbar_title_color</item>
        <item name="android:titleTextStyle">@style/ActionBarTitle</item>
        <item name="android:icon">@drawable/icon</item>
    </style>
</resources>

こちらもvalues/styles.xmlと同じです。文字色を変更する指定が追加されています。

最後にvalues-v13/styles.xmlを編集します。 values-v11との差分だけを定義するので、内容は1項目だけです。

values-v11/styles.xmlファイル全体

<resources>
    <style name="JPActionBarTitle" parent="@style/ActionBarTitle">
        <item name="android:textColor">@color/jpactionbar_title_color</item>
    </style>
</resources>

これぐらいを指定すると、だいたいどのバージョンの端末でも正しく表示されると思いますが、 API Versionによってボタンの配色など、違うところがあるので、TextViewやButton用にStyleを定義するといった事は必要だと思います。

さいごに

細かい際を全て吸収するのは難しいですが、ActionBar自体はAndroid 2.3とAndroid 3.2の端末で同じようにみえています。

ViewPagerと組み合せて使っていますが、v4サポートのFragmentと組み合せて、 できるだけ快適な操作性を提供していきたいと思っています。

これまで郵便番号検索と、Exif情報を編集するExifPMアプリを作ってきましたが、郵便番号の方はFragment対応を進めていて、ActionBarを組み込む予定です。

2012/06/15

ADT18のproguard-project.txtで困ったところ

JPEG画像のExif位置情報の削除と編集が可能なツールExifPMを作成したのですが、 その署名アプリケーションを作成する時にproguard設定で、いくつかトラブルに遭遇しました。

ここで、その内容をまとめて今後の参考のためのメモを残しておく事にします。

システムの構成

この記事は次のシステム上での挙動について書かれています。 Windowsなど、他のシステム上では当てはまらない可能性もあるのでご注意ください。

  • OS: Ubuntu 12.04 LTS 64bit版
  • CPU: AMD Phenom(tm) II X4 905e Processor
  • Memory: 16GB
  • Android開発環境:NVidia Tegra Android Developer Pack 1.0r8 (最新版"Android SDK r19, ADT 18"更新済み)

症状

Eclipse上で正常に署名済みAPKファイルを作成したと思ったものの、デバイスにインストールしてみたら起動時にClassNotFoundExceptionが発生し、異常終了する問題が発生しました。 症状は次の通りです。

  • EclipseのLogCat上ではActivityクラスやContentProviderクラスが見つからないと表示される
  • 過去に正常に動いていた時の署名済みAPKファイルと比較して、100KB以上サイズが小さくなっている

署名アプリケーションはdebug時と比較すると、Proguardを使用する事によって、1MB以上あったAPKファイルのサイズが350KB程度に圧縮されています。 トラブルが発生したAPKファイルでは200〜300KB程度になっている事も症状の一つでした。

Clean...直後には正常にAPKファイルが生成されたのに、繰り返すとサイズの小さなAPKファイルが生成されてしまいます。

-dontwarnによってアプリケーションパッケージ以下から出る警告を全て無視しているため、 気がつかないうちに一部クラスが欠落したAPKファイルを作り出してしまっているのでしょう。

ADT 18でのproguard-project.txtファイルの取り扱い

これまでは各アプリケーション共通の設定とアプリケーション固有の設定を混ぜたproguard.cfgファイルを準備していましたが、ADT17からはシステム用設定とアプリケーション用設定を分けて管理する事になります。

システム全体の設定の中ではデフォルトで-optimizationsが無効化されているので、 必要な場合には有効にする必要があります。

proguardはClass.forNameでインスタンス化したり、Reflection APIを通じてクラスにアクセスする必要がある場合には、難読化によって指定するべきメソッド名や変数名が変化してしまいますから、少なくともpublicな部分は名称が変更されないようにする必要があります。

例えばAndroidManifest.xmlにはActivityやServiceのクラス名を書きますから、少なくとも ここに記載されているクラス名は難読化の対象から外す必要があります。

システム全体のproguard-project.txtフィルには、Activityとそのサブクラス名を難読化の対象から外すような設定が含まれています。

Tips

問題が発生した時に見直す点をまとめていきます。

署名APKファイルを作成する前の儀式

EclipseのProjectメニューの中で、Clean...を選択します。 Build Automaticallyにチェックを入れていない場合には、Build Projectを選択しておきます。

少なくともADT 18.0.0.v201203301601-306762を使っている現状では、同じ設定でも、Clean...を選択せずにAPKファイルを出力した場合、サイズが小さなり、正しく動かないAPKファイルが生成されています。

これについてはprojectの設定がどこか正しくない可能性が高いと思っていますが、 原因が追求できていないため、Google Playに揚げる署名APKファイルの生成時にはClean...を毎回選択してから作業を行なっています。

staticフィルドからnon-static enumを使用している場合

クラス内部でenumを定義した時に、次のようなコードを作成したところ、proguardがバグっぽい動きをしました。

修正前:問題のあったenum定義

...
public enum Orientation {
	PORTRAIT, LANDSCAPE
}
public static CommonConfig.Orientation orientation;
...

このコードはproguard以前では問題なく動いていましたが、次のように修正してからは問題なく動くようになりました。

修正後

...
public static enum Orientation {
	PORTRAIT, LANDSCAPE
}

このstaticなフィールドとして定義したい内部enum定義であれば、staticという定義は適切だと思います。 むしろ以前のコードで問題なく動いているところに少し違和感を持っています。

課金サービス(com.android.vending.BILLING)を使用している場合

システム全体のproguard-project.txtではライセンスサービス用のインタフェースは含まれていますが、 課金サービス用の設定は含まれていませんでした。

ひょっとしたら必要ないのかもしれませんが、基本的にgenディレクトリ以下に出力されるクラスは全てproguard-project.txtの中で対象から外すように設定するべきだと思います。

次のような設定を追加しました。

proguard-project.txtに追加したBilling用設定


-keep public class com.android.vending.billing.*
Google Maps APIを使用している場合のproguard-project.txt設定

mapsについてはproguardの対象にする必要がないと思っていたのですが、 手元では次のように設定しないと動きませんでした。

google maps関連のproguard-project.txt該当個所

-keep public class com.google.android.maps.**
AdMob用設定の追加

AdMob用に次のような設定を追加しています。

AdMob用proguard-project.txtの該当個所


-keep public class com.google.ads.** {
    public protected *;
}
-keep public class com.google.gson.** {
    public protected *;
}

現状のproguard-project.txtファイル全体

参考までに、現在使っているproguard-project.txtの内容を全てそのまま載せておきます。

現行proguard-project.txtの全体

# Add any project specific keep options here:

-keep public class com.google.android.maps.**
-keep class net.yadiary.android.exifpc.beans.*
-keep class net.yadiary.android.exifpc.provider.*

-keep public class com.google.ads.** {
    public protected *;
}
-keep public class com.google.gson.** {
    public protected *;
}

-keep public class com.android.vending.billing.*

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class net.yadiary.android.exifpc.InfoFragment.DemoJavaScriptInterface {
   public *;
}

-dontwarn net.yadiary.android.exifpc.**

さいごに

いくつかシステム全体でContentProviderのサブクラスを指定しているのに、なぜか手元でも明示的にContentProviderのサブクラスを指定しないとうまく動かなかったりしています。

おそらくenumのstatic修飾子のように、適切な記述ができていない部分があるのではないかなと思います。

そのため、ここに書かれている内容はベストとは限りませんが、現状で正しく動く設定であるのも確かなので、そこら辺をふまえて参考にしてください。

2012/05/24

Fragmentを使ったAndroidアプリの作成

いままでの知識と新しい情報

Androidアプリを作っていましたが、画面の切り替えにTabを使おうとしたところで、 Deprecatedクラスを避けるためにFragmentを使うことにしました。

minSdkを低い値にしているとはいえ、TabHost, TabWidgetを使うために標準ライブラリに代替クラスは準備されずに、 Support Librariesを使わなくてはいけないというのは少しおかしな印象を持ちます。 いずれにしても、良い機会なのでFragmentについての資料を読み漁っているところです。

いろいろ新しい仕組みが整いつつあるので、これまでProviderによるNetwork MVCパターンを使ってきた非同期更新の仕組みをLoaderに切り替えたり、ProgressBarで逃げてきたAsyncTaskを管理する処理でViewを持たないFragmentを使うなど、実装をやり直す余地はありそうです。

ただ、いままでの方法は、これはこれで問題もないので、とりあえずはTab表示したいActivityと使い回しをしたいActivityをFragmentに置き換えたり、バックグランドで処理をするのが面倒だったためAsyncTask + ProgressBarで逃げていたところをViewなしFragmentにするところから始めようかなと考えています。

まぁ、ここにきて時間が少しある事もあって、あんざいゆきさんの著書やYoutube講演/資料なんかを中心にAndroid 3.x以降の変更点を調べています。

いままでPlatform 2.3.xまでの知識で、1画面1Activityなアプリを作成してきて満足していたので、Fragmentは面倒そうに感じて避けてきましたが、いまは積極的に使うべきだと思っています。

標準的な枠組みの中で、これまで各自が個別に対応していた処理が行なえるのは、チームで開発する場合などに威力を発揮するはずです。

Fragmentの良いところ

Fragmentを使う事の利点は、明示的にViewを生成して返すonCreateViewメソッドが準備されている事でしょうか。 小さな事ですが、全体の流れやメソッドに一貫性が生まれつつあるように感じています。

とはいえ、単純にActivityをFragmentに変換しただけでは、いろいろ細かいところで適当ではない処理を行なっているので、使いまわせる部品となるようにBundleオブジェクトなどでインタフェースを考えてやらないといけないところがいくつかあります。

これからFragmentPagerAdapterを使ったり、FragmentActivityとFragmentの呼び出し順などライフサイクルをちゃんと理解しないといけないので、しばらくは忙しくできそうです。

2012/05/20

Apache Commons Imaging (a.k.a. Sanselan)によるExifヘッダを編集する Androidアプリの作成

Apache CommonsのCommons Sanselanは 画像処理を行なうためのPure-Java実装ですが、2009年以降は安定版がリリースされていません。

しかしSVNのリポジトリをみると、Sanselan(org.apache.commons.sanselan)からImaging(org.apache.commons.imaging)へと名前とパッケージ名をかえて、開発が進んでいるようです。

AndroidではSanselanでも問題ないようですが、別件でTiffファイルのヘッダがうまくパースできなかったので、Commons Imagingを使う事にしました。Sanselanを使ったアプリケーションはいくつかありますが、Commons Imagingを使ったサンプルはあまりないようです。

今回はSanselanとImagingを比較しながら、使い方のメモを残していきます。

SanselanによるExif日付情報の書き換え

Sanselan自体にはExifの日時情報を書き換えるような適当なサンプルがないので、Googleで探すとGPSdingsのSanselanExifWriter.java、openstreetmapのExifGPSTagger.javaなどのコードをみる事ができます。

ちなみに、これらのコードはGPLv2, GPLv3で配布されています。

ExifGPSTagger.java でのSanselanを使ったコード例 (一部変更)

  Double[] timeStamp = {
    new Double(calendar.get(Calendar.HOUR_OF_DAY)),
    new Double(calendar.get(Calendar.MINUTE)), 
    new Double(calendar.get(Calendar.SECOND))
  };
  TiffOutputField gpsTimeStamp = TiffOutputField.create(
    GPSTagConstants.GPS_TAG_GPS_TIME_STAMP, outputSet.byteOrder, timeStamp);

SanselanではTiffOutputFieldオブジェクトを作っていきますが、Commons Imagingと比べると編集対象によって値の指定方法が異なる点はすこし扱いずらいと感じました。

Commons ImagingによるExif日付情報の書き換え

SVNからコードをcheckoutすると、パッケージ名が org.apache.commons.imaging に変更されています。 これはスナップショットですが、今回は2012/05/19にチェックアウトしたコードを使っていきます。

内部構造も変更が進んでいるので、単純にパッケージ名を変更するだけでは前述のコードは動きません。 先ほどと同じ内容のコードは次のようになります。

  TiffOutputDirectory gpsDirectory = outputSet.getGPSDirectory();
  gpsDirectory.removeField(GpsTagConstants.GPS_TAG_GPS_TIME_STAMP);
  gpsDirectory.add(GpsTagConstants.GPS_TAG_GPS_TIME_STAMP,
      RationalNumberUtilities.getRationalNumber(new Double(calendar.get(Calendar.HOUR_OF_DAY))),
      RationalNumberUtilities.getRationalNumber(new Double(calendar.get(Calendar.MINUTE))),
      RationalNumberUtilities.getRationalNumber(new Double(calendar.get(Calendar.SECOND))));

SanselanではTiffOutputFieldを作成して追加していましたが、Commons Imagingでは直接TiffOutputDirectoryのaddメソッドを使うように変更されています。

また org.apache.commons.imaging.common パッケージの RationalNumberUtilities を使い、Double型をRationalNumber型に変換するところもポイントです。

全体としては操作方法に統一性があるのでAPIドキュメントとfind, grepがあれば、必要な機能の使い方は推測できると思います。

AndroidでCommons Imagingを使う時の考慮点

パッケージのサイズが大きいので、SanselanのExif編集機能だけをAndroid用にまとめたコードsanselandroidとしてgoogle codeでホストされています。

スナップショットをビルドしてできるライブラリJARファイルは672KBなので、Sanselan-0.97と比較して200KB近く大きくなっています。 そこでsanselandoroidにならってEclipse上でライブラリプロジェクトを作成し、Androidアプリから参照するようにしました。

まだJARファイルのサイズは388KBほどあって、sanselandroidほど小さく(248KB)なっていませんが、うまくまとまればGithubにでも公開したいと思います。需要があるのが分かれば、現状のまま上げてもいいんですけどね。 とりあえずはSanselandroidで十分だと思います。

さいごに

AndroidにはExifInterfaceクラスがあって、そこそこの操作はできますが、書き換えについてはヘッダを壊してしまう場合があるように思えます。

実際の操作はandroidに組み込まれているjheadライブラリを使っているはずです。 少ないファイルを処理している範囲では、体感的なスピードはSanselanでも大差はないと感じています。

Android 2.2のソースコードではjheadのバージョンが2.87です。 そこからの変更点をみていくと、いくつか重要なfixが行なわれているようにみえるのが気になるところです。

2012/04/25

Androidで外部JarがAPKファイルに取り込まれなくなった時の対処法

ひさしぶりに自宅でAndroidアプリを開発しようとして、開発環境をNVIDIAが提供している最新のTegra Android Development Pack 1.0r6に変更したところ、いくつかのアプリケーションで実行時にjava.lang.NoClassDefFoundError:エラーを出して実行できなくなっていました。

どうやらAndroid SDK Tools r17以降(正確にはADT 17.0.0以降)では、外部Jarファイルの扱いについて変更が入った模様です。

Android SDK r17からの変更点について

r17からのJarファイルの取り扱いについて、どのように問題を解決したかは、FoxykeepがHow to fix the “NoClassDefFoundError” with ADT 17にまとめています。

エラー内容

今回の問題は実行時にならないと分からず、Eclipseの中では無事に整合性が取れてコンパイルが通ってしまう事が特徴です。

アプリケーションを実行するとLogCatには次のようなエラーが記録されていました。

04-25 09:51:55.080: E/AndroidRuntime(23064): FATAL EXCEPTION: main
04-25 09:51:55.080: E/AndroidRuntime(23064): java.lang.NoClassDefFoundError: com.google.ads.AdView
04-25 09:51:55.080: E/AndroidRuntime(23064): 	at net.yadiary.android.jpostal.JPostalSearchActivity.onResume(JPostalSearchActivity.java:204)

AdMobは別のディレクトリに最新版を持っていて、複数のアプリケーションからそれを参照する構成になっていました。

解決方法

外部Jarの参照を止めて、次のステップで修正を行ないます。

  • Java Build Pathから外部Jarを参照している項目を全て削除する
  • Refactorでlibディレクトリをlibsに変更する
  • 外部Jarで参照していたJarを作成したlibsにコピーする

これでEclipse上はJava Build Pathにプロジェクト内部のlibsフォルダにあるJarファイルが登録されているはずです。

この状態でアプリケーションを実行したところ、無事にアプリケーションが動き出しました。

さいごに

ADT 17.0.0のリリースノートをみると、libsディレクトリを使う事は変更点としてまとまっています。

ADT 17.0.0リリースノートからの抜粋

Added feature to automatically setup JAR dependencies. Any .jar files in the /libs folder are added to the build configuration (similar to how the Ant build system works). Also, .jar files needed by library projects are also automatically added to projects that depend on those library projects. 

つまり、必要なJarファイルはlibsディレクトリにコピーさえしてくれれば、ADTが勝手に組み込むよ、というわけです

まさか依存関係にあるJarファイルをAPKに組み込まなくなるとは思わなかったので、解決までに時間を取ってしまいました。

2012/04/17

データベースに格納されている画像データをWebページに表示する

これまでWebアプリケーションを作る際のサムネイルデータなど、サイズのごく限られた画像ファイルをBLOBデータとしてデータベースに格納してきました。こういったケースは比較的よくあると思います。 最近では比較的大きなサイズの画像ファイルでもBLOBとして格納してしまうかもしれません。

今回は比較的サイズの小さな画像データをWebページに大量に貼り付けたい場合のお話しです。

背景

これまでは、DB上にあってファイルとしてアクセスできない画像を表示するために、画像データのストリームを返す小さなWebアプリケーションを準備してきました。

これだと画像ファイルの数が多くなった場合には、画像のデータ分だけのコネクションが必要になります。 keep-aliveが使えて実際のコネクション数が参照数と同じではないとしても、同一ページに小さな画像を大量に表示する事 は全体のパフォーマンス上の懸念材料になってきました。

いままでのベストプラクティスであれば、1ページは30kbyte以下に抑えるとか、画像データの取り扱いについてもいろいろありましたが、デスクトップアプリケーションの代りとしてWebアプリを作る場合には、これまでのルールが当てはまらない場合もでてきました。

HTMLに画像データを埋め込む技術的な話し

これまでの手法だとimgタグのsrc属性に画像表示用のURLを指定してidなどで画像を指定するようにしてきましたが、直接に画像データを指定します。

データを埋め込む例

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAABLCAIAAAA06NSGAAAACXBIWXMAAABI
AAAASABGyWs+AAAACXZwQWcAAAB4AAAASwDhLqRrAAAki0lEQVR42u18a5Bc
..." />

いままで試したプログラミング言語毎の例を載せておきます。

RubyでBase64を扱う方法

いま手元にあるのはRuby 1.9.3-p125ですが、方法は共通のはずで、だいたい次のようなコードになっています。

Base64の文字列を得るのにpackを使っています。

rubyでのイメージファイルのbase64化


## この例ではオリジナルデータをファイルにしています
@filepath = "/somewhere/foo.jpg"

@prefix = "data:$MIME_TYPE;base64,"

## この戻り値をimgタグのsrc属性に指定します。
def getSrcData(data)
  ret = @prefix.sub("$MIME_TYPE", getMimeType())
  ret += [IO.read(@filepath)].pack("m")
  return ret
end

def getMimeType()
  ext = File.extname(@filepath).downcase.sub("\.","")
  ext = "jpeg" if ext =~ /jpg/i
  return format "image/%s", ext
end
JavascriptでBase64を扱う方法

以前、Google Chromeの機能拡張でExifの情報を扱うアプリケーションを作成しました。 Chromeでは同じ機能拡張の中でもメインのタブとポップアップウィンドウの間ではデータ参照が制限されているので、メインコンテンツの画像を機能拡張のポップアップウィンドウの中で表示するには、外部参照のURLを渡すか、データ列を渡すかの方法をとることになります。

その時はExifを解析するためにメインコンテンツ側でダウンロードしたデータをBase64で渡して、ポップアップウィンドウ側に表示させました。これはダウンロード回数を最小にするべきだろうという理由での判断でした。

Javascript自身にはbase64化の手段がないので、Masanao Izumoさんが公開されているbase64.jsを利用しました。

Javascriptでのbase64化


  imgtag_data_list.push("data:image/jpeg;base64,"+base64encode(EXIF.getRawData(node)));

この時はいくつかbase64化のライブラリを試しましたがテキストではなく、バイナリに対応しているのはIzumoさんのコードしか見つけられませんでした。コード中のEXIF.getRawData(node)で参照しているデータはXMLHttpRequestのresponseBodyです。

考慮点

同じ画像を大量に表示するような場合は、テキストファイルに埋め込むよりも、アプリケーション側でLast-ModifiedCache-Controlヘッダーをちゃんと扱うようにするのが最も経済的です。

安易にCGIやらで組むと動的アプリケーションの特性としてCache-Control: no-cache付く事になるので、キャッシュが有効になりませんが、これに気をつけるだけでブラウザが適切に繰り返しの読み込みを効率化してくれます。

他には画像データのサイズが比較的大きい場合は、HTMLファイルのダウンロードに時間がかかる事になるので、最初の画面描画が遅くなる欠点があります。ここ最近のブラウザはレンダリング途中から表示が始まるので、問題にならないかもしれませんが、いずれにしろサイズの大きなファイルを大量に表示するのは、もともとの設計に問題があります。

画像データの埋め込みはあくまでもサムネイルや、HTMLメールなどの用途に限定するべきでしょう。 よくよく考えて使わないと、策に溺れる事になります。

2012/04/15

pubimg: 画像ファイルを外部に公開するためのユーティリティ

このブログのコンテンツ自身はテキストファイルで、独自にXMLスキーマをRelaxNGを使って定義して、Emacsのnxmlモードでバリデーションしながら書いています。

外部参照用のURLは外部のXML定義ファイルにまとめていて、コアの機能は固まっていますが、ラベル一覧やURL一覧に効率良くアクセスする手段は模索中だったりします。

ブログだけでなく外部に公開する画像ファイルを対象に、効率良くアップロードするためのユーティティを作成しました。

pubimg本体の動き

外部に画像ファイルを公開する時に必要な作業の流れは次のようになっています。

  • サイズなどを整えた画像ファイルを作成
  • 手元のPCのあるディレクトリにファイルをコピー
  • Apache AntのFtpTaskでバッチ的にファイルをアップロード

この時にブログなどの記事を書く方では、最終的なURLの形式が必要になるので、ファイルを登録するとURLを表示するようにしています。 ここら辺はホームディレクトリの~/.pubimg.confプロパティファイルを参照して、コピー先やURLのprefixの情報等をApache AntとRubyの両方で共有しています。

このプログラムで実行している様子は次の通りです。

端末でpubimgを実行した様子

pubimgスクリプトの特徴

このスクリプトは単純ですが、次のような機能を持っています。

  • コピー先のファイル名を別に指定
  • 日付情報をprefixに付与
  • ファイルサフィックス(いわゆる拡張子)をコピー元から自動的に判別
  • sha256チェックサムで同じファイルが存在した場合には、コピーせずに既にアップロードされているURLを表示

たとえば gnome-screenshot を使った場合は、ファイル名が "Screenshot-1.png"のような名前がデフォルトになります。公開用に作成した分けではない画像ファイルであれば、オリジナルから名前を変更することが必要になります。

そこでpubimgは、第2引数にコピー先のファイル名を指定して、次のような使い方ができます。

$ pubimg Screenshot-4.png terminal_exec_pubimg
http://www.yasundial.org/images/pubimg/20120415.0.terminal_exec_pubimg.png

内容が異なるファイルを既にあるURLにコピーしようとした場合は、日付後のindexがインクリメントします。

$ pubimg Screenshot-5.png terminal_exec_pubimg
http://www.yasundial.org/images/pubimg/20120415.1.terminal_exec_pubimg.png

うっかりヒストリにあるコマンドを実行したり、別の名前で登録しようとしても、重複して登録される事はありません。

$ pubimg Screenshot-4.png hogehgoe
http://www.yasundial.org/images/pubimg/20120415.0.terminal_exec_pubimg.png

プロパティファイル

Apache antとRubyで共有しているプロパティファイルの内容は次のようになります。

プロパティファイル

local.dir = ${user.home}/.pubimg/files
remote.dir = /images/pubimg/.

ant.home = ${user.home}/java/tools/ant
java.home = /usr/java/1.6.0

url.prefix = www.yasundial.org/images/pubimg/

Apache AntのFtpTask用の設定ファイルは別の場所にあり、プロパティで指定する事もできるようになっています。

もし複数のftpアップロード先が必要になれば、読み込むプロパティファイルを切り替える仕組みをpubimg側に入れるだけです。

さいごに

これだけだとライブラリ管理はできていないので、ファイルのリストや削除は別のアプリケーションを作成する事にします。また将来的にはWindowsからファイルをアップロードしたいので、pubimgの機能はRESTアプリケーションとしても動く事になるでしょう。

pubimg自身は複数の小さいrubyクラスから出来ているのでREST側からその機能を呼ぶのは難しくないはずです。どちらかというと権限管理とか、RESTサーバーアプリからファイルを直接コピーさせるわけにもいかないので、そういう手間が増えそうな予感はします。

自宅用のアプリだし、セキュリティの懸念を考慮しなくていいならすぐに動くかな…。

2012/04/01

AndroidでJSONICライブラリを使ってみる

このブログを作成するシステムはgdataライブラリを利用して、いくつかの手製スクリプトで構成されています。

Subversionで履歴を管理していたのですが、実際にはほとんど使えないデータだったので履歴を切り離してGitに移行してしまいました。 まぁcommitしてきたログがあるので移行しても良かったのですが、気分的にはすっきりしていい感じです。

さて、新しい仕事を始めてからは.Net FrameworkやらAndroidやらiPhoneなんかのプログラミングを始めていますが、今回はJSONICというJavaでJSONデータを扱うライブラリについてです。

AndroidでのJSONデータの扱い

さて、Androidではorg.jsonパッケージがついてきて最低限のJSONデータを取り扱う環境があります。

送信用にJavaのデータ構造をJSONにする際には、特に問題なく扱えると思います。 反対に外部から受け取ったデータを扱う際にはローレベル過ぎて扱いが少しだけ面倒です。

今回はorg.jsonパッケージの上にライブラリを構築するのではなく、JSONICというJava言語用のJSONライブラリを利用する事にしました。

作成した成果物はjsonic4androidとしてGithubで公開しています。

JSONICを扱うメリット

基本的には無理に使う必要はありません。

自作の郵便番号検索システムから受け取ったJSONデータをJavaBeansとして扱うために使用しています。

この部分をorg.jsonパッケージを利用して取り出そうとして、面倒だったのは次のような点です。

  • UTF-8にエンコードするため、BufferedReaderを使い全データを変換する必要があった
  • 配列の中に各データのレコードを持っているため、要素名を指定してアクセスする処理が冗長で、JavaBeansにマッピングする必要があった
  • org.jsonパッケージを利用する自作変換クラスが安定して動作しなかった (これは自分の技量の問題です)

JSONのデータ構造は単純ですが項目数が多いため、各要素にアクセスするコードは繰り返しが多く、JavaBeansに変換してから扱うのがベストに思えたのですが、その変換コードを書くのがさらに面倒だったというのが実情です。

エントリ数が10以下で配列の中にさらにJSONデータ構造を持つような場合でなければ、org.jsonパッケージをそのまま使うのがお勧めです。

Android端末向けのプログラミングではライブラリを利用する事も作業量を減らす上では大切ですが、アプリ全体のパッケージサイズの圧縮が必要だったり、インタフェースを使わないなどのいくつかのプログラミング上のテクニックが存在します。

そういった事との兼ね合いで常にベストな選択をしてください。

パフォーマンス

jsonicでBufferedInputStreamからJavaBeansオブジェクト(JPostalBeanインスタンス)を生成した場合と、自前でInputStreamを開いてUTF-8に変換した文字列からJavaBeansオブジェクトを返すメソッドを作成して変換時間を計測して比較してみました。

jsonicを使ったコード


is = urlconn.getInputStream();
bis = new BufferedInputStream(is);
long beginTime = new Date().getTime();
ret = JSON.decode(bis, JPostalBean.class);
long endTime = new Date().getTime();

自前の変換メソッドを使ったコード (reqTextの生成時間は計測していません)


is = urlconn.getInputStream();
br = new BufferedReader(new java.io.InputStreamReader(is,"UTF-8"));
StringBuilder reqText = new StringBuilder();
...
long beginTime = new Date().getTime();
ret = parseJsonStream(reqText.toString());
long endTime = new Date().getTime();

合計で170件のデータを含むデータを処理しましたが、jsonicを利用した場合は約10%ほど高速でした。 自前ライブラリの場合には、BufferedReaderを使いUTF-8文字列を取得するところは省いていますが、それを含めると2倍近く処理速度の差が確認できました。

jsonicを使ったから特に処理が遅くなるという事ではなさそうなので、全体のバランスの中で選択すれば良いのかなと思います。

パッケージサイズの増加量

郵便番号検索アプリで作成したAPKファイルで比較するために、jsonicに依存するコードをコメントアウトしてjsonic4androidライブラリプロジェクトへのリンクを削除したAPKファイルを作成してみました。

$ ls -l JPostalSearch.*.apk
-rw-r--r-- 1 yasu yasu 469461 2012-04-01 10:38 /home/yasu/JPostalSearch.16e.without_jsonic.apk

jsonic4androidを含む、通常のAPKファイルは次のようになります。

$ ls -l JPostalSearch.*.apk
-rw-r--r-- 1 yasu yasu 499148 2012-04-01 10:45 /home/yasu/JPostalSearch.16e.apk

およそ30KB程度の増加量になり、配布について大きな障害ではなくなります。

jsonic4androidが対応するSDK(API)バージョン

作成しているアプリはAndroid 1.6 から(minSdkVersion="4")対応するようにエミュレータで動作を確認しています。

実機で確認しているのはAndroid 2.3.3 (SdkVersion="8")、及び、Android 3.2 (SdkVersion="13") です。

変更内容は次のセクションを参照してください。

jsonic4android - Eclipseプロジェクト形式での配布

今回扱ったJSONICのバージョンは1.2.10です。当初は1.2.9でしたがバージョンアップしたため、Githubのjsonic4androidも追随しました。

Githubに登録してあるのはEclipseに組み込む事を前提としたインポート可能なパッケージ形式です。

GithubのプロジェクトページのWikiにアクセスすると、画像で使い方が並んでいます。 gitでcloneしてから「Existing Projects into Workspace」を選ぶだけで、Eclipseの中で利用する事ができます。

jsonic4androidとして変更点

Eclipseパッケージとして組み込めるような形式になっている他の変更は次のような点です。

SDKのバージョンでいうと4、いわゆるAndroid 1.6以降で扱えるように、主にString::isEmptyメソッドを使わないように修正しています。

jsonicとproguardとの関係

リリースする際には署名をしますが、ここで通常はProguardを使う事になります。

開発環境ではうまく動くのに、署名したAPKファイルを配布すると問題になる場合がある原因の一つにはProguardがあります。

Proguardでの課題はテンプレートプログラミングを行なうためのGenericsとの相性です。

Proguardはパッケージ名やメソッドなどを変更してしまいますが、Java Reflection APIを扱うJSONICからはメソッドの名称が変更されてしまうと変数にアクセスする事ができなくなってしまいます。

鉤括弧でクラスを指定するGenericsの形式を使う場合には、明示的にproguard.cfgでそのオブジェクトを対象外にする必要があります。

今回はJPostalBeanクラスをjsonicのdecodeメソッドで処理しているため、proguard.cfgに次のような記述を追加しました。

proguard.cfgに追加したJSONICで処理するJavaBeans用の記述

-keep class net.yadiary.android.jpostal.beans.JPostalBean {
  <init>(...);
  *;
}

さいごに

JSONはクライアントとサーバーが1対1に対応するような軽量Webサービスを利用する場合の選択肢としては最有力だと思います。

SOAPでなければいけない場合は、ステートフルなセッションを行ない、メッセージボディを途中で分割して別々のサーバーに送信するSOPA対応Proxyを使用しなければいけない、限られた環境に限定されるでしょう。

Androidのようなスマートフォンで外部とのメッセージをやりとりする場合には自然に使う事になるJSONですが、アクセスするためにJSONICのようなデコーダとJavaBeansのデータ構造を利用しないと、JSONのデータ構造が変化する度にコードを追加、修正する手間が発生する事になります。

選択肢は沢山あるので、うまくライブラリを利用して見通しの良いコードを書くようにしたいものです。

2012/03/22

EXIF Geotag Checker Chrome機能拡張を作った時の変更点まとめ

画像ファイルにGPSの位置情報が入っていたら気持ち悪いなぁと思って、いくつかChrome機能拡張を探してみました。 ちゃんとExif情報を表示するものはいろいろあったのですが、その中からプライバシー情報を見つけるのは面倒で、汎用的なViewerはあったのですが、適当なものがみつかりませんでした。

Exif Geotag Checker Window

EXIF情報が常に悪いかというと、写真なんかを扱う人であれば、管理上は間違いなく重要な情報で、画像ビューアーがいつの間には画像を上書き保存していてEXIF情報が失われたと知れば怒る人もいるでしょう。

iPhoneであれば画像をメーラーで添付する時にEXIF情報は削除してくれますが、iPhoto経由なんかで普段とちょっと違う方法で画像をアップロードしたらGPS情報がばっちり残っている可能性もあります。

今回は思い切ってEXIFの数あるエントリの中から、GPSだけ、それも時刻、緯度、経度情報の3点だけを表示するツールを作成してみました。 EXIFの時刻情報のDateTimeOriginalもみるべきなのかもしれませんが、それはツールが勝手に改竄したとかいう主張も通りそうに思えるので今回は省きました。

興味のある方は、Gitoriousでコードをforkして機能を追加してみてください。

今回作成した機能拡張の機能そのものもはライブラリに頼っていますが、それ以外の基本的なところでいろいろ壁にぶつかったのでまとめておくことにしました。

作成したChrome機能拡張について

DateTimeOriginalについてのところで書きましたが、今回はGPS情報の3点だけを表示するようにしました。 EXIFのコメント欄にメールアドレスなんかが入っている可能性もあります。
とはいえ、全部をチェックするのは時間がかかる上に、現実的には問題が発生する可能性は低そうです。

それが必要であればEXIFのViewerを使ってください。

制限事項

今回作成したアプリケーションは、Ajaxなどでブラウザに動的に表示している画像には対応できません。

その他にもいくつか制限はあるものの、本格的なExifViewerはむしろ余計に混乱するので、いくつかの技術的なポイントの解決策の模索と合わせて、ニッチな需要を探してみる事にしました。

技術的なポイント

機能的の中心になるEXIF情報の取得には、既に公開されている外部ライブラリを使っています。 README.mdにも含めていますが、中心となる機能部分にはJacob Seidelinさんのexif.js(+binaryajax.js)を使用しました。

この他にもContent Scripts側での非同期処理を扱うために、jsDeferredライブラリ(jsdeferred.nodoc.js)も使用しています。

いろいろライブラリは使用しましたが、最終的にはexif.jsとbinaryajax.jsには、次のような変更を加えています。

  • IEに特化したコードをロードする個所のコメントアウト
  • 起動時の自動ロード(loadAllImages)の停止
  • exif属性のあるimgタグだけを対象にしていた処理のスキップ
  • 画像データを入手するためにメッセージボディ全体を入手するコードの追加

作業の中心はEXIFについてではなく、できるだけドキュメントのDOMの影響を受けないようにしたり、非同期呼び出しの内部構造作成に時間を取るようにしました。

取り組んだいくつかの課題

開発の前の調査段階と開発初期の試行錯誤の時期では次のような、課題を感じました。

Chrome機能拡張に固有な課題

画面に表示されているデータはDOMとして入手が可能ですが、Chromeで直接にDOMを操作するためにはContent Scriptsと呼ばれるコードインジェクションの手段を使う必要があります。 BackgroundタイプやBrowser Actionタイプからは非同期(コールバック)通信の仕組みを利用して、その結果だけを受け取る事になります。

またContent Scriptsは、画面に表示されているドキュメントの構造(DOM)の影響を受けます。 例えば、Content Scriptsを使って画面上にjQuery UIのポップアップウィンドウを表示させようとすると、コンテンツが既にロードしていたCSSの影響などを受ける可能性があります。

他のExifViewerを使って感じた課題

EXIF Viewerと呼ばれるようなアプリケーションをいくつか触ってみた時に、画像を解析するために外部のCGIを呼んでいるものがありました。

Browser Actionであるポップアップウィンドウから画像データにアクセスするのは面倒なのでリーズナブルな方法ではありますが、データを送信したCGIから画像データにアクセスするため、外部に公開されているサイトでないと正常に動かない課題があります。

このため、NATの中にいる自宅やイントラネットなどからはうまく動かない事になります。 また場合によっては画像が外部サイト上にキャッシュされる事を問題視する事もあるでしょう。

解決するべき課題

ざっと感じた点をまとめて、以下のようなポイントに注意して作業を進めました。

  • 外部サイトのCGIなどに頼らず、Chromeブラウザで完結した処理を実現する
  • アドレスバーの横にアイコンを出し、ポップアップした画面に結果を表示する
  • 問題があるか、ないか、ひと目で判断できるようにする

とはいえ画像データを2重にダウンロードする課題はあります。そのためJPEG画像のみをロードするようにPNGやGIFは無視する仕組みを入れています。そのためにexif.jsにもともとあった、読み込む対象をexif属性で指定する処理と、全ての画像データにアクセスする処理は動かないようにしています。

その他にも、ポップアップ画面で画像を表示させる時にもBasic認証が必要なページや相対URLから絶対URLへの変換といった面倒な処理への対応が必要になります。

こんな感じで、解決するべき課題はありました。 しかしなんとか、荒削りですが、一通り動かすことができるものを作る事ができました。

作成したコードはGitoriousから入手する事が可能です。

また機能拡張自体はChrome Webストアから入手する事ができます。 [ EXIFジオタグチェッカー ]

Browser Actionとしてポップアップ画面からコンテンツの画像を表示する方法

Chrome機能拡張についていえば、アクセスしているタブページとBrowser Actionでアドレスバー横のボタンを押して表示されるポップアップ画面とは完全に独立しています。

この環境でポップアップ画面に画像データを表示するためには、imgタグのsrc=...に次の2つの方法で画像ファイルを指定する必要があります。

  • httpから始まるURLで画像ファイルの場所を指定する
  • Base64でエンコードされたデータそのものを指定する

データそのものを指定する方法は、情報量が増えるので普通は使いません。

どちらでも良さそうだと思ったのですが、認証が必要なページの画像をURL指定でポップアップ画面からアクセスした時には認証ダイアログは出ずにアクセスが拒絶されました。 そんな分けでBase64を使って、タブページから画像データの情報そのものを入手できるようにContent Scriptsを作成しました。

扱うデータの量が増えたので、少し負荷テストらしき事をしてみました。 この段階ではSpeed Tracerのために開発版のChromeを使う必要があって準備していないので、細かい時間の測定は後回しにして、印象だけで書いています。

負荷テスト1

バックグラウンドでexif情報をチェックするスクリプトを動かして画面のレンダリングに異常がないか、確認しました。

テストのためにWebサイトの画像作成用に持っているイメージ素材集のJPEGイメージをいくつか使い、一つのindex.htmlの中にimgタグを2600以上配置させる負荷テストを行ないました。

この中にgeotag情報を持ったJPEG画像を配置しましましたが、時間がかかったものの、ローカルのWebサーバを経由させた事を考えても、思ったよりも早いスピードで処理を行なう事ができました。

負荷テスト2

次にGPS情報を持ったJPEGファイルのコピーを100件作成し、index.htmlに100件のimgタグを書き込んで、このファイルをロードするテストを行ないました。

この記事を書きながらテストしている今は、結果を表示するまでに1、2秒程度のタイムラグがあります。 デフォルトのページの内容が更新されないと、読み込み中なのかどうか、そのステータスを表示することができていません。

機能拡張を入れることでJPEGデータのロードは2倍発生するので、その分の処理時間は単純に倍かかります。 その予測とほぼ同じ変化がある事はわかりました。

ブラウザが表示した画像データのキャッシュにアクセスできれば良いのですが、そうするとCaptchaや乱数表のようなアクセスする度に変化する事でセキュリティを保っているようなアプリケーションの脅威になってしまうので、永遠にそんな事はできないでしょう。

そんな分けで、JPEGファイルについて再読み込みを許可、あるいは予測していない場合には、JPEGファイルに2回アクセスする事で問題が発生する可能性はあります。

今後Chrome機能拡張を作成する時に気をつけること

今後も気が向けば機能拡張を作ったり、現状のアプリケーションに変更を加える事もあるでしょう。

タブに開いているコンテンツ(のDOM)にアクセスするタイプのアプリケーションは、その解析にContent Scriptsを使い、そのデータの取得に非同期(コールバック)通信の仕組みを使う事になります。

そういった全体の構造を考えて、どういうコードをどこで使うか、そういう判断というか、全体の構成と楽をするための手段について検討することが必要でしょう。

JavaScriptを使う時にはライブラリを使わなくとも似たような処理はいろいろな方法で可能になります。 しかしコードが長くなって読み難くなってしまうところが難点だと思っています。

苦労すれば最終的に同じ(ような)機能の実装はできるけれど、それを高いメンテナンス性を保って作る事は、相当な知識と面倒な作業が発生するでしょう。JavaScriptの手軽さと難しさはそういうところにあると思います。

余談ですが、JavaScriptを学校で教えるべき言語に上げる人は多いそうですが、かなり特定の問題領域(ドメイン)に特化した知識+経験になりそうな点が心配です。

情報系の大学であれば、同じものを別の角度でみるためにも、JavaとScalaを合わせて教えて、同じ機能を別々の方法で実装させたりするのが、時間はかかっても最終的には良いJavaScriptプログラマを生み出すような気がします。

創造性っていうのは応用力の積み重ねでしかないので、思ったものが作れれば何でもいいんですけどね。

JavaScriptの需要が多いのは確かですが、最初からJavaScriptだと、苦労をしてもJavaScriptしか使えない人間になりそうに思えるのが怖いところです。

2011/04/06

Visual Studio 2005でCrypto++ライブラリを使ってみる

手元のVisual Studio 2005はVB2005を使って、以前働いていた職場でプログラムを作る他はもっぱら自分の遊びようでしたが、今回はひさしぶりにVC++2005を使ってみる事にしました。

いまどきVC#じゃなくて、VC++を使ったのはIPropertySetStorageを使うプログラミングをするためにC#を使うのは大変でオーバーヘッドが大きくメリットがなかったからです。

IPropertySetStorageの使い方はいろいろ資料があるので、今回はそれと一緒に組み合せたCrypto++ライブラリの使い方についてメモを残しておきます。

Visual C++ 2005と組み合せる方法

www.cryptopp.com には、VC++との組み合せについて参考になるドキュメントはみつかりませんでした。

そこでGoogleでいくつか検索して参考にしたのは mogproject.blogspot.com の「Crypto++ 5.6.1のビルド」でした。

入手できるcryptopp561.zipを展開したディレクトリに含まれているslnファイルをVS2005から開きました。

この全体をDebug用とRelease用の2つのタイプに分けてビルドした後は、ひとまず閉じて、自分のプロジェクトファイルを開き直します。

使い方 - cryptopp.dll OR cryptlib.lib

どちらのファイルを使うにしても、ヘッダーファイルが含まれている、アーカイブを展開したフォルダのトップを「追加のインクルードディレクトリ」に含めておきます。

DLLを使う場合には、リンカオプションでDLLを含むディレクトリへのパスを通しておき、"dll.h"をインクルードするだけで直接 cryptopp.dll ファイルの名前を指定する必要はありません。

cryptlib.libを使う場合には、「追加の依存ファイル」に"cryptlib.lib"を含めておきます。

基本的にはこれだけだったのですが、デフォルトの設定で動かしていると次のような問題が発生しました。

自分のプロジェクトからCrypto++を使う時の問題

ビルドしたところ、DLLはうまく作れたけれど、LIBファイルとのリンクには失敗したという事が起こりました。

遭遇したエラーメッセージ

1>msvcprt.lib(MSVCP80.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::~basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(void)" (??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ) は既に cryptlib.lib(iterhash.obj) で定義されています。
1>msvcprt.lib(MSVCP80.dll) : error LNK2005: "public: __thiscall std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >(char const *)" (??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@PBD@Z) は既に cryptlib.lib(iterhash.obj) で定義されています。
1>LINK : warning LNK4098: defaultlib 'LIBCMT' は他のライブラリの使用と競合しています。/NODEFAULTLIB:library を使用してください。
1>C:\Users\yasu\Documents\Visual Studio 2005\Projects\TestCryptoPP\Release\TestCryptoPP.exe : fatal error LNK1169: 1 つ以上の複数回定義されているシンボルが見つかりました。

これは「ランタイムライブラリ」の設定で「マルチスレッドDLLl(/MD)」が選択されていた事が原因です。

リンクする対象がDLLかスタティックライブラリかによって、オプションを変更しなきゃいけないのは理解できますが、どこを変更すれば良いのかは調べるのに時間がかかりました。

/MD, /MD等のメニュー

ランタイムライブラリ切り替えダイアログ

DLL v.s. スタティックライブラリ

簡単な文字列のSHA1 Digestを計算して、表示するアプリケーションを作成して比較しました。

DLLの場合は、DLLファイルが1.2MB程度で、オブジェクトコードは7KBほど。

スタティックライブラリの場合は、オブジェクトコードは88KBほど。

とりあえず自分の用途では、スタティックライブラリの方が扱いは楽かなぁと感じています。

Crypto++を扱う上でのリファレンス、マニュアル等

Googleなんかで検索すると、Crypto++のドキュメントがなさすぎるという記述を目にします。

確かにドキュメントやチュートリアルは少なくて、基本的な操作を知っている既にプログラマな人以外お断り的な雰囲気は感じます。

ただ、どういう風にメソッドやクラスを使えばいいのかというと、サンプルが付いてくるので、cryptestの中を除くとだいたい必要そうな情報はあるように思いました。

ちゃんと使っているわけじゃないので、なんともいえないですが、典型的な操作についてはテスト用のコードを追えばなんとかなりそうな気がします。