PythonでAndroidアプリを作る。

# はじめに
PythonアプリをAndroidで動かす場合2つの手段がある。
1. **Pydroid3でPythonスクリプトを起動する。**
Pydroid3はGooglePlayからインストールできるアプリ。PythonスクリプトをAndroid上で動かすIDE。pipも使えるので、PC上のPythone IDEを使うのとほぼ変わらない。KivyでGUIを書けば、アンドロイドアプリを起動しているのと変わらない。ただPydroid3を起動して、該当のPythoneアプリを実行するという2段階の処理をしないといけないので、アプリアイコンクリックで一発起動させるということはできない。

2. **Kivy+Buildozerでアンドロイドアプリを作る。**
Buildozerは、KivyでGUIを書いたPythonアプリを、apkファイルにパッケージ化するツール。このapkファイルをAndroidにインストールすれば、アンドロイドアプリとして動作する。

　本記事は2について、WindowsのPC環境でBuildozerのインストールから、パッケージ化までの一連の流れをまとめる。

# 目次
+ [ツールと環境](#ツールと環境)
+ [ツールインストール](#ツールインストール)
+ [ビルド](#ビルド)
+ [アプリ例](#アプリ例)

[[Pythonまとめへ]](https://qiita.com/tapitapi/items/2f71e89dcfd0a8f6bf41)

## ツールと環境
本記事で登場するツールは下記の通り。PythonとWSL2(Ubuntu)はインストール済である前提。

### **Kivy** (https://kivy.org/)
アプリのGUI部分。PythonのGUIライブラリ。Tkinterみたいなもの。使い方は [PythonのGUIライブラリKivyを使う](https://qiita.com/tapitapi/items/a4a4e3a7164afd922115) を参照。

### **Buildozer** (https://buildozer.readthedocs.io/en/latest/)
apkパッケージ化するためのビルドツール。BuildozerはLinuxとmacOSにしか対応していないため、Windowsの場合はWSL2(Ubuntu)上で動作させる。

### **試した環境**
OS|windows11
-|-
WSL2|Ubuntu 22.04.2 LTS
Python|Python 3.10.12
Kivy|Kivy 2.2.1
Buildozer|Buildozer 1.5.0

[戻る](#目次)


## ツールインストール
KivyとBuildozerの準備についてまとめる。

### Kivy
pipでkivyパッケージをインストール
```console:cmd
$ pip3 install kivy
```
### Buildozer
https://buildozer.readthedocs.io/en/latest/installation.html を参照。注意点としてはインストールは root でなくユーザですること。rootですると下記のように怒られる。

**Buildozer is running as root!**
**This is not recommended, and may lead to problems later.**

WSL(Ubuntu)でユーザを追加する方法は [WSL2のTips](https://qiita.com/tapitapi/items/5a10162a067ccb101ab3) を参照。


+ **pipでBuildozerパッケージをインストールし、バージョン確認**
```console:cmd
$ pip3 install --user --upgrade buildozer
$ buildozer version
```

+ **関連ツールをインストール**
```console:cmd
$ sudo apt update
$ sudo apt install -y git zip unzip openjdk-17-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev
$ pip3 install --user --upgrade Cython==0.29.33 virtualenv  # the --user should be removed if you do this in a venv

# パスを追加
$ export PATH=$PATH:~/.local/bin/
```
これでツールの準備完了。
[戻る](#目次)

## ビルド
https://buildozer.readthedocs.io/en/latest/quickstart.html を参照。
+ **.pyと.kvファイルを用意する**
メインファイル名はmain.pyとする。今回は下記アプリを例として使用。

https://qiita.com/tapitapi/items/7c41dab03089ef9852fb

<details><summary><b>main.py/gui_sample.kvファイルはこちら。</summary><div>

```Python:main.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout #boxのレイアウト


#.kvファイル内に記述したモジュール名と同じにする。
class Root_Layout(BoxLayout):
	r_color=0.00
	g_color=0.00
	b_color=0.00
	a_color=1.00
		
	def sliderR_move(self):
		self.r_color=format(self.ids.slider_R.value,'.3f')
		self.ids.slider_text_R.text = 'R:'+str(self.r_color)
		self.ids.slider_text_R.background_color = (self.r_color,0,0,1)
		#全体色
		self.ids.text_color.background_color = (self.r_color, self.g_color, self.b_color, self.a_color)
		self.ids.text_color.text = '( '+ str(self.r_color) +' ,'+ str(self.g_color) +' ,'+ str(self.b_color) +' ,'+ str(self.a_color) +' )'
			
	def sliderG_move(self):
		self.g_color=format(self.ids.slider_G.value,'.3f')
		self.ids.slider_text_G.text = 'G:'+str(self.g_color)
		self.ids.slider_text_G.background_color = (0,self.g_color,0,1)
		#全体色
		self.ids.text_color.background_color = (self.r_color, self.g_color, self.b_color, self.a_color)
		self.ids.text_color.text = '( '+ str(self.r_color) +' ,'+ str(self.g_color) +' ,'+ str(self.b_color) +' ,'+ str(self.a_color) +' )'

	def sliderB_move(self):
		self.b_color=format(self.ids.slider_B.value,'.3f')
		self.ids.slider_text_B.text = 'B:'+str(self.b_color)
		self.ids.slider_text_B.background_color = (0,0,self.b_color,1)
		#全体色
		self.ids.text_color.background_color = (self.r_color, self.g_color, self.b_color, self.a_color)
		self.ids.text_color.text = '( '+ str(self.r_color) +' ,'+ str(self.g_color) +' ,'+ str(self.b_color) +' ,'+ str(self.a_color) +' )'

	def sliderA_move(self):
		self.a_color=format(self.ids.slider_A.value,'.3f')
		self.ids.slider_text_A.text = 'A:'+str(self.a_color)
		self.ids.slider_text_A.background_color = (0,0,0,self.a_color)
		#全体色
		self.ids.text_color.background_color = (self.r_color, self.g_color, self.b_color, self.a_color)
		self.ids.text_color.text = '( '+ str(self.r_color) +' ,'+ str(self.g_color) +' ,'+ str(self.b_color) +' ,'+ str(self.a_color) +' )'

		
#class名称XXXをkvファイル(小文字でOK)にしてファイルをリンクさせる(gui_sample.kv)
class GUI_sample(App):
	title = 'GUI sample'

if __name__ == '__main__':
    GUI_sample().run()
```

```Python:gui_sample.kv
Root_Layout:
	#第一階層
	BoxLayout:
		orientation:'vertical'
		#背景設定
		canvas:
			Color:
				rgba: 1,1,1,1
			Rectangle:
				size: self.size
				pos: self.pos
				
		TextInput:
			id:text_color
			text: '(1,1,1,1)'
			font_size:50
			multiline:False	#1行
			foreground_color:1,1,1,1	#文字色
		#第二階層(Slider_R/slider_text_R)
		BoxLayout:
			orientation:'horizontal'
			#第三階層(slider_R)
			Slider:
				id				:slider_R
				size_hint_x		:0.8	#x軸のサイズ
				max				:1		#カーソルの最大値
				min				:0		#カーソルの最小値
				value			:1		#初期値
				cursor_width	:16		#カーソルのサイズ(幅)
				cursor_height	:16		#カーソルのサイズ(高)
				on_touch_move	:root.sliderR_move()	#カーソルを動かしたときの動作
				on_touch_up  	:root.sliderR_move()	#カーソルをタッチした時の動作
			#第三階層(slider_text_R)
			TextInput:
				id:slider_text_R
				size_hint_x:0.2		#x軸のサイズ
				text: ''
				foreground_color:1,1,1,1	#文字色
				font_size:25
		#第二階層(Slider_G/slider_text_G)
		BoxLayout:
			orientation:'horizontal'
			#第三階層(slider_G)
			Slider:
				id				:slider_G
				size_hint_x		:0.8	#x軸のサイズ
				max				:1		#カーソルの最大値
				min				:0		#カーソルの最小値
				value			:1		#初期値
				cursor_width	:16		#カーソルのサイズ(幅)
				cursor_height	:16		#カーソルのサイズ(高)
				on_touch_move	:root.sliderG_move()	#カーソルを動かしたときの動作
				on_touch_up  	:root.sliderG_move()	#カーソルをタッチした時の動作
			#第三階層(slider_text_G)
			TextInput:
				id:slider_text_G
				size_hint_x:0.2		#x軸のサイズ
				text: ''
				foreground_color:1,1,1,1	#文字色
				font_size:25
		#第二階層(Slider_B/slider_text_B)
		BoxLayout:
			orientation:'horizontal'
			#第三階層(slider_B)
			Slider:
				id				:slider_B
				size_hint_x		:0.8	#x軸のサイズ
				max				:1		#カーソルの最大値
				min				:0		#カーソルの最小値
				value			:1		#初期値
				cursor_width	:16		#カーソルのサイズ(幅)
				cursor_height	:16		#カーソルのサイズ(高)
				on_touch_move	:root.sliderB_move()	#カーソルを動かしたときの動作
				on_touch_up  	:root.sliderB_move()	#カーソルをタッチした時の動作
			#第三階層(slider_text_B)
			TextInput:
				id:slider_text_B
				size_hint_x:0.2		#x軸のサイズ
				text: ''
				foreground_color:1,1,1,1	#文字色
				font_size:25
		#第二階層(Slider_A/slider_text_A)
		BoxLayout:
			orientation:'horizontal'
			#第三階層(slider_A)
			Slider:
				id				:slider_A
				size_hint_x		:0.8	#x軸のサイズ
				max				:1		#カーソルの最大値
				min				:0		#カーソルの最小値
				value			:1		#初期値
				cursor_width	:16		#カーソルのサイズ(幅)
				cursor_height	:16		#カーソルのサイズ(高)
				on_touch_move	:root.sliderA_move()	#カーソルを動かしたときの動作
				on_touch_up  	:root.sliderA_move()	#カーソルをタッチした時の動作
			#第三階層(slider_text_A)
			TextInput:
				id:slider_text_A
				size_hint_x:0.2		#x軸のサイズ
				text: ''
				foreground_color:1,1,1,1	#文字色
				font_size:25
```
</div></details>


+ **specファイル生成**
適当にフォルダを作って、main.pyとgui_sample.kvを配置。```buildozer init```でspecファイルを生成する。specファイルは、アプリ名や使用するpythonのライブラリ指定などapkパッケージにする際に必要なパラメータを調整するためにある。今回のアプリ内容であればデフォルトでも使えるので調整しない。

```console:cmd
# フォルダをcolor_barとする
$ cd color_bar
$ buildozer init #specファイル生成
File buildozer.spec created, ready to customize!
$ ls
buildozer.spec  gui_sample.kv  main.py
```
Specファイルのカスタマイズは下記参照。

https://qiita.com/tapitapi/items/ffbbb7eb26d1761bff8d

+ **ビルドする**
```buildozer -v android debug```でビルドする。初回ビルドはツールをいろいろ引っ張ってきたりするので数分かかる。また、1回だけだとErrorで終了することもある。その時は、再度ビルドコマンドを実行。成功すれば```\bin```配下にapkファイル```myapp-0.1-arm64-v8a_armeabi-v7a-debug.apk```が生成される。
```console:cmd
$ buildozer -v android debug
```

+ **apkファイルをAndroid端末へインストールする**
apkファイルをUSBなどでAndroid端末へ転送。apkを実行して、インストールする。My Applicationのアイコンが出てくるので、クリック。アプリが実行される。

## アプリ例
https://qiita.com/tapitapi/items/c431ec323aeab99261c9

https://qiita.com/tapitapi/items/51ddefc1dd8e810430c8

https://qiita.com/tapitapi/items/7c41dab03089ef9852fb

以上

[戻る](#目次)