Androidの操作を自動化をした時のまとめ

# 記事の目的
数ヶ月前にやめてしまったけど、力を入れていたので忘れないようにメモしておく。既に色々と思い出せないのだけど。。。。

## どんな操作を自動化をしていたか
複数のアプリに対して複数のアカウントを作成して所定の操作を自動化していた。ただし、そのアプリで自動操作を禁止している場合は注意。操作の内容は、基本、画像認識しながらタップ。たまにテキスト入力とスワイプ。裏ではデータベースに対する処理を実施していた。

## システム構成
* MySQL（アカウント情報やそのアカウントに関する情報、実行結果など）
* Nginx + Unicorn （管理用のWebアプリ）
* Android端末（仮装ではなく実物、30台くらい）
    * OSバージョン6
    * root化
    * Androidアプリ
        * OpenCV
        * Ruboto
        * 自動操作用の独自アプリ
            * 後述のマクロを実行
            * マクロファイルや画像の同期
* 自動操作を記述したマクロファイル（Rubyで記述）
* 開発用PC
    * Dropbox

### システムの特徴(1) - Rubyの使用

Rubotoを使ってマクロをRubyで書けるようにしたことが一番の特徴だと思う。そもそもRubotoを使っている人は少ないと思うし、それを自動化のために使っている人も少ないはず。パフォーマンスは、Javaと比較して遜色なかった。

また、マクロを作る人が私だけでなく、プログラミング初心者の人もいたので、コンパイルが必要なJavaよりもRubyの方が楽だと思ったから。データベースにはActiveRecordを使用して直接接続するようにした。クローズドなアプリなので、Web APIを用意するようなことはしなかった。

ただ、AndroidアプリはJavaで書いた。Rubyで書いて問題が起きて欲しくなかったのと、Androidアプリを正規の方法で開発して練習したかったから。

### システムの特徴(2) - ファイル同期

マクロファイルと画像の同期方法。作成したマクロや後述するテンプレート画像をAndroid端末と同期を取る必要があったため、DropboxとAndroidアプリのバックグラウンドサービスを使って実現した。PCとAndroid端末を直接同期する方法もあったが、PCもAndroid端末も複数箇所に置いてあったので、DBサーバ経由で同期することにした。特別な技術は使っていないが、こういう構成にすることは少ないと思うし、もっといい方法があったようにも思う。

**PC** <-- (Dropbox) --> **DBサーバ** <-- (Androidアプリ) --> **Android端末**

Dropboxは、それ以前から他の拠点とのファイル同期に使用していたので、その流れで使うことになった。Dropboxを使っていなかったらもっと別の方法になったと思う。

### システムの特徴(3) - 画像認識

タップを自動化するときに座標を指定することもできるが、画面にボタンが表示されていない状態でタップしても意味がない。そのため、正常にボタンが表示された上でタップするような仕組みとして画像認識させることとした。画像認識はOpenCVのテンプレートマッチングを利用した。

しかし、画像認識の速度が遅かったため、テンプレート画像のサイズを小さくしたり、マッチング対象をスクリーン全体にするのではなく限定的にするようにして対応した。また、 ```screencap``` コマンドで画面キャプチャを取得する方法も速度が遅かったため、別の方法で荒い画面キャプチャを撮ることにした。

また、画像認識だけでなく、画面に表示されている画像に対して何かしらの答えが必要になる場合があり、そのような場合はOpenCVが非常に役に立った。

### システムの特徴(4) - 効率の良いマクロ実行

まだまだ改善の余地があったが、アプリによって操作時間が異なる上にアカウント数も異なっていたため、「アプリA用のマクロをAndroid端末aで実行」という実行方法だと、所定時間内に終わらないことがあったり、逆に早く終わりすぎてAndroid端末がアイドル状態になって有効活用できない状態になってしまうことがある。

そのため、優先度の高いマクロは端末指定で実行することとし、優先度の低いマクロはアイドル状態のマクロで実行されるような仕組みにした。このようにすることで、どのAndroid端末も常に何かのマクロが動いている、という状態にすることができた。

### システムの特徴(5) - 動作確認

マクロがエラーになった時のデバッグ方法が厄介だった。Android端末で発生したエラーは実行結果としてDBに保存され、それはWebアプリを通して参照するようにしたが、そもそもどのような画面がAndroid端末上に表示されていたのかを知ることが最も大事だった。しかも、エラーが発生した時の画面だけでなく、そのエラーが発生するまでの全ての画面が必要になることが多かった。

そのため、画面操作するたびに画面キャプチャを取得し、それを動画ファイルに変換し、Webアプリから参照できるようにした。これにより、どの操作から意図しない状況が始まったのかが容易にわかるようになり、デバッグが捗るようになった。

また、他の拠点とChatworkで情報共有していたため、必要な情報はAPI経由でChatworkで連携するようにした。連携した情報は、以下のようなもの。

* 10分以上マクロが実行されていない端末
    * 熱によりハングする端末を知りたかった
    * バグにより待機状態になる端末を検知したかった
* 空き容量が1GB未満の端末
    * そもそもの端末の容量が少なかったり、実行可能なアプリを増やすと容量が足らなくなることがあったので、その端末を知りたかった
* 1日以内で直近10回のエラー率50%以上のマクロ
    * マクロによっては100%の成功率を達成することが難しかったため、エラーの続くマクロを知りたかった
* 現在実行中のマクロの過去24時間以内のエラー
    * アプリの仕様変更が発生すると、それに起因する同一エラーが多く発生したので、そのような内容を知りたかった
    * 発生件数の多いエラーを優先的にデバッグしたかった


### システムの特徴(6) - Android端末へのログイン

面倒なので極力Android端末へログインすることは避けたかったが、全てのログをDBに保存している訳ではなかったので、ログを確認したり、Android端末内の資産を確認したり、cpuの状態を確認するためにログインが不要になることはなかった。

また、端末数が増えるに従い、物理的にどの端末であるかを意識することが面倒になった。

そのため、端末にIPを振り、IP経由で ```adb shell``` を実行できるようにした。エラーが発生した端末のIPはWebアプリ上で把握できるので、ログインすることがかなり楽になった。

### システムの特徴(7) - アカウント間連携

複数アカウントで同時に操作したいことがあり、それらのアカウントが実行可能になるまで待機し、揃い次第操作をしていくようなマクロも作れるようにした。アカウント間のデータの共有はDBを使用した。

些細なことを特徴にしすぎて特徴が多くなってきた・・・。

## マクロの例

以下のような処理をするマクロの例を掲載しておきます。

* OKボタンが表示されたらタップ、閉じるボタンが表示されたら終了、２つとも表示されていなかったら端っこをタップ、をループ
* その後、次へボタンをタップ

```ruby
module HogeApp
class FugaMacro < MacroBase  
  def start_macro context, g
    # スクリーンの対象エリア
    areas = [
      g.margin([460, 1156, 160, 70]),
      g.margin([460, 1086, 160, 70]),
    ]

    # 60秒のタイムアウト付きでループ。タイムアウトが発生したら例外発生。
    g.screens!({:timeout => 60, :areas => areas}) do |s|

      # OKボタンが表示されたらタップ。画像認識の精度を調整するためにスコアを指定。
      s.touch "btn_ok.png", :score => 390 + 1000

      # 閉じるボタンが表示されたらループ終了
      s.search "btn_close.png" do
        throw :end
      end

      # 何も表示されなければ端っこをタップ
      s.etc do
        g.tap_edge
      end
    end

    # スクリーンの対象エリアを指定して、次へボタンが表示されたらタップ。タップできなければ例外発生。
    g.touch_game_image! "btn_next.png", {:area => g.margin([460, 1086, 160, 70])}

    # 続く・・・
```

ActiveRecordを使っていたので、必要があれば他のカウントの情報だったり、自分のアカウントの所有物などをDBに問合せたり更新したりした。

## 困難に感じたところ

### 困難(1) - 仮想端末

仮想端末は、アプリ側が検知して起動できない場合があり、そのために色々な対策をする必要があった。また、仮想端末にするためにはそのホストとなるPCが必要になってしまう。

そのため、物理端末を使用することとし、また、端末単独で動くように独自アプリを作ることとした。

これにより、端末を自由に外に持ち出すこともできるようになった。インターネット環境さえあれば、端末単独でマクロを回すことができる。

### 困難(2) - キャプチャ速度

画面キャプチャが遅い遅い・・・。高価な端末を購入する訳には行かなかったし、マクロ実行中は常に画面キャプチャを取るので、これがボトルネックになっていた。

そのため、 ```screencap``` でキャプチャを撮ることはやめ、直接キャプチャを取得するようにした。また、取得するときにデータを間引いて荒い画像を取得するようにしてパフォーマンスを改善するようにした。

### 困難(3) - 端末が死ぬ

忘れてしまった。メモリリークかな。死ぬといっても壊れる訳ではなく、うんともすんとも言わなくなり、ハングすること。ああ、そうだ、 ```su``` が無応答になってしまったのだ。たまーに発生する事象だった。検索してもほとんど事例がなく困った。そのような場合は、 ```su``` を実現するためのプロセスを定期的に再起動するようにした。また、メモリリークに対応するために、お手軽な端末の再起動を定期的に実行するようにした。

### 困難(4) - Dropboxが、

忘れてしまったが、Dropbox関連の問題は色々あった。WindowsとLinuxの相性問題だったのかもしれない。使い方の問題もあると思うが、正常に同期しない場合があった。どう解決したか覚えていない。。。

### 困難(5) - 熱対策

熱を持つと、自動的にCPUの速度が遅くなるように制御されているため、扇風機が必須だった。扇風機によって50%くらいパフォーマンスが上がることもあった。

### 困難(6) - バッテリーの膨張

解決しなかった問題だが、バッテリーが膨張する。使っていたバッテリーの問題かもしれないが、物理的な端末を使うが故の問題だった。おかげでバッテリー交換スキルが上達したが、これほど退屈なことはなかった。やっぱり仮想の方が運用は楽だよなー。

## 最後に

* もし同じシステムを組むとしたら、今度は仮想でやってみたい。Noxは結構安定しているように見える。
* 機械学習やディープラーニングを取り入れていきたい。
* 開発効率をもっと上げたかった。テンプレート画像を取得するためのCLIコマンドを用意したが今ひとつだった。
* アプリごとにアカウントを保存するための仕組みが違っていて、勉強にはなったけどなぜもっと統一されないのか疑問ではあった。