2013/02/18

Android:Service.dumpでサービスの状態をダンプする

Serviceにはdumpメソッドが用意されています。
これは下記コマンドが実行された時に、サービスの状態をダンプするメソッドです。
$ adb shell dumpsys activity service <yourservicename>
Service.dump
http://developer.android.com/reference/android/app/Service.html#dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[])

dumpメソッドにはPrintWriterインスタンスが引数として渡されます。
これにサービスの情報を入力すれば、ログとしてそれが出力されます。

・動作サンプル
$ adb shell dumpsys activity service yuki.test.messenger/.MyMessenger
MyMessenger.java
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] options) {
    pw.println("MyMessenger dump!!!");
}
-出力結果-
SERVICE yuki.test.messenger/.MyMessenger 41fa2ef0 pid=6151
  Client:
    MyMessenger dump!

Service.dumpメソッドを提供することで、開発者はサービスの情報を好きなタイミングで取得することが可能です。
※ただし、対象のサービスが実行中であることが前提です
ActivityManagerServiceもこのメソッドを提供しています。
ActivityManagerServiceをdumpすればデバッグ時に有益な情報を確認できます。
⇒http://yuki312.blogspot.jp/2012/04/androidactivity.html

Object.toStringのそれと同じく、Service.dumpは非常に有益なメソッドです。
toStringをオーバライドする際の一般契約と同じように、dumpする内容は"簡素であり、読みやすく、有益な表現"であるべきでしょう。


●サンプルコード

Service.dumpの第三引数 options にはコマンド引数が格納されます。
$ adb shell dumpsys activity service yuki.test.messenger/.MyMessenger -a -b -c
であれば
 options[0] = "-a"
 options[1] = "-b"
 options[2] = "-c"
といった具合です。

これらを活用すれば、必要に応じたダンプ情報を得ることができます。
また、よくある"ヘルプコマンド"も実装可能です。

下記はそのサンプルコードです。
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] options) {
    boolean dumpAll = false;
    boolean dumpClient = false;
    boolean dumpMessage = false;
    boolean dumpState = false;

    int optionIndex = 0;
    while (optionIndex < options.length) {
        String option = options[optionIndex];
        if (TextUtils.isEmpty(option) || option.charAt(0) != '-') {
            break;
        }
        optionIndex++;

        if ("-a".equals(option) || "-all".equals(option)) {
            dumpAll = true;
        } else if ("-c".equals(option) || "-client".equals(option)) {
            dumpClient = true;
        } else if ("-m".equals(option) || "-message".equals(option)) {
            dumpMessage = true;
        } else if ("-s".equals(option) || "-state".equals(option)) {
            dumpState = true;
        }  else if ("-h".equals(option) || "-help".equals(option)) {
            pw.println("MyMessenger dump options:");
            pw.println("  [-a] [-c] [-m] [-s]");
            pw.println("    a[ll]: dump all info.");
            pw.println("    c[lient]: reply target.");
            pw.println("    m[essage]: handle message log.");
            pw.println("    s[tate]: service state change log.");
            return;
        } else {
            pw.println("Unkown argument: " + option + "; use -h for help.");
        }
    }

    pw.println("Service state");
    pw.println("    Service create time is " + new Date(mOnCreateTime));
    pw.println("    " + mCurrentState);
    pw.println();

    if (dumpClient || dumpAll) {
        pw.println("Client(" + mClients.size() + ")");
        for (ClientRecord cr : mClients.keySet()) {
            pw.println("    " + cr);
        }
        pw.println();
    }

    if (dumpMessage || dumpAll) {
        pw.println("Message Logs");
        for (MessageLog log: mMessageLogs) {
            pw.println("    " + log);
        }
        pw.println();
    }

    if (dumpState || dumpAll) {
        pw.println("ServiceState Logs");
        for (ServiceState.ServiceStateLog log: mCurrentState.getStateLogs()) {
            pw.println("    " + log);
        }
        pw.println();
    }
}
-出力結果-
・引数無し
$ adb shell dumpsys activity service yuki.test.messenger/.MyMessenger
SERVICE yuki.test.messenger/.MyMessenger 41fd0950 pid=6151
  Client:
    Service state
        Service create time is Sun Jan 06 03:58:13 GMT+00:00 1980
        Current service state is RUNNING
・引数指定(-c:クライアント情報出力, -m:メッセージ履歴出力)
$ adb shell dumpsys activity service yuki.test.messenger/.MyMessenger -c -m
SERVICE yuki.test.messenger/.MyMessenger 41fa2ef0 pid=6151
  Client:
    Service state
        Service create time is Sun Jan 06 03:58:28 GMT+00:00 1980
        Current service state is BLOCKED

    Client(2)
        ClientRecord [ PackageName=test.pkg1, ID=1, hash=b6f4c7b1 ]
        ClientRecord [ PackageName=test.pkg2, ID=1, hash=b6f4c7b2 ]

    Message Logs
        03:58:44.570: { what=3 when=-1ms }
        03:58:44.339: { what=2 when=-1ms }
        03:58:28.681: { what=1 when=-5ms }
・引数指定(-a:全情報出力)
$ adb shell dumpsys activity service yuki.test.messenger/.MyMessenger -a
SERVICE yuki.test.messenger/.MyMessenger 41fa2ef0 pid=6151
  Client:
    Service state
        Service create time is Sun Jan 06 03:58:28 GMT+00:00 1980
        Current service state is BLOCKED

    Client(2)
        ClientRecord [ PackageName=test.pkg1, ID=1, hash=b6f4c7b1 ]
        ClientRecord [ PackageName=test.pkg2, ID=1, hash=b6f4c7b2 ]

    Message Logs
        03:58:44.570: { what=3 when=-1ms }
        03:58:44.339: { what=2 when=-1ms }
        03:58:28.681: { what=1 when=-5ms }

    ServiceState Logs
        03:58:44.571: { RUNNING => BLOCKED }
        03:58:44.340: { NEW => RUNNING }
・引数指定(-h:ヘルプ)
$ adb shell dumpsys activity service yuki.test.messenger/.MyMessenger -h
SERVICE yuki.test.messenger/.MyMessenger 41fa2ef0 pid=6151
  Client:
    MyMessenger dump options:
      [-a] [-c] [-m] [-s]
        a[ll]: dump all info.
        c[lient]: reply target.
        m[essage]: handle message log.
        s[tate]: service state change log.
・引数指定(-p:サポートしない引数)
$ adb shell dumpsys activity service yuki.test.messenger/.MyMessenger -p
SERVICE yuki.test.messenger/.MyMessenger 41fa2ef0 pid=6151
  Client:
    Unkown argument: -p; use -h for help.
    Service state
        Service create time is Sun Jan 06 03:58:28 GMT+00:00 1980
        Current service state is BLOCKED

便利ですね。好きなタイミングで実行できるのが強力です。

注意すべき点として、、、
ログ出力の場合と同じく、dump情報で出力する情報には注意が必要です。
# Service.dumpで出力される情報はbugreportにも出力されます。
開発時以外に必要ないのであれば、ビルドタイプでdump情報を絞る等の対応を考えます。

以上です。
2013/02/14

Android:contentコマンドでContentProviderを呼び出す

Android4.1.1以降、コマンドラインユーティリティにcontentコマンドが追加されました。

サンプルコマンド:
$ content query --uri content://yuki.authority/
このコマンドは、コンテンツへのCRUD操作をコンテンツプロバイダ経由で実行します。
例えば、データベースレコードの検索や削除などが可能です。

SQLite3コマンドでも同様にデータベースの編集が可能ですが、
SQLite3コマンドの対象がデータベースであるのに対して、contentコマンドの対象はコンテンツプロバイダです。
これはつまり、
  • コンテンツプロバイダの簡単な動作確認として使用可能
  • コンテンツプロバイダを経由するため、コンテンツオブサーバへの通知も実行される
といったメリットがあります。


●contentコマンドの使い方

使い方は
& adb shell content
と入力すれば使い方が表示されます。

【query】
$ adb shell content query --uri <URI> [--projection <PROJECTION>] [--where <WHERE>] [--sort <SORT_ORDER>]

【insert】
$ adb shell content insert --uri <URI> --bind <BINDING> [--bind <BINDING>...]


【update】
$ adb shell content update --uri <URI> [--where <WHERE>]


【delete】
$ adb shell content delete --uri <URI> --bind <BINDING> [--bind <BINDING>...] [--where <WHERE>]

・<URI>
    コンテンツプロバイダのURI
    (例)--uri content://hoge.authority

・<PROJECTION>
    検索対象のカラム名を指定します。指定方法は <COLUMN_NAME>[:<COLUMN_NAME>...]
    (例)column1:column2:column3

・<WHERE>
    SQLのWHERE句を指定します。
  (例)--where "column1='where_string'"

・<SORT_OREDER>
    ソート対象の列を指定します。
   (例)--sort "column1 DESC"

・<BINDING>
    次のフォーマットに従い、カラムにバインドする情報を指定します。
  <COLUMN_NAME>:<TYPE>:<COLUMN_VALUE>
  <TYPE>にはb - boolean, s - string, i - integer, l - long, f - float, d - doubleが指定可能です。
  (例)column:s:new_string


●動作確認

動作確認の対象となるテーブル情報は下記。

・Authority
yuki.authority

・テーブル情報
cid   name     type  
----  -------  --------
0     _id      INTEGER
1     number   INTEGER

・テーブル内容
_id         number
----------  ------------
1           1416078507
2           1355288181
3           380734541
4           254152153
5           18301077

【query】
# content query --uri content://yuki.authority/ --projection _id:number --where "_id BETWEEN 1 AND 3" --sort "_id DESC"
_id:number --where "_id BETWEEN 1 AND 3" --sort "_id DESC"
Row: 0 _id=3, number=380734541
Row: 1 _id=2, number=1355288181
Row: 2 _id=1, number=1416078507

【insert】
# content insert --uri content://yuki.authority/ --bind number:i:1234567
yuki.authority/ --bind number:i:1234567
-結果確認-
# content query --uri content://yuki.authority/ --projection _id:number --where "number=1234567" --sort "_id DESC"
_id:number --where "number=1234567" --sort "_id DESC"
Row: 0 _id=6, number=1234567

【update】
# content update --uri content://yuki.authority/ --bind number:i:9999
yuki.authority/ --bind number:i:9999
-結果確認-
# content query --uri content://yuki.authority/ --projection _id:number --where "number=9999"
_id:number --where "number=9999"
Row: 0 _id=1, number=9999
Row: 1 _id=2, number=9999
Row: 2 _id=3, number=9999
Row: 3 _id=4, number=9999
Row: 4 _id=5, number=9999
Row: 5 _id=6, number=9999

【delete】
# content delete --uri content://yuki.authority/ --where "_id=1"
yuki.authority/ --where "_id=1"
-結果確認-
# content query --uri content://yuki.authority/ --projection _id:number
om.example.testcprovider/ --projection _id:number
Row: 0 _id=2, number=9999
Row: 1 _id=3, number=9999
Row: 2 _id=4, number=9999
Row: 3 _id=5, number=9999
Row: 4 _id=6, number=9999

以上です。