main

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


Java


Создаем два файла Test.java и CallbackWrapper.java.


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 = kivy_test

# (str) Package name
package.name = kivy_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,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 = python3, \
               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 = 23b

# (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

android.gradle_dependencies = 'de.d0pam1n:pjsip-for-android:2.6'

#android.add_jars = %(source.dir)s/java_lib/AJVoIP.jar
#android.add_aars = %(source.dir)s/libs_lib/*.aar

# (list) Android additionnal libraries to copy into libs/armeabi
android.add_src = %(source.dir)s/java_src/*
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.library_references = /home/djvu/workspace/intercom/phone/libs/libs_arm64_v8a/*.*

#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 исходники.


Теперь соберем 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!')

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


Ссылки


Комментарии (0)