杰瑞科技汇

Android如何调用Python脚本?

Android 调用 Python 的核心思想是:在 Android 设备上运行一个 Python 环境,并通过某种通信机制(如 Socket、HTTP、gRPC)让 Android 应用(Java/Kotlin)与 Python 脚本进行数据交换和指令传递。

下面我将详细介绍几种主流的实现方法,从最简单到最完整,并给出各自的优缺点和适用场景。


使用 Termux(最简单,适合学习和原型验证)

这种方法利用 Android 上的终端模拟器 Termux 来运行 Python 脚本,然后通过 Android 的 Runtime.exec() 命令行调用。

原理

  1. 安装 Termux:在 Android 设备上安装 Termux 应用,它提供了一个 Linux 终端环境。
  2. 安装 Python:在 Termux 中使用包管理器安装 Python。
  3. 编写 Python 脚本:编写一个可以接受命令行参数或从标准输入读取数据的 Python 脚本。
  4. Android 调用:在 Android App 中,通过 ProcessBuilderRuntime.exec() 执行一个 shell 命令来启动 Termux 中的 Python 脚本,并传递数据。

步骤详解

  1. 在 Android 设备上安装 Termux

    • 从 F-Droid 或 Google Play Store 安装 Termux。
    • 打开 Termux,更新包列表并安装 Python:
      pkg update
      pkg install python
  2. 编写 Python 脚本

    • 在 Termux 的家目录下创建一个脚本,~/my_script.py

    • 这个脚本可以从命令行参数中读取数据,或者从标准输入读取数据。

    • 示例 (从参数读取):

      # my_script.py
      import sys
      if len(sys.argv) > 1:
          # 从第二个参数开始获取数据
          input_data = " ".join(sys.argv[1:])
          print(f"Hello from Python! Received data: {input_data}")
          # 做一些处理,比如反转字符串
          result = input_data[::-1]
          print(f"Processed result: {result}")
      else:
          print("No arguments provided.")
    • 示例 (从标准输入读取):

      # my_script_stdin.py
      import sys
      # 读取一行标准输入
      input_data = sys.stdin.readline()
      print(f"Hello from Python! Received data: {input_data.strip()}")
      # 做一些处理,比如计算字符串长度
      result = len(input_data)
      print(f"Processed result (length): {result}")
  3. 在 Android App (Kotlin) 中调用

    • 使用 ProcessBuilder 来构建并执行命令。

    • 重要:你需要获取 Termux 的可执行文件路径,通常在 /data/data/com.termux/files/usr/bin/

    • 调用 my_script.py (带参数):

      import android.os.Process
      import java.io.BufferedReader
      import java.io.File
      fun runPythonScriptWithArgs() {
          val pythonExecutable = "/data/data/com.termux/files/usr/bin/python"
          val scriptPath = "/data/data/com.termux/files/home/my_script.py"
          val dataToSend = "Hello Android!"
          // 检查文件是否存在
          if (!File(pythonExecutable).exists() || !File(scriptPath).exists()) {
              // 处理错误,例如文件未找到
              return
          }
          try {
              // 使用 ProcessBuilder 构建命令
              // 注意:命令和参数需要是分开的字符串
              val process = ProcessBuilder()
                  .command(pythonExecutable, scriptPath, dataToSend)
                  .redirectErrorStream(true) // 合并错误流和输出流
                  .start()
              // 读取 Python 脚本的输出
              val reader = BufferedReader(process.inputStream)
              val output = StringBuilder()
              var line: String?
              while (reader.readLine().also { line = it } != null) {
                  output.append(line).append("\n")
              }
              // 等待进程结束
              val exitCode = process.waitFor()
              if (exitCode == 0) {
                  // 成功,处理输出
                  println("Python script output:\n$output")
              } else {
                  // 失败,处理错误
                  println("Python script exited with code $exitCode")
              }
          } catch (e: Exception) {
              e.printStackTrace()
          }
      }
    • 调用 my_script_stdin.py (通过管道):

      fun runPythonScriptWithStdin() {
          val pythonExecutable = "/data/data/com.termux/files/usr/bin/python"
          val scriptPath = "/data/data/com.termux/files/home/my_script_stdin.py"
          val dataToSend = "Hello Android from stdin!"
          try {
              val process = ProcessBuilder()
                  .command(pythonExecutable, scriptPath)
                  .redirectErrorStream(true)
                  .start()
              // 向进程的标准输入写入数据
              val outputStream = process.outputStream
              outputStream.write((dataToSend + "\n").toByteArray()) // 写入数据并换行
              outputStream.close()
              // 读取输出
              val reader = BufferedReader(process.inputStream)
              val output = StringBuilder()
              var line: String?
              while (reader.readLine().also { line = it } != null) {
                  output.append(line).append("\n")
              }
              val exitCode = process.waitFor()
              if (exitCode == 0) {
                  println("Python script output:\n$output")
              } else {
                  println("Python script exited with code $exitCode")
              }
          } catch (e: Exception) {
              e.printStackTrace()
          }
      }

优点

  • 简单快捷:无需复杂的编译和配置,几分钟即可上手。
  • 轻量级:不需要完整的 Linux 内核。
  • 灵活性高:可以方便地使用任何 Python 库。

缺点

  • 依赖 Termux:用户设备上必须安装并打开 Termux。
  • 性能差:通过进程间通信和文本解析,不适合高频、大数据量的调用。
  • 用户体验差:后台运行一个终端进程,可能会被系统杀死,且不优雅。
  • 不安全:直接暴露了命令行接口,容易被恶意调用。

使用 Chaquopy(最推荐,用于生产环境)

Chaquopy 是一个商业插件(有免费版和付费版),它允许你直接在 Android Studio 项目中集成 Python 解释器,这是目前最成熟、最方便的解决方案。

原理

Chaquopy 将 Python 解释器(CPython)编译为 Android 的原生库(.so 文件),并将其打包到你的 APK 中,它提供了一个 Java/Kotlin API 来加载 Python 模块,并自动处理数据类型转换(如 Java List -> Python list)。

步骤详解

  1. build.gradle (Module: app) 中添加依赖

    plugins {
        id 'com.chaquo.python' version '15.0.0' // 使用最新版本
    }
    android {
        // ...
        defaultConfig {
            // ...
            python {
                // 指定 Python 版本
                version "3.8"
                // 指定要打包的 Python 模块或文件
                pip {
                    // 安装第三方库,numpy
                    install "numpy"
                    // 或者指定你自己的本地库路径
                    // sourceSets {
                    //     main {
                    //         python.srcDir "src/main/python"
                    //     }
                    // }
                }
            }
        }
    }
  2. 创建 Python 模块

    • src/main/ 目录下创建一个 python 文件夹。

    • python 文件夹中创建你的 Python 模块,my_module.py

      # src/main/python/my_module.py
      def process_data(data_list):
          """一个简单的函数,接收一个列表,返回处理后的列表"""
          print(f"Python received: {data_list}")
          # 假设我们想对列表中的每个数字求平方
          result = [x * x for x in data_list]
          print(f"Python returning: {result}")
          return result
      class MyPythonClass:
          def __init__(self, name):
              self.name = name
          def greet(self):
              return f"Hello from {self.name} in Python!"
  3. 在 Kotlin 代码中调用

    • 在 Activity 或其他地方,通过 Chaquopy 提供的 API 调用 Python 代码。

    • 初始化 Python 环境:在 Application 类或 Activity 的 onCreate 中。

      class MyApplication : Application() {
          override fun onCreate() {
              super.onCreate()
              // 初始化 Chaquopy
              Chaquopy.start(this)
          }
      }
    • 调用 Python 函数

      import com.chaquo.python.Python
      import com.chaquo.python.android.AndroidContext
      class MainActivity : AppCompatActivity() {
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.activity_main)
              // 获取 Python 实例
              val py = Python.getInstance()
              // 获取 Python 模块
              val myModule = py.getModule("my_module")
              // 调用 Python 函数,并传递数据
              val inputData = listOf(1, 2, 3, 4, 5)
              // Chaquopy 会自动进行类型转换
              val processedData = myModule.callAttr("process_data", inputData).toJava(List::class.java)
              println("Kotlin received from Python: $processedData") // 输出: [1, 4, 9, 16, 25]
              // 调用 Python 类
              val pythonClass = myModule.get("MyPythonClass").call("Alice")
              val greeting = pythonClass.callAttr("greet").toString()
              println(greeting) // 输出: Hello from Alice in Python!
          }
      }

优点

  • 无缝集成:直接在 Android Studio 中开发,像调用 Java/Kotlin 代码一样调用 Python。
  • 高性能:Python 代码作为本地库运行,IPC 开销极小。
  • 类型安全:自动进行数据序列化和反序列化,无需手动处理 JSON 或文本。
  • 打包简单:Python 依赖和代码会自动打包进 APK,用户无需额外安装任何东西。
  • 支持 PyPI:可以方便地通过 pip 安装绝大多数 Python 库。

缺点

  • 商业软件:免费版有功能限制(例如不能打包 Python 标准库之外的模块),完整功能需要付费。
  • APK 体积增大:打包 Python 解释器和依赖库会使 APK 文件体积显著增加(几十MB)。
  • 平台限制:目前主要支持 Android 和 iOS。

使用 gRPC 或 Socket 通信(最灵活,适合复杂应用)

这种方法的核心是将 Python 代码作为一个独立的服务在后台运行,Android 应用通过网络(本地 Socket 或 HTTP)与该服务通信。

原理

  1. Python 服务端:编写一个 Python 脚本,使用 Flask (HTTP) 或 grpc (gRPC) 框架暴露一个 API 接口。
  2. Android 客户端:在 Android App 中,使用网络库(如 OkHttp, Retrofit for HTTP;或 gRPC Android 库)向 Python 服务端发送请求并接收响应。

步骤详解

  1. Python 服务端 (使用 Flask 示例)

    • 安装 Flask: pip install Flask

    • 创建 server.py:

      from flask import Flask, request, jsonify
      import numpy as np
      app = Flask(__name__)
      @app.route('/process', methods=['POST'])
      def process():
          data = request.json
          numbers = data.get('numbers', [])
          # 使用 numpy 进行计算
          arr = np.array(numbers)
          squared = arr ** 2
          return jsonify({
              'status': 'success',
              'original': numbers,
              'squared': squared.tolist() # numpy array 转为 list
          })
      if __name__ == '__main__':
          # 在 0.0.0.0 上监听,以便从其他设备访问
          # 5001 是一个常用的非特权端口
          app.run(host='0.0.0.0', port=5001)
    • 如何运行

      • 在开发机上:直接 python server.py
      • 在 Android 设备上:需要一种方式让 Python 脚本持续运行,可以使用 Termux + nohupsystemd 服务,或者更复杂的方案如 Pyodide (WebAssembly) 运行在 WebView 中。
  2. Android 客户端 (使用 Retrofit + OkHttp 示例)

    • 添加依赖:

      // build.gradle
      implementation 'com.squareup.retrofit2:retrofit:2.9.0'
      implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    • 定义 API 接口:

      import retrofit2.Call
      import retrofit2.http.Body
      import retrofit2.http.POST
      data class ProcessRequest(val numbers: List<Int>)
      data class ProcessResponse(val status: String, val original: List<Int>, val squared: List<Int>)
      interface ApiService {
          @POST("/process")
          fun processData(@Body request: ProcessRequest): Call<ProcessResponse>
      }
    • 创建 Retrofit 实例并发起请求:

      val retrofit = Retrofit.Builder()
          .baseUrl("http://127.0.0.1:5001/") // Python 服务运行在本地
          .addConverterFactory(GsonConverterFactory.create())
          .build()
      val apiService = retrofit.create(ApiService::class.java)
      val request = ProcessRequest(listOf(1, 2, 3, 4, 5))
      apiService.processData(request).enqueue(object : Callback<ProcessResponse> {
          override fun onResponse(call: Call<ProcessResponse>, response: Response<ProcessResponse>) {
              if (response.isSuccessful) {
                  val result = response.body()
                  println("Received: ${result?.squared}") // 输出: [1, 4, 9, 16, 25]
              } else {
                  println("Error: ${response.errorBody()}")
              }
          }
          override fun onFailure(call: Call<ProcessResponse>, t: Throwable) {
              println("Network failure: ${t.message}")
          }
      })

优点

  • 高度解耦:Android 端和 Python 端可以独立开发、部署和更新。
  • 技术栈灵活:Python 端可以使用任何 Web 框架,Android 端可以使用任何网络库。
  • 可扩展性强:可以轻松将服务部署到远程服务器,实现真正的客户端-服务器架构。
  • 适合复杂逻辑:非常适合将计算密集型任务卸载到云端或本地服务器。

缺点

  • 架构复杂:需要处理网络通信、错误处理、序列化等。
  • 延迟:网络通信会引入额外的延迟。
  • 需要持续运行的服务:在 Android 设备上维持一个后台 Python 服务的运行比较麻烦。

总结与选择建议

方法 核心原理 优点 缺点 适用场景
Termux 通过命令行调用后台进程 简单、快速、轻量 性能差、依赖Termux、体验不佳 学习、原型验证、个人工具
Chaquopy 将Python解释器打包进APK 无缝集成、高性能、类型安全 APK体积大、商业软件(有免费版) 生产环境、App集成、需要高性能调用
gRPC/Socket 网络通信(客户端-服务器) 架构灵活、可扩展、技术栈自由 架构复杂、有网络延迟、需后台服务 复杂应用、微服务、云端计算、将Python作为独立服务

如何选择?

  • 如果你是初学者,只是想快速验证一个想法:使用 Termux,它能让你最快地跑通流程。
  • 如果你正在开发一个商业 App 或一个功能完善的应用,需要将 Python 逻辑深度集成进去强烈推荐 Chaquopy,它的便利性和性能优势远超其他方法,是目前 Android 集成 Python 的工业级标准。
  • 如果你的应用架构复杂,或者 Python 逻辑需要运行在远程服务器上:使用 gRPC/Socket 通信,这是一种更通用、更灵活的架构模式,尤其适合跨设备或跨平台的服务调用。
分享:
扫描分享到社交APP
上一篇
下一篇