Используйте скрипты и пакеты Python для расширения возможностей Android-приложений

Python — один из самых популярных языков в сообществе разработчиков из-за его простоты, надежности и большой экосистемы пакетов, которые делают его очень полезным в различных областях. Использование таких пакетов, как NumPy и SciPy, позволяет программистам легко выполнять высокоуровневые математические операции, недоступные в других языках программирования. Как насчет использования возможностей Python в приложениях для Android?

Chaquopy — это платформа, которая может помочь разработчикам запускать сценарии Python из кода Java/Kotlin в приложениях для Android. В отличие от других кросс-язычных библиотек, здесь нет проблем с NDK или нативным кодом, а установка проста. В этой статье мы рассмотрим Chaquopy, его архитектуру и использование с кодом Kotlin.

Содержание

Что такое Chaquopy и как он использует Python на Android?

Как и большинство межъязыковых взаимодействий, Python и Android имеют общую родословную C/C++, что позволяет им общаться через общую среду. NDK для Android позволяет разработчикам использовать собственные библиотеки (написанные на C/C++) в приложении для Android, что отлично подходит для высокопроизводительной графики и научных вычислений.

Chaquopy использует CPython, реализацию Python, написанную на языке C. В отличие от распространенного заблуждения, Python не является чисто интерпретируемым языком. Исходный код Python сначала компилируется в специальный байт-код, который затем интерпретируется CPython. CPython — это лишь одна из нескольких реализаций Python, другие включают PyPy, IronPython, Jython и т. д.

Команда Chaquopy создает CPython с набором инструментов Android NDK. CPython загружается из репозитория Maven Central с помощью плагина Chaquopy Gradle во время сборки проекта, и пользователям не нужно загружать NDK для этого процесса. Он также загружает среду выполнения Chaquopy, которая связывает код Java/Kotlin с Python через JNI.

Между тем, нам также понадобится диспетчер пакетов Python pip, который может загружать пакеты для интерпретатора. Популярные пакеты, такие как NumPy и SciPy, используют собственный код для выполнения вычислений с интенсивным использованием ЦП, которые необходимо собрать перед установкой. Итак, команда Chaquopy поддерживает свои собственные репозитории с собственными пакетами, созданными специально для архитектуры Android ARM. Разработчики этих пакетов не создают собственный код для цели Android из-за меньшего числа пользователей, поэтому команда Chaquopy создает их для цели Android и распространяет через собственный репозиторий.

Для чистых пакетов Python не требуется никаких внешних сборок, и интерпретатор Chaquopy может запускать их напрямую. Для общего обзора Chaquopy содержит три основных компонента:

  1. Плагин Chaquopy Gradle
  2. Среда выполнения Чакопи
  3. Репозиторий пакетов

1. Добавление Chaquopy в ваш Android-проект

1.1. Зависимости Gradle и спецификации ABI

Чтобы добавить Chaquopy в ваш новый/существующий проект Android, перейдите к сценарию build.gradle уровня проекта, где мы определяем подключаемые модули Gradle для проекта, и добавляем подключаемый модуль Chaquopy Gradle,

plugins {
    id 'com.android.application' version '7.3.0' apply false
    id 'com.android.library' version '7.3.0' apply false
    id 'org.jetbrains.kotlin.android' version '1.7.21' apply false
    id 'com.chaquo.python' version '13.0.0' apply false 
}

Далее, на уровне модуля build.gradle, мы включим плагин Chaquopy, а также укажем фильтры ABI,

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.chaquo.python'
}

android {
    ...
    defaultConfig {
        ...
        ndk {
            abiFilters "armeabi-v7a" //, "arm64-v8a", "x86", "x86_64"
        }
    }
    ...
}

Как упоминалось в официальной документации, интерпретатор Python — это нативный компонент, созданный с использованием Android NDK. NDK создает собственный код для определенных архитектур, таких как arm, x86 или x86_64. Разные устройства поддерживают разные архитектуры, поэтому мы можем включить только эту конкретную сборку интерпретатора Python, а не сборку для всех архитектур, что увеличивает размер приложения. Официальные документы Android говорят,

По умолчанию система сборки включает двоичные файлы для каждого ABI в один APK, также известный как толстый APK. Толстый APK значительно больше, чем тот, который содержит только двоичные файлы для одного ABI; компромиссом становится более широкая совместимость, но за счет более крупного APK. Настоятельно рекомендуется использовать Наборы приложений или Разделения APK, чтобы уменьшить размер ваших APK, сохраняя при этом максимальную совместимость устройств.

1.2. Версия Python и пакеты PIP

Далее мы настроим версию Python, которую необходимо собрать. Мы можем указать это, изменив уровень модуля build.gradle ,

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'com.chaquo.python'
}

android {
    ...
    defaultConfig {
        ...
        ndk {
            abiFilters "armeabi-v7a" //, "arm64-v8a", "x86", "x86_64"
        }
        python {
            version "3.8"
        }
    }
    ...
}

Разные версии Chaquopy поддерживают разные версии Python с разными минимальными требованиями к уровню API. Воспользуйтесь этой таблицей, чтобы найти версию, соответствующую вашим требованиям. Далее мы указываем пакеты, которые должны быть установлены в интерпретаторе Python.

defaultConfig {
    python {
        pip {
            // A requirement specifier, with or without a version number:
            install "scipy"
            install "requests==2.24.0"

            // An sdist or wheel filename, relative to the project directory:
            install "MyPackage-1.2.3-py2.py3-none-any.whl"

            // A directory containing a setup.py, relative to the project
            // directory (must contain at least one slash):
            install "./MyPackage"

            // "-r"` followed by a requirements filename, relative to the
            // project directory:
            install "-r", "requirements.txt"
        }
    }
}

Существуют разные способы установки пакетов в Chaquopy; это может быть имя пакета с определенной версией, пользовательский пакет или список пакетов requirements.txt.

2. Использование объектов Python из кода Java/Kotlin

В Python мы используем функции или элементы данных, принадлежащие модулю Python, который представляет собой файл .py, содержащий исходный код. Чтобы использовать любой элемент из модуля Python, первым шагом является размещение исходного кода Python в каталоге <project>/app/src/main/python.

# Contents of my_module.py

import numpy as np

def get_exec_details():
    return __file__

def sumOp( nums ):
    return sum( nums )

def powOp( a , x ):
    return a**x

def npMatrixSum( m , n ):
    mat = np.ones( ( m , n ) )
    mat_sum = np.sum( mat , axis=1 )
    return mat_sum

class Operations:

    num_ops = 2

    def meanOp( self , nums ):
        return sum( nums ) / len( nums )

    def maxOp( self , nums ):
        return max( nums )

nums_len = 10
nums_len_str = "ten"
ops = Operations()

Чтобы использовать элементы из my_module, мы используем метод Python.getModule, передавая имя модуля. Перед этим нам нужно включить Python для приложения, что можно выполнить в onCreate методе Application ,

class App : Application() {

    override fun onCreate() {
        super.onCreate()
        if( !Python.isStarted() ) {
            Python.start( AndroidPlatform( this ) )
        }
    }

}

Добавляя App к AndroidManifest.xml ,

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <application
        android:name=".App"
        ...
    </application>

</manifest>

А затем в MainActivity нам разрешено Python.getInstance (иначе мы получили бы PyException ),

val py = Python.getInstance()
val module = py.getModule( "my_module" )

2.1. Доступ к переменным (элементам данных)

Чтобы использовать член данных, например nums_len в my_module.py ,

val numsLength = module[ "nums_len" ]?.toInt()
println( "Nums Length is $numsLength" )
Nums Length is 10

Чтобы получить доступ к атрибутам объекта ops класса Operations,

val ops = module[ "ops" ]!!
println( "Operations: $ops" )
println( "num_ops : ${ ops[ "num_ops" ] }" )
println( "mean func : ${ ops[ "meanOp" ] }" )
Operations: <my_module.Operations object at 0xb9339ce8>
num_ops : 2
mean func : <bound method Operations.mean of <my_module.Operations object at 0xb9339ce8>>

2.2. Вызов функций

Поскольку функция в Python является объектом, доступ к функциям как к значениям module разрешен. Затем мы используем метод PyObject.call для передачи аргументов функции и получения результатов (если функция возвращает значение).

val sumFunc = module[ "sumOp" ]
val sum = sumFunc?.call( intArrayOf( 12 , 25 , 32 ) )
val powFun = module[ "powOp" ]
val pow = powFun?.call( 5 , 2 )
println( "Sum: $sum" )
println( "Pow: $pow" )
Sum: 69
Pow: 25

Чтобы получить доступ к функциям-членам из объекта ops,

val meanFunc = ops[ "meanOp" ]
val mean = meanFunc?.call( intArrayOf( 23 , 45 , 12 , 91 ) )
println( "Mean: $mean" )

// OR

val mean = ops.callAttr( "meanOp" , intArrayOf( 23 , 45 , 12 , 91 ) )
println( "Mean: $mean" )
Mean: 42.75

Вот пример, в котором функция Python использует numpy и возвращает результат типа np.ndarray.

# my_module.py
import numpy as np

def npMatrixSum( m , n ):
    mat = np.ones( ( m , n ) )
    mat_sum = np.sum( mat , axis=1 )
    return mat_sum
val npSumFunc = module[ "npMatrixSum" ]
val output = npSumFunc?.call( 2 , 3 )

// OR

val output = module.callAttr( "npMatrixSum" , 2 , 3 )

println( "Output: $output" )
println( "Output shape: ${output!![ "shape" ] }")
Output: [3. 3.]
Output shape: (2,)

Ресурсы

Конец

Надеюсь, я добавил новый инструмент в ваш набор инструментов для разработки под Android! Chaquopy — отличный инструмент с аккуратным синтаксисом и простой установкой. Убедитесь, что вы используете в своем следующем проекте Android. Продолжайте учиться и хорошего дня впереди!