CLIでAndroidアプリのデバッグをするためにStetho Custom dumpapp Pluginsを作る

Stethoが便利なので、より活用するべく、Custom dumpapp Pluginsの作り方を調べてみました。

![スクリーンショット 2015-05-28 21.54.46.png](https://qiita-image-store.s3.amazonaws.com/0/47437/cc3fbeef-8530-b571-7411-d13d00b66484.png)


## **Stethoについて**
まず、Stethoについてですが、Chrome DevTools上でのAndroidアプリのデバッグを可能としてくれるFacebook製ソフトウェアです。
[Stetho](http://facebook.github.io/stetho/)

Stethoを使うと以下のようなことができるようになる。

### **Network Inspection**
![スクリーンショット 2015-05-28 21.55.28.png](https://qiita-image-store.s3.amazonaws.com/0/47437/e4290cc2-098b-139e-5292-cce200d25f2a.png)
Chrome DevToolsのNetwork Inspection（JSONレスポンスhelperや画像プレビュー、HARフォーマットでのエクスポートなど）をAndroidアプリの通信においても使えるようになる。

### **Database Inspection**
![スクリーンショット 2015-05-28 22.07.17.png](https://qiita-image-store.s3.amazonaws.com/0/47437/853165f4-c2a6-e8a4-0e11-8bf8bd9149e5.png)
SQLiteやPreferenceの値を、Chrome DevToolsの使い慣れたUIで、確認したり書き換えたりできるようになる。

### **View Hierarchy**
![スクリーンショット 2015-05-28 21.54.33.png](https://qiita-image-store.s3.amazonaws.com/0/47437/4f715e63-1740-959b-4c38-18eefd784f14.png)

Chrome DevToolsの見慣れた画面で、アプリのView hierarchyを確認することができる。
「Copy XPATH」とかも使えるので、Appiumとかでテストするときも便利そう。

### **dumpapp**
![スクリーンショット 2015-05-28 22.16.54.png](https://qiita-image-store.s3.amazonaws.com/0/47437/ef6f9251-61c1-e783-f2ff-2effbd22bdf8.png)
これは他と少し毛色の違う機能で、dumpappはChrome DevToolsを使ってではなく、コマンドライン上からのAndroidアプリのデバッグを可能としてくれる。
さらにdumpappは拡張することができる（**Custom dumpapp Plugins**）。
その**Custom dumpapp Plugins**の作リ方に関しては、「See the stetho-sample project for more details.」と書いてあるのみで、他に説明は無いっぽいので、サンプルを参照しながら、実際に作ってみることにする。


### **Stethoの導入**
#### **build.gradle**

```groovy:build.gradle
compile 'com.facebook.stetho:stetho:1.1.1'
// 以下はNetwork inspectionする場合に必要
// OkHttpを使う場合
compile 'com.facebook.stetho:stetho-okhttp:1.1.1'
// UrlConnectionを使う場合
compile 'com.facebook.stetho:stetho-urlconnection:1.1.1'
```

#### **Applicatioクラス**

```java
public class MyApplication extends Application {
    public void onCreate() {
        super.onCreate();
        Stetho.initialize(
             Stetho.newInitializerBuilder(this)
             // dumpappを有効にする
             .enableDumpapp(
                  Stetho.defaultDumperPluginsProvider(this))
             // Chrome DevToolsでのデバッグを有効にする
             .enableWebKitInspector(
                  Stetho.defaultInspectorModulesProvider(this))
             .build());
   }
}
```

#### **Network Inspection機能を使う場合**

**OkHttp**
OkHttpのNetworkInterceptorsにStethoInterceptorを追加するだけでOK。

```java:
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(new StethoInterceptor());
```

**HttpURLConnection**
HttpURLConnectionの場合は、ちょっと面倒。
以下のサンプルのように実装する必要がある。
[Networker.java](https://github.com/facebook/stetho/blob/master/stetho-sample/src/main/java/com/facebook/stetho/sample/Networker.java)


#### **dumpappを使う場合**

dumpappコマンド([stethoのリポジトリに含まれている](https://github.com/facebook/stetho/tree/master/scripts))のパスを環境変数に追加

```zsh
export PATH=”$PATH:/path/to/stetho/scripts”
```
※ Python 3がインストールされてない場合はインストールが必要。

## **Stethoが提供するdumpapp Plugins**

最初から、いくつかのdumpapp Pluginsが提供されているので、調査がてら見てみる。
Stetho.defaultDumperPluginsProvider(this)で使えるようになるのは、以下の3つ。

```java:Stetho.java
plugins.add(new HprofDumperPlugin(context));
plugins.add(new SharedPreferencesDumperPlugin(context));
plugins.add(new CrashDumperPlugin());
```
[Stetho.java](https://github.com/facebook/stetho/blob/master/stetho/src/main/java/com/facebook/stetho/Stetho.java#L120-L122)


### **SharedPreferencesDumperPlugin**
SharedPreferenceの値を出力したり書き換えたりできる。
[SharedPreferencesDumperPlugin.java](https://github.com/facebook/stetho/blob/master/stetho/src/main/java/com/facebook/stetho/dumpapp/plugins/SharedPreferencesDumperPlugin.java)

**write**

```zsh
# dumpapp prefs write <path> <key> <boolean|int|long|float|string|set> <value>
dumpapp prefs write shikato shikatokey string shikatoval
```

**print**

```zsh
dumpapp prefs print
# 以下のように出力される
# shikato:
# shikatokey = shikatoval
```


### **CrashDumperPlugin**
クラッシュを意図的に発生させることができる。
[CrashDumperPlugin.java](https://github.com/facebook/stetho/blob/master/stetho/src/main/java/com/facebook/stetho/dumpapp/plugins/CrashDumperPlugin.java)


引数で指定（デフォルトはjava.lang.Error）した例外を発生させる。(simulates a program crash)

```zsh
dumpapp crash throw java.lang.NullPointerException
```

System.exitを使う。(simulates an abnormal Android exit strategy)

```
dumpapp crash exit
```

ProcessBuilderを使ってkillコマンドを実行。(simulates the low memory killer)

```
dumpapp crash kill
```

### **HprofDumperPlugin**
Heap dump（HPROF）を、stdoutに出力したり、Android端末内のファイルに書き込んだりできる。
[HprofDumperPlugin.java](https://github.com/facebook/stetho/blob/master/stetho/src/main/java/com/facebook/stetho/dumpapp/plugins/HprofDumperPlugin.java)

```zsh
dumpapp hprof -
```


## **Custom dumpapp Pluginsの作り方**
HelloWorldなサンプル dumpapp Plugin。

```java:HelloWorldDumperPlugin.java
// DumperPluginをimplementsする
public class HelloWorldDumperPlugin implements DumperPlugin {
  private static final String NAME = "hello";

  // getNameで返す値がdumpappp実行時のコマンドとなる
  // ex.) appdump {{NAME}}
  @Override
  public String getName() {
    return NAME;
  }

  // getNameで指定したコマンドを実行したときに呼ばれるメソッド
  @Override
  public void dump(DumperContext dumpContext) throws DumpException {
    PrintStream writer = dumpContext.getStdout();
    // {{NAME}}以降の引数が返ってくる
    // appdump {{NAME}} hoge huga
    Iterator<String> args = dumpContext.getArgsAsList().iterator();

    String helloToWhom = args.hasNext() ? args.next() : null;
    if (helloToWhom != null) {
      doHello(writer, helloToWhom);
    } else {
      doUsage(writer);
    }
  }

  private void doHello(PrintStream writer, String name) throws DumpUsageException {
    if (TextUtils.isEmpty(name)) {
      // This will print an error to the dumpapp user and cause a non-zero exit of the
      // script.
      throw new DumpUsageException("Name is empty");
    }

    writer.println("Hello " + name + "!");
  }

  private void doUsage(PrintStream writer) {
    writer.println("Usage: dumpapp " + NAME + " <name>");
  }
}
```
[HelloWorldDumperPlugin.java](https://github.com/facebook/stetho/blob/master/stetho-sample/src/debug/java/com/facebook/stetho/sample/HelloWorldDumperPlugin.java)

作ったCoustom dumpapp Pluginを有効にするには以下の様に実装する。

```java:SampleDebugApplication.java
  @Override
  public void onCreate() {
    super.onCreate();

    long startTime = SystemClock.elapsedRealtime();
    final Context context = this;
    Stetho.initialize(
        Stetho.newInitializerBuilder(context)
            // DumperPluginsProviderをimplementsしたクラスを渡す
            .enableDumpapp(new SampleDumperPluginsProvider(context))
            .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))
            .build());
    long elapsed = SystemClock.elapsedRealtime() - startTime;
    Log.i(TAG, "Stetho initialized in " + elapsed + " ms");
  }

  // DumperPluginsProviderをimplementsしたクラスを用意する
  private static class SampleDumperPluginsProvider implements DumperPluginsProvider {
    private final Context mContext;

    public SampleDumperPluginsProvider(Context context) {
      mContext = context;
    }

    @Override
    public Iterable<DumperPlugin> get() {
      ArrayList<DumperPlugin> plugins = new ArrayList<DumperPlugin>();
      for (DumperPlugin defaultPlugin : Stetho.defaultDumperPluginsProvider(mContext).get()) {
        plugins.add(defaultPlugin);
      }
      // 有効にしたいPluginを追加する
      plugins.add(new HelloWorldDumperPlugin());
      plugins.add(new APODDumperPlugin(mContext.getContentResolver()));
      return plugins;
    }
  }
```
[SampleDebugApplication.java](https://github.com/facebook/stetho/blob/master/stetho-sample/src/debug/java/com/facebook/stetho/sample/SampleDebugApplication.java)

HelloWorldDumperPluginを実行すると、こんな感じに出力される。
![スクリーンショット 2015-05-28 22.32.16.png](https://qiita-image-store.s3.amazonaws.com/0/47437/150d94c6-38a5-58bf-9b59-7e5d3c37bd83.png)

## **InfoDumperPlugin**
簡単に作れそうなので、実際に作ってみる。

作ってみたのは、dpiやBuildConfigの値など、コマンドライン上から確認できると便利そうな値を出力するプラグイン。

[info-dumper](https://github.com/shikato/info-dumper)

![screen gif](http://38.media.tumblr.com/aa7134963258048bfe1758fbaa821111/tumblr_np2dmkXiOC1ro6w1ho1_500.gif)

今のところ以下のような情報を表示できる。

| Command | Action |
|:-----------|------------:|
| buildconf   |BuildConfig fields.|
| id     |AndroidID, UUID, Advertising ID. |
| dpi       |dpi info. |
| memory         |Memory info|
| network    |Network info. |
| permission       |Required permissions. |
| lastupdate    | Lastupdate time. |
| error    |    Error state info. |
| tel    |     TelephonyManager info. |
| appinfo    | android.content.pm.ApplicationInfo fields. |
| osbuild    | android.os.Build fields.|
| all    |     all. |


## **最後に**
Stethoかなり便利。dumpappもっと活用していきたい。
せっかく作ったInfoDumperPluginも、もうちょっと便利にしたい。

#### **メモ**
DevToolsにdumpappが組み込まれるかも？


```
lynfogeek commented on 23 Feb

The DumperPlugins are great, but it would be even better to embed them into the DevTools.
One solution could be to create a new tab (like this extension does) , and list there all the enable plugins. Whenever the user click on one of them, it pulls the data and display it.
```
（commented on 23 Febとなってるので望み薄かもだけど）
https://github.com/facebook/stetho/issues/68