2012年7月30日月曜日

TimeInterpolatorを使ってかんたんEasing。

Easingというのはスクロール時の変化量にアクセントを付ける関数のことで、これがあるのとないのとではUIの気持ちよさが全然変わる。

iOSと比べると、androidは組み込みのEasing関数が充実しているんだけど、あまり有効活用されているイメージがない。

----

Easing関数を扱うインターフェースであるInterpolatorは、基本的にアニメーションフレームワークの一部であって、自作View内の動きにちょっとEasingを効かせたい、というような用途には使えなかった。

しかしandroid3系からは、TimeInterpolatorにgetInterpolationというpublicメソッドが加わった。これは現在の経過時間を0~1.0fで入力すると、Easingによる補完結果を0~1.0f(Overshootなど1.0fを超える値が返ることもある)で返してくれるという超お手軽メソッドである。

private final TimeInterpolator interpolator = new DecelerateInterpolator();
private int alpha 255;
//ScheduledExecutorServiceでの定期処理を想定
@Override
public void run() {  
 alpha -= 4;
 size = 120 * interpolator.getInterpolation(1.0f - alpha/255.0f);

 if(alpha < 0){
  alpha = 255;
 }
 //ハンドラでview#invalidateを呼んでる。
 handler.sendEmptyMessage(0);
}

テキトウなコードだけど、これでEasingできてしまう。動きが気に入らなければInterpolatorを適当なのに変えるだけでいい。複雑なアニメーションロジックは隠蔽され、再利用によってお手軽に気持ちいい動作を実現できる。すばらしい。

しかし3以降限定なので、まだまだ主流の2系をハブることになってしまう。

----

android2系にはgetInterpolation()がないのか?っていうと実はそんなことはなくて、実はInterpolatorインターフェースで宣言されていました。

PropertyAnimationの導入と共にアニメーションフレームワークが書き換えられて、getInterpolation()はInterpolatorからTimeInterpolatorへ、代わりにInterpolatorがTimeInterpolatorを継承することで、今までのコードも互換性を保つように変更されました。

ですので、newした実装クラスをInterpolatorでキャストして、Interpolatorインターフェース経由で操作すれば、android2系でも動くはずです。たぶん。

----

Scroller(フリック時のスクロール量をエミュレーション)とか、Interpolatorみたいなのはフレームワーク内部のクラスだからほとんど話題にされてるのを見ないけど、実は外部からも普通に使えて、意外と便利だったりする。

2012年7月27日金曜日

androidで嘘の位置情報を端末に伝える(擬似ロケーション)

エミュレータの場合

DDMSのエミュレータコントロールのLocation Controlsから、擬似座標を送信することができるので非常に簡単です。Devicesでエミュレータを選択しないとアクティブにならないという点だけ注意。

KMLというXMLベースの記述法で、位置情報のリストを作成し読み込むことができます。これにより複数の位置情報のテストも簡単に行えます。

KMLとはKeyhole Markup Languageの略称です。KeyholeとはGoogle Earthの昔の名前のことです。なので、Google Earthでピンを立てた場所を、KMLとして出力することができます。

ただしGoogle Earthが出力するフォーマットは、DDMSが認識するフォーマットより新しいのか認識しなかったり、マルチバイト文字が含まれると文字化けや読み込み不良を起こす場合があります。

うまく読み込めなかった場合は、Google Earthが出力したKMLをシンプルに整形しましょう。coordinates要素内に、Longitude、Latitude、heightのの順に、コンマ区切りで書けば認識します。順序が緯度経度、ではなく経度緯度なところだけ注意。

実機の場合

実機で擬似位置座標を送る場合は、専用のロジックが必要になります。実装したアプリケーションはマーケットを探せばありますが、以下にどうやって擬似ロケーションを発生させるアプリを作るのか述べます。調べ中の要素があるので以下の記述は不正確な恐れがあります。というか、不正確だと思った方がいいです。

開発者オプションの設定

まず、開発者オプションにて「擬似ロケーションを有効」にします。

「擬似ロケーションを有効」にしない場合、以下に述べる方法で偽の位置情報を発生させても例外が発生して成功しません。

「擬似ロケーションを有効」の設定は、アプリケーション内から確認することができます。Settings.Secureを使います。詳細はhttp://d.hatena.ne.jp/terurou/20100930/1285814213をどうぞ。onResume()でこの設定を確認することで、位置情報偽装対策を行うことができます。(ちゃんと作られているアプリは、この対策を行っているはず)

マニフェストの設定

擬似ロケーションを生成したいアプリのマニフェストで、ACCESS_MOCK_LOCATIONのパーミッションを取得する必要があります。(ACCESS_FINE_LOCATIONもいるかもしれません。)

ACCESS_MOCK_LOCATIONは擬似ロケーションを生成するためのパーミッションです。「エミュレータで擬似情報を送信するためのパーミッション」という記述を見かけたことがありますが、DDMSから位置情報を送信するのにパーミッションは必要ありません。

LocationManagerの設定

LocationManagerをシステムサービスから取得し、addTestProviderで擬似ロケーションプロバイダを作成します。

引数が大量にありますが、重要なのは第一引数のnameだけだと思います。ここに使うプロバイダ名(GPSプロバイダのモックならば、LocationManager.GPS_PROVIDER)を渡します。

name以外の引数は、ネットワークや衛星や電話回線の使用、電力コストや正確性などのプロバイダの属性を意味しています。これらは複数のロケーションプロバイダが存在するときに、端末がどのプロバイダを優先的に使うのか判断するために存在するようです。

とりあえず真理値の部分は全部falseでも動きます。消費電力と正確性はCriteriaクラスのintの定数になので、CriteriaのReferenceを参照してください。

これでLocationManagerに擬似ロケーションのプロバイダが設定されました。

Locationの生成と設定

嘘の位置情報となるLocationオブジェクトを作ります。任意の座標をセットしてください。

そしてLocationManagerのsetTestProviderLocationに、設定したnameと、作成したLocationを渡してセットします。

擬似座標情報の取得

あとは通常の手順で位置情報を取得します。

requestLocationUpdatesに、同じくnameと、位置情報取得間隔の精度を指定するminTime、minDistance、あとはコールバックなどを設定します。

minTimeは最低再取得間隔です。本来は電源節約などのために設定するものですが、0ミリ秒で問題ないと思います。サービスなどで長期に渡って擬似座標を送信するプログラムを作る場合は、大きな数字を与えてください。

minDistanceは最低再取得距離です。単位はメーターで、直近で取得したポイントから指定したメーター以上動かないと、LocationListenerのonLocationChangedが呼ばれません。0の場合は移動なしで呼ばれるかというと、端末によるのかもしれませんが、Nexus Sでは全く同一の座標だと一度しかonLocationChangedは呼ばれませんでした。

繰り返し擬似座標を生成し続ける必要がある場合、擬似乱数でLocationにほんのわずか揺らぎを与えながら、ループなりハンドラで繰り返しrequestLocationUpdatesを呼べば問題ないと思います。

onLocationChangedの引数で受け取るLocationの値が、擬似ロケーションとして与えたダミーの値と一致していれば成功です。

後片付け

最後にremoveUpdates、removeTestProviderで擬似ロケーションプロバイダを取り除きます。

コード

以下は参考になるか不明なコード抜粋。

LocationManager manager = (LocationManager)getSystemService(LOCATION_SERVICE);
manager.addTestProvider(LocationManager.GPS_PROVIDER, false, false, false, false, false, 
 false , false, android.location.Criteria.POWER_LOW, android.location.Criteria.ACCURACY_FINE);

//本当はこの辺は別スレッドでループ回してた。
//別スレッドからLocationManagerを操作する場合、ハンドラを経由するか、
//Looperを渡すオーバロードメソッドを使用します。
Location location = new Location(LocationManager.GPS_PROVIDER);
location.setLatitude(TEST_LATITUDE);
location.setLongitude(TEST_LONGTUDE);
manager.setTestProviderLocation(LocationManager.GPS_PROVIDER, location);
manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);

manager.removeLocationUpdates(LocationManager.GPS_PROVIDER);
manager.removeTestProvider(LocationManager.GPS_PROVIDER);

余談

位置情報を使うアプリを作る場合、位置情報を取得するクラスはインターフェースにしておくとテストが楽だと思います。

そうすれば手の込んだことをしなくても、モックのLocationListenerにテストしたい座標を返却させるだけで済むような気がします。

2012年7月26日木曜日

root化周りのメモ。

root化

root化という表現は端末が進化するみたいな感じがあるけど、実際は単に端末の管理者権限を取得するということ。

ブートローダー

内臓メモリからOSを起動させるプログラム。

本来コンピュータにおいてメモリなど記憶装置を操作するのはOSの仕事です。しかしOSが未起動の(メモリにロードされていない)状態では、「メモリを操作するOS自身が、OSをメモリにロードする」という矛盾が発生してしまいます。

なのでOSを起動させるための専用のブートローダーが付属しています。

ブートローダーのアンロック(解除)

ブートローダーはその目的から、「特定のOSをメモリから起動する」ことに特化した、書き換え不可能な(ロックされた)ものとして提供されるが、一部端末ではその制約を解除し、ユーザー自身の手で任意のブートローダーやOSイメージを書き込むことができるようになる。

OSイメージ(ROMイメージ)

端末に書き込まれているOSのイメージはしばしばROMイメージと呼ばれる。ユーザーの手によってカスタマイズされたOSイメージは、カスタムROMと呼ばれ、それに対してメーカー公式のものを純正ROMと呼んだりする。

ROMという呼び名はOSイメージをRead-Only-Memoryに書き込んでいた時代に由来する誤用で、正確にはファームウェアと呼ばれるべきものだと思う。

リカバリイメージ

通常のOSイメージがアップデート時などに破損したときなど、万が一に備えて復旧作業を行うために用意されている別系統のファームウェアのこと。

ClockworkMod Recovery

カスタムのリカバリイメージ。純正のリカバリイメージとは異なり、既存のOSイメージのバックアップや、カスタムOSイメージの書き込みなどを支援する機能を持つため、OSイメージの書き換えなどを行う人たちに重宝されている。

2012年7月24日火曜日

androidのグループIDがよく分からない。

プリファレンスを操作するコードを書いてて、複数のデータをちゃんと扱えているかテストするのに楽しようとcpでコピーしたら、ファイルの所有ユーザーID/グループIDはrootになってしまい、データを読み出せずにテストに失敗して原因調査に余計な時間を使うハメに…。

ちなみに実機でdata/data領域を操作するにはroot化してsuを入れないとダメです。cpなどのコマンドもマーケットからBusyBoxを導入する必要があります。

BusyBoxを導入すればユーザーIDを操作できるchownコマンドも使えるようになるので、そのアプリが生成したほかのファイルを見て、同じアプリID(アプリ毎にユニークな^app_\d+$みたいなID)をユーザーIDとして指定すれば、ちゃんと読み込んでくれます。

グループIDも、ユーザーIDと同じアプリIDが設定されていたので、chgrpコマンドで指定してみたのだけど、こちらはエラーが出て失敗。そんなグループIDは存在しない、らしい。ちゃんと設定されてるファイルもあるのに。

----

androidのシステムは存在しないはずのグループIDをどうやってファイルに付与してるの?わたし、気になります。

Linux自体詳しくないからそこから調べたんだけど、本来ユーザーやグループは/etc/userや/etc/groupで管理されているらしく、そこにアクセスすればユーザー一覧、グループ一覧を見れるはずが、androidはそれらは存在しないと言われてしまう。

セキュリティのためなのだろうか?

androidの本体のソースコードから、

/system/core/include/private/android_filesystem_config.h

を見ると、android_id_info構造体という形で定義されている初期のUIDとGIDのリストを参照することはできる。ここで読み込まれた構造体の値を、/etc/userや/etc/groupの代わりに渡すスタブが存在するらしい。

/bionic/libc/bionic/stubs.c

これがスタブの役割を果たしているコードなのかな?

全然謎が解けない。

----

もう少し調べてみる。

ごく基本な話として、LinuxにおいてユーザーIDはユーザーに対して付与されるけど、androidではアプリに対して付与される。

Context.MODE_PRIVATEでプリファレンスなどを生成するということは、アプリに対して付与されたユニークなユーザーIDでのみ操作することを許可する、ということ。これがandroidのセキュリティの基礎になっている。

では複数のアプリでファイルを共有する場合にはどうしているのか?ここでグループIDを使っているのではないか?と思うところだけど、そんなことはなかった。

複数のアプリでMODE_PRIVATEなファイルを共有したい場合は、それぞれのアプリのandroidmanifestのsharedUserIdに任意の同じ文字列を付与すればよい。ここで指定したidがユーザーIDになるわけではなくて、やっぱり^app_\d+$というユーザーIDになるんだけど、同じ証明書でかつ同じsharedUserIdを持つアプリの場合は、同じユーザーIDが割り当てられる。

複数アプリで同じユーザーIDを共有できるので、同じプロセスでアプリを立ち上げることができ、MODE_PRIVATEなファイルも共有することもできる。

結局androidにおけるグループIDは何なんだろう。どこで使ってるんだろう。

----

androidの巷のセキュリティ本を見ると、「androidはLinuxベースなのでユーザーIDとグループIDを使っている」と書いてあるけど、android_filesystem_config.hで定義されてるシステム周りのファイルを除いては、グループIDの存在意義があるとも思えないし、ユーザーの手が届く領域については「ユーザーIDだけで管理されている」とみなしていいような気がするんだけど、どうなんだろう?

アプリがインストールされた時にユーザーIDを付与してるコードを探し出そうと思うけどもう気力が尽きたー。

Process.javaからnativeのC++コードからgidやuidを読み込んでいるってのは分かったけど、何の役にも立ちそうにない。

javaコードでもグループIDを読み出せるようにしているってことは、きっとどこかで使ってはいると思うのだけど…。

2012年7月9日月曜日

GAE/Jにおけるmemcacheの使い方

もうGAEも公開から数年経って既に使いこなされてるツールだと思うから、もっとちゃんと調べれば分かるレベルのことだとは思うんだけど。

いまいちmemcacheの使い方が分からない。

datastoreへのアクセス頻度を減らすために使うとして、まずsetKeysOnly()でキーだけ取り出すクエリを発行して、memcacheにキーをもとにデータを保持してるか問い合わせて、なければdatastoreにキーのリスト渡してエンティティを取り出す…という感じなんだろうか。

keyOnlyとエンティティ込み、datastore問い合わせとmemcache問い合わせの効率比がよく分からないからなんともいえないけど、本当にこのやり方は効率いいんだろーか。その辺ちゃんと調べないとダメだねえ。

最新記事とかアクセス頻度の高い記事に山を張って、memcache使う処理を組み込む感じのがいいのかなー。

うーん…。