Chaquopy: Usando Python em Aplicativos Android

Mar 29 2023
Use scripts e pacotes Python para sobrecarregar aplicativos Android
Python é uma das linguagens mais populares na comunidade de desenvolvedores devido à sua simplicidade, robustez e um grande ecossistema de pacotes que o tornam super útil em vários domínios. O uso de pacotes como NumPy e SciPy permite que os programadores executem facilmente operações matemáticas de alto nível que, de outra forma, não estariam disponíveis em outras linguagens de programação.

Python é uma das linguagens mais populares na comunidade de desenvolvedores devido à sua simplicidade, robustez e um grande ecossistema de pacotes que o tornam super útil em vários domínios. O uso de pacotes como NumPy e SciPy permite que os programadores executem facilmente operações matemáticas de alto nível que, de outra forma, não estariam disponíveis em outras linguagens de programação. Que tal trazer o poder do Python para aplicativos Android?

Chaquopy é uma estrutura que pode ajudar os desenvolvedores a executar scripts Python a partir do código Java/Kotlin em aplicativos Android. Ao contrário de outras bibliotecas multilíngue, não há problemas de NDK ou código nativo e a instalação é fácil. Nesta história, exploraremos o Chaquopy, sua arquitetura e uso com código Kotlin.

Conteúdo

  • O que é o Chaquopy e como ele usa o Python no Android?
  • Adicionando Chaquopy em seu projeto Android
  • Usando objetos Python do código Java/Kotlin
  • Recursos

Assim como funciona a maioria das interfaces entre linguagens, Python e Android têm um ancestral comum de C/C++ que pode permitir que eles se comuniquem em um meio comum. O NDK do Android permite que os desenvolvedores usem bibliotecas nativas (escritas em C/C++) em um aplicativo Android, o que é ótimo para gráficos de alto desempenho e computação científica.

Chaquopy usa CPython, uma implementação de Python escrita em linguagem C. Ao contrário do equívoco comum, o Python não é uma linguagem puramente interpretada. O código-fonte do Python é primeiro compilado para um bytecode especial que é então interpretado pelo CPython. CPython é apenas uma das várias implementações de Python, outras incluem PyPy, IronPython, Jython etc.

Arquitetura Chaquopy. Fonte: Malcolm Smith (com permissões adquiridas)

A equipe do Chaquopy cria o CPython com a cadeia de ferramentas NDK do Android. O CPython é baixado do repositório Maven Central pelo plug-in Gradle do Chaquopy durante a construção do projeto e os usuários não precisam baixar o NDK para o processo. Ele também baixa os tempos de execução do Chaquopy, que faz a interface do código Java/Kotlin com o Python por meio do JNI.

Enquanto isso, também precisaríamos do gerenciador de pacotes do Python pip, que pode baixar pacotes para o interpretador. Pacotes populares gostam NumPye SciPyusam código nativo para executar cálculos intensivos de CPU, que precisam ser compilados antes da instalação. Portanto, a equipe Chaquopy mantém seus próprios repositórios com pacotes nativos criados especificamente para a arquitetura ARM do Android. Os mantenedores desses pacotes não constroem seu código nativo para o destino Android, devido a um número menor de usuários, portanto, a equipe Chaquopy os cria para o destino Android e os distribui por meio de seu próprio repositório.

Para pacotes Python puros, nenhuma construção externa é necessária e o interpretador do Chaquopy pode executá-los diretamente. Para uma visão geral de alto nível, o Chaquopy contém três componentes principais:

  1. Chaquopy Gradle Plugin
  2. Chaquopy Tempo de Execução
  3. Repositório de Pacotes

1.1. Dependências Gradle e especificações ABI

Para adicionar Chaquopy em seu projeto Android novo/existente, vá para o script de nível de projeto build.gradle, onde definimos plug-ins Gradle para o projeto e adicione o plug-in Gradle do Chaquopy,

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 
}

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"
        }
    }
    ...
}

O comportamento padrão do sistema de compilação é incluir os binários para cada ABI em um único APK, também conhecido como APK gordo . Um APK gordo é significativamente maior do que um contendo apenas os binários para uma única ABI; a compensação está ganhando compatibilidade mais ampla, mas às custas de um APK maior. É altamente recomendável que você aproveite os App Bundles ou APK Splits para reduzir o tamanho de seus APKs e, ao mesmo tempo, manter a compatibilidade máxima do dispositivo.

1.2. Versão Python e pacotes PIP

Em seguida, configuraremos a versão do Python que deve ser construída. Podemos especificar isso modificando o nível do módulo 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"
        }
    }
    ...
}

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"
        }
    }
}

2. Usando objetos Python do código Java/Kotlin

Em Python, usamos funções ou membros de dados que pertencem a um módulo Python, que é um .pyarquivo que contém o código-fonte. Para usar qualquer membro de um módulo Python, o primeiro passo é colocar o código-fonte Python no <project>/app/src/main/pythondiretório.

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

      
                
Location of the Python module relative to the Android project and its contents. Image Source: Image by author

class App : Application() {

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

}

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

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

</manifest>

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

Para usar um membro de dados, como nums_lenem my_module.py,

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

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

moduleComo uma função é um objeto em Python, é permitido acessar funções como valores de the . Em seguida, usamos PyObject.callo método para passar argumentos para uma função e obter os resultados (se a função retornar um valor)

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

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

# 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,)

  • Documentos oficiais do Chaquopy
  • Perguntas frequentes do Chaquopy : Esta seção contém respostas para várias perguntas sobre o Chaquopy
  • Demonstrações do Chaquopy no GitHub

Espero ter adicionado uma nova ferramenta à sua caixa de ferramentas de desenvolvimento Android! Chaquopy é uma ótima ferramenta com uma sintaxe elegante e instalação sem complicações. Certifique-se de usar em seu próximo projeto Android. Continue aprendendo e tenha um bom dia pela frente!