2012/09/24

Android:隔離プロセス上でServiceを実行する~isolatedProcess~


JellyBeanからServiceを隔離プロセス上で実行できるようになりました。

Serviceを隔離プロセス上で実行するにはAndroidManifestのserviceタグでisolatedProcess属性にtrueを指定します。
# デフォルト値はfalse
http://developer.android.com/guide/topics/manifest/service-element.html#isolated
<service ...
    android:isolatedProcess="true" />

●PIDとUID

実際にこのサービスを実行すると、呼出し元とは別のPIDで起動しているのがわかります。
# ps
USER      PID   PPID  VSIZE  RSS   WCHAN    PC         NAME
...
u0_a47    1068  37    171612 34304 ffffffff 40033a40 S yuki.test.isolateservice
u0_i5     1083  37    170200 28668 ffffffff 40033a40 S yuki.test.isolateservice
...
隔離プロセス上ではUIDも異なります。

isolatedProcess=false(同プロセス)でサービスを呼び出した場合
caller  UID=10047 / PID=1198
service UID=10047 / PID=1198

isolatedProcess=true(隔離プロセス)でサービスを呼び出した場合
caller  UID=10047 / PID=1068
service UID=99006 / PID=1083
隔離プロセスのUIDには99000~99999が割り当てられます。
参考:android.os.Process.FIRST_ISOLATED_UID / LAST_ISOLATED_UID


●隔離プロセスの制限

隔離プロセス上で実行できる動作には厳しい制限があります。

【動作制限の例】
  • ContentResolverを操作できない
  • BroadcastIntentを送信できない
  • BroadcastReceiverを登録できない
  • 自プロセスのメモリ情報を取得できない
  • その他色々...

この辺の動作制限は、主にActivityManagerServiceのenforceNoIsolatedCaller()でチェックしています。
http://tools.oesf.biz/android-4.1.1_r1.0/xref/frameworks/base/services/java/com/android/server/am/ActivityManagerService.java#2240
隔離プロセスから制限有りのAPIを呼ぶとエラーが返されます。

エラーメッセージ「Isolated process not allowed to call ***」

・BroadcastIntentの送信時
java.lang.SecurityException: Isolated process not allowed to call broadcastIntent
  ...
  at android.content.ContextWrapper.sendBroadcast(ContextWrapper.java:312)
  at yuki.test.isolateservice.IsolatedService.onHandleIntent(IsolatedService.java:14)

・ContentResolver経由でクエリ発行時
java.lang.SecurityException: Isolated process not allowed to call getContentProvider
  ...
  at android.content.ContentResolver.query(ContentResolver.java:313)
  at yuki.test.isolateservice.IsolatedService.onHandleIntent(IsolatedService.java:18)

●隔離プロセス上にあるサービスとの通信

隔離プロセス上にあるサービスとの通信はstartやbindといった基本APIに限られます。
バインド状態にあるサービスの独自APIをコールすることはできません。

サービス接続時に渡されるIBinderにはBinderProxyインスタンスが格納されています。
private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        LocalBinder binder = (LocalBinder)service;  // ClassCastException発生!
    }
そのため、サービスインスタンスが取得できません。


●隔離プロセスの使いどころ

ドキュメントとして、使用すべきシーンやガイドラインが見当たりませんが、、、
最小権限の原理と、サービスが基本的にバックグラウンドで動作するという特性から、
セキュリティに関連するアップデートと思われます。
隔離プロセスを応用したベストプラクティスがあれば新たに記事を投稿しようと思います。

以上です。

2012/09/02

Android:TimingLoggerで処理間隔をログで出力する

TimingLoggerというクラスがあります。

このクラスを使用すると、処理間隔をわかりやすくログ出力できます。
実際にコードと出力イメージをみたほうが理解できます。

サンプルコード
TimingLogger timings = new TimingLogger("tag", "method");
// work a
timings.addSplit("work a");
// work b
timings.addSplit("work b");
// work c
timings.addSplit("work c");
timings.dumpToLog();
ログ
D/tag     ( 3459): method: begin
D/tag     ( 3315): method:      5 ms, work a
D/tag     ( 3315): method:      3 ms, work b
D/tag     ( 3315): method:      12 ms, work c
D/tag     ( 3315): method: end, 20 ms
TimingLoggerはスレッドセーフに設計されていません。
TimingLoggerで使える主なメソッドは下記。

TimingLogger(String tag, String label)
コンストラクタ。
dumpToLogでの出力結果に、ログタグtagと計測対象のメソッド名labelを設定します。

addSplit(String splitLabel)
計測に"マーカー"となるラベルを設定します。
dumpToLogでの出力結果に、前回マーカーポイントからの経過時間を表示させます。

dumpToLog()
計測した結果をログ出力します。

reset()
計測情報を全てリセットします。
addSplitで設定したマーカー情報もリセットされます。
コンストラクタで指定したtagとlabel情報はそのまま残ります。

reset(String tag, String label)
計測情報に加えて、コンストラクタで設定したログタグとメソッド名情報をtag,labelで上書きます。

参考:http://developer.android.com/reference/android/util/TimingLogger.html

以上です。