Pull to refresh

Kivy. Сборка пакетов под Android и никакой магии

Reading time 5 min
Views 53K

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



Конечно, мог бы! Итак, вы написали свой код на Python и Kivy. Что нужно для того, чтобы это можно было запустить на Android устройствах? Перейдите в репозиторий KivyMD и вы увидите, что в этой инструкции уже давно прописаны шаги, которые позволят вам собрать APK пакет:

  1. Загрузите 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()

По-моему, вывод очевиден и не нуждается в моем комментировании…

Надеюсь, был вам полезен. Оставляю опрос на тему «Удалось ли вам построить приложение для Андроид».
Only registered users can participate in poll. Log in, please.
Удалось ли вам построить приложение для Андроид по этой инструкции?
66.67% Да 36
33.33% Нет 18
54 users voted. 57 users abstained.
Tags:
Hubs:
+17
Comments 113
Comments Comments 113

Articles