
Продолжение статьи 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.

Устанавливаем на телефон:
adb install -r ./bin/kivy_test-*.apk
Находим приложение на телефоне:

Подключаемся к консоли телефона и мониторим работу приложения:
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
На экране телефона видим следующую картинку:

Нажатие на кнопку закрывает приложение.
Linux
Так же main.py можно запустить из под linux, будет подключен класс java.lang.System и вызван метод:
System.out.println('I/python: Java: Hello world!')
Частично материал дублирует прошлую статью, сделано с целью просмотра всего в одном месте.
Спасибо за внимание.
