
Во вчерашней статье Python в Mobile development, в которой речь шла о библиотеке KivyMD (коллекции виджетов в стиле Material Design для использования их в кроссплатформенном фреймворке Kivy), в комментариях меня попросили рассказать о процессе сборки пакета для платформы Android. Для многих этот процесс, к сожалению, был и остается чем-то из ряда магического шаманства и не подъёмным для новичков делом. Что ж, давайте разбираться, так ли на самом деле все сложно и действительно ли я маг и волшебник...

Конечно, мог бы! Итак, вы написали свой код на Python и Kivy. Что нужно для того, чтобы это можно было запустить на Android устройствах? Перейдите в репозиторий KivyMD и вы увидите, что в этой инструкции уже давно прописаны шаги, которые позволят вам собрать APK пакет:
- Загрузите XUbuntu 18.04
Установите Virtual Box на свой компьютер.
Создайте новую виртуальную машину на основе загруженного образа XUbuntu
Запустите виртуальную машину XUbuntu, откройте терминал и выполните нижеследующую команду:
wget https://github.com/HeaTTheatR/KivyMD-data/raw/master/install-kivy-buildozer-dependencies.sh
chmod +x install-kivy-buildozer-dependencies.sh
./install-kivy-buildozer-dependencies.sh
Все! Теперь у вас есть виртуальная машина для сборки APK пакетов для приложений Kivy! Что дальше? Давайте, собственно, займемся сборкой тестового приложения. Создайте в домашнем каталоге вашей виртуальной машины директорию TestKivyMD с пустым файлом main.py:

Далее откройте файл main.py и напишите код нашего тестового приложения, которое будет использовать библиотеку KivyMD:
from kivy.lang import Builder from kivymd.app import MDApp KV = """ Screen: MDToolbar: title: "My firt app" elevation: 10 md_bg_color: app.theme_cls.primary_color left_action_items: [["menu", lambda x: x]] pos_hint: {"top": 1} MDRaisedButton: text: "Hello World" pos_hint: {"center_x": .5, "center_y": .5} """ class HelloWorld(MDApp): def build(self): return Builder.load_string(KV) HelloWorld().run()
Сохраните, откройте терминал в директории с файлом main.py и установите библиотеку KivyMD:
sudo pip3 install kivymd
После установки можно протестировать наш код:
python3 main.py
Результатом работы скрипта будет экран с Toolbar и одной кнопкой «Hello World»:

Дальше нам нужно создать файл спецификации buildozer.spec, который должен располагаться в той же директории, что и файл main.py:

Если вы не закрывали терминал (если терминал был закрыт, откройте его в директории TestKivyMD), введите команду:
buildozer init
Эта команда создаст дефолтный файл спецификации. Откройте его и отредактируйте:
[app] # (str) Title of your application title = KivyMDTest # (str) Package name package.name = kivymd_test # (str) Package domain (needed for android/ios packaging) package.domain = com.heattheatr # (str) Source code where the main.py live source.dir = . # (list) Source files to include (let empty to include all the files) source.include_exts = py,png,jpg,jpeg,ttf # (list) Application version version = 0.0.1 # (list) Application requirements # comma separated e.g. requirements = sqlite3,kivy requirements = python3,kivy==1.11.1,kivymd # (str) Supported orientation (one of landscape, sensorLandscape, portrait or all) orientation = portrait # (bool) Indicate if the application should be fullscreen or not fullscreen = 1 # (list) Permissions android.permissions = INTERNET,WRITE_EXTERNAL_STORAGE # (int) Target Android API, should be as high as possible. android.api = 28 # (int) Minimum API your APK will support. android.minapi = 21 # (str) Android NDK version to use android.ndk = 17c # (bool) If True, then skip trying to update the Android sdk # This can be useful to avoid excess Internet downloads or save time # when an update is due and you just want to test/build your package android.skip_update = False # (bool) If True, then automatically accept SDK license # agreements. This is intended for automation only. If set to False, # the default, you will be shown the license when first running # buildozer. android.accept_sdk_license = True # (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64 android.arch = armeabi-v7a [buildozer] # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output)) log_level = 2 # (int) Display warning if buildozer is run as root (0 = False, 1 = True) warn_on_root = 0 # (str) Path to build artifact storage, absolute or relative to spec file build_dir = ./.buildozer # (str) Path to build output (i.e. .apk, .ipa) storage bin_dir = ./bin
Здесь все понятно поэтому дополнительные комментарии излишни. Почитайте внимательно дефолтную спецификацию, в ней можно указать путь к иконке, пресплеш при загрузке приложения и многое другое. Я оставил лишь то, что нам сейчас нужно для сборки нашего тестового пакета. И, собственно, запускаем процесс сборки командой в терминале:
buildozer android debug
Можете смело идти на кухню и заваривать кофе, потому что в первый раз процесс загрузки и компиляции библиотек займет очень много времени. Все последующие сборки проходят за 10-20 секунд.
Кофе выпит и самое время заглянуть в терминал:

Вуаля! Наше приложение построено! Самое время закинуть его на смартфон и запустить:

Все работает! И оказывается не все так сложно, как казалось.
Также меня спрашивали:

Ни у Flutter ни у React Native нет преимуществ перед языком разметки Kivy Language, которая позволяет создавать и позиционировать лайоуты и виджеты. Как по мне, то, как строится UI во Flutter — это самое настоящее извращение. Придумать это мог только больной на голову человек. Чтобы не быть голословным, давайте посмотрим на код Flutter и код Kivy одного и того же простейшего приложения… Выглядеть оно будет следующим образом:

Ниже я привожу код из статьи Про Flutter, кратко: Основы:
import 'package:flutter/widgets.dart'; main() => runApp( Directionality( textDirection: TextDirection.ltr, child: Container( color: Color(0xFFFFFFFF), child: App(), ), ), ); class App extends StatelessWidget { @override Widget build(BuildContext context) { return Center( child: GestureDetector( // используется как обычный виджет onTap: () { // одно из свойств GestureDetector // Этот метод будет вызван, когда дочерний элемент будет нажат print('You pressed me'); }, child: Container( // нашей кнопкой будет контейнер decoration: BoxDecoration( // стилизуем контейнер shape: BoxShape.circle, // зададим ему круглую форму color: Color(0xFF17A2B8), // и покрасим его в синий ), width: 80.0, height: 80.0, ), ), ); } } class Counter extends StatefulWidget { // Изменяемое состояние хранится не в виджете, а внутри объекта особого класса, // создаваемого методом createState() @override State<Counter> createState() => _CounterState(); // Результатом функции является не просто объект класса State, // а обязательно State<ИмяНашегоВиджета> } class _CounterState extends State<Counter> { // Внутри него мы наконец-то можем объявить динамические переменные, // в которых мы будем хранить состояние. // В данном случае, это счетчик количества нажатий int counter = 0; // А дальше все очень просто, мы имплементируем точно такой же метод // для отрисовки виджетов, который мы использовали в классе Stateless виджета. @override Widget build(BuildContext context) { // И тут практически ничего не изменилось с нашего последнего примера, // а то что изменилось — я прокомментировал: return Center( child: GestureDetector( onTap: () { // В момент, когда кнопка нажата, мы увеличиваем значение // перменной counter. setState(() { // setState() необходим для того, чтобы вызвать методы // жизненного цикла виджета и сказать ему, что пора обновится ++counter; }); }, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, color: Color(0xFF17A2B8), ), width: 80.0, child: Center( child: Text( // выводим значение свойства counter '$counter', // чтобы следить за его изменением style: TextStyle(fontSize: 30.0), ), ), ), ), ); } }
А вот абсолютно тоже самое, но с использованием Kivy и KivyMD:
from kivy.lang import Builder from kivymd.app import MDApp KV = """ #:import get_color_from_hex kivy.utils.get_color_from_hex Screen: MDCard: MDLabel: value: 0 text: str(self.value) halign: "center" on_touch_down: self.value += 1 canvas.before: Color: rgba: get_color_from_hex("#4eaabe") Ellipse: pos: self.center[0] - dp(25), self.center[1] - dp(25) size: dp(50), dp(50) """ class HelloWorld(MDApp): def build(self): return Builder.load_string(KV) HelloWorld().run()
По-моему, вывод очевиден и не нуждается в моем комментировании…
Надеюсь, был вам полезен. Оставляю опрос на тему «Удалось ли вам построить приложение для Андроид».
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Удалось ли вам построить приложение для Андроид по этой инструкции?
66.67%Да38
33.33%Нет19
Проголосовали 57 пользователей. Воздержались 58 пользователей.
