Chaquopy: Android アプリでの Python の使用

Mar 29 2023
Python スクリプトとパッケージを使用して Android アプリを強化する
Python は、そのシンプルさ、堅牢性、および複数のドメインで非常に便利なパッケージの大規模なエコシステムにより、開発者コミュニティで最も人気のある言語の 1 つです。NumPy や SciPy などのパッケージを使用すると、プログラマーは、他のプログラミング言語では簡単に利用できない高レベルの数学演算を実行できます。

Python は、そのシンプルさ、堅牢性、および複数のドメインで非常に便利なパッケージの大規模なエコシステムにより、開発者コミュニティで最も人気のある言語の 1 つです。NumPy や SciPy などのパッケージを使用すると、プログラマーは、他のプログラミング言語では簡単に利用できない高レベルの数学演算を実行できます。Android アプリに Python のパワーをもたらすにはどうすればよいでしょうか?

Chaquopy は、開発者が Android アプリで Java/Kotlin コードから Python スクリプトを実行するのに役立つフレームワークです。他のクロスランゲージ ライブラリとは異なり、NDK やネイティブ コードの煩わしさはなく、インストールも簡単です。このストーリーでは、Chaquopy とそのアーキテクチャ、および Kotlin コードでの使用について説明します。

コンテンツ

  • Chaquopy とは何ですか? Android で Python をどのように使用しますか?
  • Android プロジェクトに Chaquopy を追加する
  • Java/Kotlin コードからの Python オブジェクトの使用
  • 資力

ほとんどの言語間インターフェースが機能するように、Python と Android には C/C++ という共通の祖先があり、共通の媒体を介して通信することができます。Android の NDK を使用すると、開発者は Android アプリ内で (C/C++ で記述された) ネイティブ ライブラリを使用できます。これは、高性能グラフィックスと科学計算に最適です。

Chaquopy は、C 言語で書かれた Python の実装である CPython を使用します。よくある誤解とは異なり、Python は純粋に解釈された言語ではありません。Python のソース コードは、最初に特別なバイトコードにコンパイルされ、次に CPython によって解釈されます。CPython は Python のいくつかの実装の 1 つに過ぎず、他には PyPy、IronPython、Jython などがあります。

チャコピー アーキテクチャ。出典:Malcolm Smith(許可取得済み)

Chaquopy チームは、Android の NDK ツールチェーンを使用して CPython を構築しています。CPython は、プロジェクトのビルド中に Chaquopy の Gradle プラグインによって Maven Central リポジトリからダウンロードされ、ユーザーはプロセスのために NDK をダウンロードする必要はありません。また、JNI を介して Java/Kotlin コードと Python のインターフェイスとなる Chaquopy ランタイムもダウンロードします。

pip一方、インタープリター用のパッケージをダウンロードできるPython のパッケージ マネージャーも必要です。人気のあるパッケージは、ネイティブ コードを使用しNumPySciPyCPU を集中的に使用する計算を実行するため、インストール前にビルドする必要があります。そのため、Chaquopy チームは、Android の ARM アーキテクチャ専用に構築されたネイティブ パッケージを使用して、独自のリポジトリを維持しています。これらのパッケージのメンテナーは、ユーザー数が少ないため、Android ターゲット用のネイティブ コードをビルドしません。そのため、Chaquopy チームは Android ターゲット用にパッケージをビルドし、独自のリポジトリを介して配布します。

純粋な Python パッケージの場合、外部ビルドは必要なく、Chaquopy のインタープリターで直接実行できます。ハイレベルな概要として、Chaquopy には 3 つの主要なコンポーネントが含まれています。

  1. Chaquopy Gradle プラグイン
  2. Chaquopy ランタイム
  3. パッケージ リポジトリ

1.1。Gradle の依存関係と ABI 仕様

新規/既存の Android プロジェクトに Chaquopy を追加するには、プロジェクト レベルの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 
}

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

ビルド システムのデフォルトの動作は、各 ABI のバイナリを 1 つの APK (ファット APKとも呼ばれます) に含めることです。ファット APK は、1 つの 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"
        }
    }
    ...
}

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. Java/Kotlin コードからの Python オブジェクトの使用

Python では、.pyソース コードを含むファイルである Python モジュールに属する関数またはデータ メンバーを使用します。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()

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

nums_lenのようにデータ メンバーを使用するには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>>

関数は 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

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

  • Chaquopy の公式ドキュメント
  • Chaquopy FAQ : このセクションには、Chaquopy に関するさまざまな質問への回答が含まれています。
  • GitHub の Chaquopy デモ

あなたの Android 開発ツールボックスに新しいツールが追加されたことを願っています! Chaquopy は、きちんとした構文と手間のかからないインストールを備えた優れたツールです。次の Android プロジェクトで必ず使用してください。学び続けて、良い一日を!