Python3.7を使ったKivyアプリをAndroidのAPKにする

長らくAndroidのAPKの作成にはうまくいってなかったのですが、最近成功したのでここに記録します。APKを作る為の道具である`python-for-android`と`buildozer`は目まぐるしく更新されていて、しばらく経っただけで挙動が変わっていたりbuildがうまくいかなくなったりと非常にやっかいだったのですが、最近は安定してきているので記事にまとめます。

# アプリ開発を始める前に

androidへ持って行く予定がある場合はあらかじめ自分が使う予定の非標準moduleを使った簡単なtest codeを書き、androidでtestしておく事をお勧めします。理由はそれによって

- apkに組み込まれるpythonのversion
- 使いたい非標準moduleがandroidでも使えるのか

がわかるからです。もし使いたいmoduleが使えなかった場合は

1. そのmoduleを諦める
2. そのmoduleの為のrecipeを書く

のどちらかを選ばなければなりません。recipeとはapk化の際にmoduleを組み込む為の手順をpython-for-androidに教えるもので、例えばcythonによるcompileが必要なmoduleは基本的にrecipe無しでは組み込めないと思ったほうが良いです。[これ](https://github.com/kivy/python-for-android/tree/develop/pythonforandroid/recipes)がpython-for-androidが元から持っているrecipeで、もしrecipeを自分で書きたいのならそれと[doc](https://python-for-android.readthedocs.io/en/latest/recipes/)を見るのが良いと思います。

# 手順

(既に作っているKivyアプリの為の仮想環境がある状態を想定しています)

### buildozerが必要とする物を入れる

厳密には`buildozer`ではなく`buildozer`が内部で使う`python-for-android`に必要な物で、[ここ](https://buildozer.readthedocs.io/en/latest/installation.html#targeting-android)を参考に入れておきます。私はUbuntu 16.04ベースのOSを使っているので`Android on Ubuntu 16.04 (64bit)`に書いてある物を入れました。

### 以前使っていたfile群を削除

もし以前使っていた`buildozer.spec`、buildozerの作業directory(`.buildozer`)が残っているのなら削除する事をお薦めします。これらのfileが現在のversionの`buildozer`と互換性がある保証が無いからです。

### 仮想環境を有効にする

ここからの作業は全て仮想環境上で行うので有効にしておきます。

```
$ pipenv shell
```

### buildozerをinstall

安定版と開発版のどちらを選ぶかは難しい所です。私は開発版で駄目だった場合にのみ安定版を試しています。

```:開発版のinstall
$ pipenv install https://github.com/kivy/buildozer/archive/master.zip
```

```:安定版のinstall
$ pipenv install buildozer
```

### buildozer.specを作成

```
$ buildozer init
```

と打つと`buildozer.spec`が出力されるので、その中の以下の項目を書き換えました。

```ini
# アプリに付ける名前。日本語も使える。
title = Test Application

# Package name
package.name = testapp

# Package domain (needed for android/ios packaging)
package.domain = jp.gottadiveintopython

# main.pyのあるdirectory
source.dir = ./testapp

# APKに含めたくないdirectory
source.exclude_dirs = .venv,.git

# 依存関係 python3crystaxではなくpython3なのが注意
requirements = python3,kivy,android,plyer

# logにかけるfilter。
# 既定値のままだとpython interpreterの吐くlogしか出力されず、
# もしinterpreterの立ち上げ自体に失敗した場合(例えばAPKのinstallに失敗するなど)
# 何の手がかりも得られなくなってしまう。なので最初はこの値を
# android.logcat_filters = *:W
# にでもしておき、interpreterが立ち上がる事を確認してから既定値に戻すのもあり。
android.logcat_filters = *:S python:D

# 使用するpython-for-androidを最新安定版(master)ではなく最新開発版(develop)に指定
p4a.branch = develop

# logのlevel。これは必ずに最大にしておく。
log_level = 2
```

### Android端末をADBモード(開発者モード)でPCに繋ぐ

やり方はググるといくらでも出てくるので省きます。

### 実行

```
$ buildozer android debug deploy run logcat
```

このcommandによってAPKを作ってAndroid端末にinstallしてから実行するまでを一気に行ってくれます。commandの意味は

- `android`向けの`debug`版のAPKを作り
- それを端末にinstall(`deploy`)し
- 実行し(`run`)し
- logを見る(`logcat`)

となっています。一度成功したら、以後はソースコードを修正する度にこのcommandで動作確認をする作業の繰り返しになります。

# 今回初めて出遭ったerror

上のcommandではinstallがうまくいかず、Android端末側の操作によってAPKをinstallしようとした時(野良APKをinstallするやり方)なのですが

![Screenshot.png](https://qiita-image-store.s3.amazonaws.com/0/144673/ea7e3187-d027-9ae3-49ab-9dcdcd81bf91.png)
となりました。最初は「また壊れたAPKができてしまったのか」と思ったのですが、原因をさぐるべくlogを読んでいくと

```
W/PackageParser( 3472): /sdcard/Download/testapp-0.1-debug.apk (at Binary XML file line #11): Requires newer sdk version #21 (current version is #17)
W/PackageInstaller( 3472): Parse error when parsing manifest. Discontinuing installation
```

という行がありました。おそらくですが`Requires newer sdk version #21`の`21`はAPKが要求するAPIの最低versionで`(current version is #17)`の`17`はそれをinstallしようとしたAndroid端末が対応しているAPIの最大versionなんじゃないかと思います。なのでAPKが求めるversionのAPIに端末側が対応していないという意味のerrorだと思います。実際、より新しいAndroid端末では成功しました。

というわけで今回、`パッケージの解析中に問題が発生しました。`はAPKの作成に失敗しているとは限らないという事を学びました。

# 権限に関して

buildozer.specに書くだけでなく[この様に](https://python-for-android.readthedocs.io/en/latest/apis/)実行時にも権限を求める必要があります。

# 他

- [logcatのdoc](https://developer.android.com/studio/command-line/logcat?hl=ja)
- [release版のAPKをbuild](https://qiita.com/gotta_dive_into_python/items/7f790243bf2c16039691)