Androidのアプリのリリースチェック手法

この記事はゆめみ Advent Calendar 2017 の 13 日目の記事です。

# はじめに

私はアプリのリリース前確認の一貫として、apk内の各リソースや設定値を確認することがあります。

各種設定や変更はgitで管理し、コードレビューなど行っていればそうそう意図しない変更は混入しない気もするのですが、実際にはレビューでも見落とす、取り違えるなどが可能性としては起こりえます。
アプリ公開したら、テスト環境に接続していたなんてことは避けたいので、この辺りはリリース前の定形作業として最終チェックすることにしています。

このエントリではAndroidアプリをリリースする際のリリースチェックの内容とその手法について述べます。
また、どうしてそのようなチェックをするのかという点についても、可能な限り書くようにしました。

## 注意事項

このエントリではリリースチェックで確認したほうが良いだろうと思われる内容についてピックアップして解説しています。
ここに書いてある内容をすべて実施すればカバーできるというわけではありませんし、問題が起きないと保証できるわけでもありませんのでご注意お願いします。

また、冒頭で述べたようにできるだけapk内の設定値を事前に確認して、リリース事故を防ごうという意図で記述しているものですが、一部リバースエンジニアリング的な要素を含みます。
他者のアプリバイナリをリバースエンジニアリングする行為は多くの場合禁止されているかと思いますので、その点はご注意お願いします。

# 必須確認事項


## logcatの出力内容

リリースビルドのアプリでデバッグログが出ていないか確認します。
実行するときはプロセスIDでgrepすると捗ります。

ちなみに、プロセスIDは以下で調べられます。

```:プロセスIDの調べ方
adb shell ps | grep {パッケージ名}
```
```:結果例(左から2列目がプロセスID)
u0_a60    28925 4243  1612140 41592 SyS_epoll_ 0000000000 S com.google.android.inputmethod.japanese
```

開発時に必要で、リリース時は不要なログとなる場合が多いと思うので、ProGuardで処理するか、LogクラスのWrapperクラスを作ってそこで制御します。
最低の情報をLog.iで残した方が良いという意見もありますが、個人的にはエンドユーザーのlogcatを取得する方法がない以上、表示しないほうが良いと考えてます。

ちなみに利用しているSDKがlogcatに出力しているケースもあるので、完全には消せないこともあるのですが、ドキュメントを読むとlogの制御方法がオプションとして用意されているケースもあります。

過去にAPIのレスポンスやAPIのURL、EditTextの入力値がlogcatに出ているアプリを見たことがありますが、これはかなりみっともないです。
ほぼユーザーはいないでしょうが、古いAndroid4.0のOSですと、logcatの内容は他のアプリから取得できるので、センシティブな情報が表示されている場合、流出リスクになり得ます。

## gitのdiff内容

前回のリリースリリースからの差分が多い場合、ソースコードの全差分を上から見ろというのは流石につらいので(できるなら見たほうが良いですが)、基本的にはcommit履歴やgit diffの--statオプションで前回のリリース時のtagを指定してファイルの当たりをつけてから細かい差分を見てます。

```:実行例
git diff v1.1.0 v1.1.1 --stat
```
```:結果
 README.md                                                       |   2 +-
 src/main/java/yu/intel/myapplication/MainActivity.java          |  16 ++++++++--------
 2 files changed, 9 insertions(+), 9 deletions(-)
```

ここで明らかに今回の修正範囲に含まれないファイルが出てくれば、--statオプションを外してファイル名指定で中身を確認します。

```:実行例
git diff v1.1.0 v1.1.1 src/main/java/yu/intel/myapplication/MainActivity.java 
```

## パーミッション

前回のリリースと比較して意図しないパーミッションが追加されていないか確認します。
各モジュールのAndroidManifestがbuild時にマージされますので、外部ライブラリが利用しているパーミッションもここに含まれて出てきます。

```:実行例
aapt dump badging app-debug.apk | grep "permission"`
```
```:結果 
uses-permission: name='android.permission.INTERNET'
uses-permission: name='android.permission.CAMERA'
  uses-implied-feature: name='android.hardware.camera' reason='requested android.permission.CAMERA permission'
```

ちなみに意図しないパーミッションが出てきたが、どこで追加したかわからないというような場合には、Android StudioのMerged Manifestで調べることができます。
調べるにはAndroid StudioでAndroidManifestを開いて、Merged Manifestタブを選択します。
すると、マージされたAndroidManifestとそれぞれの要素がどこの由来なのか簡単に調べることが可能です。
詳しくは、以下の公式ドキュメントを参照してください。

https://developer.android.com/studio/build/manifest-merge.html?hl=ja

## アプリバージョン

versionCodeが前回のリリース時より大きくないと、コンソールでapkをアップロードできないので、事前確認します。
合わせて、minSdkVersionやtargetSdkVersionが変わってないかも確認しておきます。

最近はGooglePlayにアップロードする際に同様の確認が可能なので、ここでは省いても良いかもしれませんが、上げる前に確認しておきたい場合にどうぞ。

```:実行例
aapt dump badging app-debug.apk| grep "Version"
```
```:結果
package: name='intel.yu.myapplication' versionCode='1' versionName='1.0' platformBuildVersionName=''
sdkVersion:'19'
targetSdkVersion:'26'
```

## 署名の確認

前回のリリースと同じ署名でなければアップデートできませんので、確認します。
別の署名だとGoogle Play Consoleで弾かれるほか、fingerprintを必要とする機能がうまく動きません。

サンプルではデバッグのKeyStoreで、実際には `CN=Android Debug, O=Android, C=US` にはkeystore作成時に記載した内容が格納されています。
※サンプルなので、MD5ハッシュとか、SHA1ハッシュは適当に伏せています。

ファーストリリースの場合は所有者や発行者なども意図したものになっているか確認します。
ちなみに、新規でkeystoreを作るなら、署名アルゴリズムはSHA1withRSAではなくて、強度の問題でSHA256withRSAで作っておいたほうが良いと思います。

```:実行例
keytool -list -printcert -jarfile app-debug.apk
```
```:結果
署名者番号1:

署名:

所有者: CN=Android Debug, O=Android, C=US
発行者: CN=Android Debug, O=Android, C=US
シリアル番号: 01234567
有効期間の開始日: Thu Oct 04 16:24:49 JST 2012終了日: Sat Sep 27 16:24:49 JST 2042
証明書のフィンガプリント:
	 MD5:  00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
	 SHA1: 00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00
	 SHA256: FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF:FF
	 署名アルゴリズム名: SHA1withRSA
	 バージョン: 3
```

# 任意の確認確認

## 上書きインストール

新規インストールして動作確認は誰でもやると思うのですが、新規インストールすると問題が起きないが、上書きインストールすると問題が起きるということはありえます。
特にバージョン毎の移行処理や特定のキャッシュファイルに依存する処理に関する内容を修正した場合は、上書きインストールテストを行うことをおすすめします。

普通に端末にapkファイルを移動してインストールしても良いですが、adb installするときは -r オプションをつけると上書きインストールになります。

```:実行例
adb install -r app-debug.apk
```

## configファイルの確認

リソースやclassに環境ごとに切り替わるような値が入っている場合は、以下のような方法で取り出します。

### res以下のvaluesに定義された設定値

例えば、 main/res/values に 

```:config.xml
<resources>
    <string name="endpoint">http://www.example.com/</string>
</resources>
```
というような環境ごとのAPIのドメインが定義されていたとします。

apkからresourceを抽出するには `aapt dump --values resources` を利用します。

```:実行例
aapt dump --values resources app-debug.apk | grep "endpoint" -A 1
```
```:結果
      spec resource 0x7f0b0020 intel.yu.myapplication:string/endpoint: flags=0x00000000
      spec resource 0x7f0b0021 intel.yu.myapplication:string/search_menu_title: flags=0x00000004
--
        resource 0x7f0b0020 intel.yu.myapplication:string/endpoint: t=0x03 d=0x0000029b (s=0x0008 r=0x00)
          (string8) "http://www.example.com/"
```

### ソースコード中に定義された設定値

ソースコード中に定数で保持している値です。
ユースケースとしてはBuildConfigとか、定義ファイルを定数でソース中に持っている時になると思います。

これは探しにくいのですが、apktoolでsmaliを取り出すと探しやすくなります。
うまく引っ掛けてあげることでこのあたりのチェックを自動化出来るかもしれません。

ProGuard掛けていると

```:実行例
apktool d -f -r app-debug.apk -o release_check_tmp/
grep "XXX_KEY" release_check_tmp/smali/intel/yu/myapplication/HogeConfig.smali
```
```:結果(2行目のみ)
.field public static final XXX_KEY:Ljava/lang/String; = "qawsedrftgyhujikolp"
```

1行目で -o オプションで指定したディレクトリに展開、2行目でgrepします。
-oオプションを指定した場合は、apkのファイル名から拡張子を除いたものが使われます。(例だと app-debug)
`/build/output/apk`以下に生成されるファイルは通常、ProductFlavorsによってファイル名が変わる(=展開先ディレクトリ名も変わる)ので、shellなんかでチェックスクリプトを書くならば、-oで指定してあげたほうが楽です。
ちなみに1行目は展開しているだけなので、複数の変数を確かめる場合も1回の実行で良いです。

smaliは読めなくはないのですが、リリースチェックではなく読むことを目的とするならば、dex2jarでapkファイルからjarファイルにした後、そのjarファイルをJD-GUIで見るほうが読みやすいです。
目的と外れるので、リンクの紹介だけにしておきます。

[JD-GUI (http://jd.benow.ca/)](http://jd.benow.ca/) 
[dex2jar (https://sourceforge.net/projects/dex2jar/)](https://sourceforge.net/projects/dex2jar/) 

### assetに定義された設定値

同様に生成されたフォルダを見るとわかるのですが、assetにあるファイルなんかも見れます。

```:実行例
apktool d -f -r app-debug.apk -o release_check_tmp/
cat release_check_tmp/assets/config.json
```
# 利用しているツールの説明

この記事で利用しているツールの説明になります。

## aapt 

Android SDKに含まれているツールです。
Android Asset Packaging Toolの略のようです。apkの内容をリストアップできます。

おそらくPATHが通ってないので、コマンドが見つからないと言われるのですが、Android SDKのインストールフォルダの `build-tools/` にバージョン毎にディレクトリがあります。（どうもインストールしているバージョン等時期によって場所が異なるようなのですが、私はこの場所にあります。）

私は`build-tools/27.0.0` に対して、 `now-version` というシンボリックリンクを貼ってそこにPATHを通しています。
バージョン番号が別のツールを使用するときはPATHを変えずに、シンボリックリンクの向き先を変更します。

## apktool

Androidアプリをリバースエンジニアリングするためのツールです。

これはjarファイルとそのWrapperを下記から落としてPATHが通った場所においておきます。
https://ibotpeaches.github.io/Apktool/install/

私は `/usr/local/bin` に置いてます。
Jenkins等のCIでこの手のチェックをしたい時で自分がタッチできないビルド環境で使用したい場合は、同じレポジトリに置いたり、なければ実行時にダウンロードしてきたりと工夫が必要です。