Java из Python (Kivy) и обратно на Android

от автора

main

Продолжение статьи C/C++ из Python (ctypes) на Android. Теперь поговорим о том как использовать Java из Python, ведь для android это родной язык и почти все работающие библиотеки и нужные вещи написаны на нем. Научимся создавать классы, вызывать методы, возвращать результат и дергать callback-и. На Linux аналогично работает.

Java

Создаем два файла Test.java и CallbackWrapper.java. И засовываем их в папку java/some/kivy_test в корне проекта. В дальнейшем папку java подключим в buildozer.spec.

Test.java:

package some.kivy_test;  // Подключение интерфейса CallbackWrapper из CallbackWrapper.java import some.kivy_test.CallbackWrapper;  /**     \brief Тестовый класс.      Содержит набор методов, вызываемые из python и в python (main.py). */ public class Test {     CallbackWrapper wrapper = null;      /**         Конструктор класса         \param callback_wrapper указатель на callback_wrapper из main.py     */     public Test(CallbackWrapper callback_wrapper) {         this.wrapper = callback_wrapper;     }      /**         Функция возврата строки         \return Возвращает строку     */     public String hello() {         return "Hello from Test.java";     }      /**         Функция возврата строки         \param arg1 строка         \return Возвращает строку     */     public String hello2(String arg1) {         return arg1;     }      /**         Функция вызова callback1 из callback_wrapper (main.py)     */     public void callback1() {         this.wrapper.callback1();     }      /**         Функция вызова callback2 из callback_wrapper (main.py)     */     public void callback2() {         this.wrapper.callback2("callback");     }      /**         Функция вызова callback3 из callback_wrapper (main.py)     */     public void callback3() {         this.wrapper.callback3(11, (byte) 22);     } }

Класс Test продемонстрирует рабочий функционал:

  • создание класса;
  • вызов методов класса;
  • вызов callback-ов.

CallbackWrapper.java:

package some.kivy_test;  // Обертка для python callback-ов public interface CallbackWrapper {     public void callback1();     public boolean callback2(String arg1);     public int callback3(int arg1, byte arg2); }

CallbackWrapper — интерфейс методов Java реализованных в python.

Python

Здесь нам понадобится фреймворк Kivy, buildozer — утилита для создания apk пакетов и jnius — библиотека доступа к классам Java. Установку и настройку проводил по статье: Kivy. Сборка пакетов под Android и никакой магии

Установка kivy, buildozer & jnius

sudo pip3 install kivy sudo pip3 install buildozer sudo pip3 install pyjnius

Установив kivy, buildozer и jnius приступим к созданию тестовой программы. Создадим папку под нее:

mkdir android_python cd android_python

Теперь создадим main.py, это точка запуска нашей программы.

touch main.py

И заполним его:

#!/usr/bin/python3 #-*- coding: utf-8 -*-  import kivy kivy.require("2.1.0") from kivy.app import App from kivy.uix.button import Button  from kivy.utils import platform  import jnius from jnius import cast from jnius import autoclass, PythonJavaClass, java_method  # Подключение класса System System = autoclass('java.lang.System')  # Подключение классов Android if platform == 'android':     PythonActivity = autoclass('org.kivy.android.PythonActivity')     currentActivity = cast('android.app.Activity', PythonActivity.mActivity)  # Класс графики, который создает кнопку для выхода из приложения. class ButtonApp(App):      def build(self):         # use a (r, g, b, a) tuple         btn = Button(text ="Push Me !",                    font_size ="20sp",                    background_color = (1, 1, 1, 1),                    color = (1, 1, 1, 1),                    size_hint = (.2, .1),                    pos_hint = {'x':.4, 'y':.45})          # bind() use to bind the button to function callback         btn.bind(on_press = self.callback)         return btn      # callback function tells when button pressed     def callback(self, event):         if platform == 'android':             currentActivity.finishAndRemoveTask()          System.exit(0)  ## # Класс оповещений событий из Java ## class CallbackWrapper(PythonJavaClass):     __javacontext__ = 'app'      # Указываем что этот класс является реализацией интерфейса     # CallbackWrapper из пакета some.kivy_test     __javainterfaces__ = ['some/kivy_test/CallbackWrapper']      def __init__(self):         super().__init__()      ##     # Реализация callback-ов описанных в CallbackWrapper.java.     # Имена методов должны быть разные.     ##     @java_method('()V')     def callback1(self):         print("Python: @java_method('()V')")      @java_method('(Ljava/lang/String;)Z')     def callback2(self, arg1):         print("Python: @java_method('(Ljava/lang/String;)Z'), ", arg1)         return True      @java_method('(IB)I')     def callback3(self, arg1, arg2):         print("Python: @java_method('(IB)I'), ", arg1, arg2)         return 555  ## #  Старт. ## if __name__ == "__main__":     # Печать функций python     print("Python: Hello world!")      # Вызов метода печати из класса System.     System.out.println('I/python: Java: Hello world!')      if platform == 'android':         # Получение указателя на класс Test из Test.java         Test = autoclass('some.kivy_test.Test')          # Создание класса с callback-ми, переменная содержащая          # объект класса должна быть глобальной или быть переменной          # внутри класса. Иначе объект Python выходит за рамки          # интерпретатора Python, даже если ссылка на него все еще          # существует в Java.         global callback_wrapper         callback_wrapper = CallbackWrapper()          # Создание класса Test из Test.java, передаем в него указатель          # на класс callback-ов         test = Test(callback_wrapper)          # Демонстрация вызовов методов из класса Test         print("Python: ", test.hello())         print("Python: ", test.hello2("test.hello2()"))          # Демонстрация вызовов методов CallbackWrapper определенных         # здесь из класса Test         test.callback1()         test.callback2()         test.callback3()      # Отрисовка графики приложения     ButtonApp().run() 

Callback-и из CallbackWrapper создаются через декоратор java_method. Подробное описание сигнатурного формата java_method.

main.py cоздает простую графическую программу с одной кнопкой при нажатии которой произойдет закрытие приложения. Основная задача статьи показать как работать с Java, результат работы увидим в консоли.

Buildozer

Так же нам понадобится файл спецификации buildozer, описывающий правила сборки пакета apk. Создаем его:

touch buildozer.spec

Заполняем:

[app]  # (str) Title of your application title = KivyTest  # (str) Package name package.name = kivy_test  # (str) Package domain (needed for android/ios packaging) package.domain = some.kivy_test  # (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,kv,mp3, #,so,2,6,2,1,a  source.include_patterns = img/*, font/*, ui/*, music/*  # (list) Application version version = 0.0.28  # (list) Application requirements # comma separated e.g. requirements = sqlite3,kivy requirements = android,                python3,                hostpython3,                kivy,                jnius  # (str) Custom source folders for requirements # Sets custom source for any requirements with recipes # requirements.source.kivy = ../../kivy #requirements.source.libtest = lib/libtest  # (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 = WRITE_EXTERNAL_STORAGE  # (int) Target Android API, should be as high as possible. android.api = 31  # (int) Minimum API your APK will support. android.minapi = 21  # (str) Android NDK version to use android.ndk = 25b  # (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.archs = arm64-v8a  # (list) Android additionnal libraries to copy into libs/armeabi android.add_src = %(source.dir)s/java android.add_libs_arm64_v8a = %(source.dir)s/libs/libs_arm64_v8a/*.* android.add_libs_armeabi_v7a = %(source.dir)s/libs/libs_armeabi-v7a/*.*  #android.copy_libs = 1  [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.spec собирает приложения под архитектуру arm64-v8a.
В поле android.add_src указывается папка в которой находятся java исходники. Структура папок с исходниками должна совпадать со структурой имени java пакета.

Теперь соберем apk пакет:

buildozer android debug

Операция очень долгая и растянется на несколько десятков минут. Так же потребуется порядка 1.5 GB свободного места, т.к. buildozer подтянет все необходимые библиотеки для сборки.
После удачного завершения в папке bin соберется пакет kivy_test-0.0.1-arm64-v8a-debug.apk.

Android

На телефоне нужно включить режим отладки по USB и разрешить установку через USB.

main

Устанавливаем на телефон:

adb install -r ./bin/kivy_test-*.apk

Находим приложение на телефоне:

main

Подключаемся к консоли телефона и мониторим работу приложения:

adb logcat | grep python

Запускаем и получаем следующее — Java отработала без проблем:

I/python  (21423): [INFO   ] [Text        ] Provider: sdl2 I/python  (21423): Python: Hello world! I/System.out(21423): I/python: Java: Hello world! I/python  (21423): Python:  Hello from Test.java I/python  (21423): Python:  test.hello2() I/python  (21423): Python: @java_method('()V') I/python  (21423): Python: @java_method('(Ljava/lang/String;)Z'),  callback I/python  (21423): Python: @java_method('(IB)I'),  11 22 I/python  (21423): [INFO   ] [Window      ] Provider: sdl2 I/python  (21423): [INFO   ] [GL          ] Using the "OpenGL ES 2" graphics system

На экране телефона видим следующую картинку:

main

Нажатие на кнопку закрывает приложение.

Linux

Так же main.py можно запустить из под linux, будет подключен класс java.lang.System и вызван метод:

System.out.println('I/python: Java: Hello world!')

Частично материал дублирует прошлую статью, сделано с целью просмотра всего в одном месте.
Спасибо за внимание.

Ссылки


ссылка на оригинал статьи https://habr.com/ru/articles/683536/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *