在Android(Kotlin)中用Chaquopy运行Quart服务时跨语言调用线程错误排查
问题根源
- 错误实例化Activity:Python代码中
activity = MainActivity()完全不符合Android开发规范——Activity对象必须由系统框架创建和管理,手动new的实例不具备完整生命周期,也无法正常访问UI组件。 - 线程违规操作:Android的所有UI操作(比如调用
webView.evaluateJavascript)必须在主线程执行,而Quart的after_serving回调运行在Python的后台线程中,直接调用Activity方法会触发线程检查错误。 - 冗余Looper调用:在启动Python服务器的后台线程中调用
Looper.prepare()和Looper.loop()完全多余,反而会混淆线程模型,引发addObserver必须在主线程调用的错误。
解决方案
步骤1:传递合法的Activity实例给Python
在Kotlin中启动Python时,将当前已由系统创建的MainActivity实例作为参数传给Python模块,而非让Python手动创建。
步骤2:强制UI操作在主线程执行
修改MainActivity中的closeLoader方法,确保所有UI相关逻辑都在主线程运行。
步骤3:移除后台线程的Looper代码
Quart本身是异步框架,启动Python服务器的后台线程不需要Looper支持,直接运行即可。
修改后的代码
Kotlin代码(MainActivity.kt)
package com.example.chatterbot import android.annotation.SuppressLint import android.net.http.SslError import android.os.Bundle import android.util.Log import android.webkit.SslErrorHandler import android.webkit.WebView import android.webkit.WebViewClient import androidx.appcompat.app.AppCompatActivity import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.chaquo.python.Python import com.chaquo.python.android.AndroidPlatform class MainActivity : AppCompatActivity() { private val tag: String = "MainActivity" private lateinit var webView: WebView private lateinit var swipeRefreshLayout: SwipeRefreshLayout @SuppressLint("SetJavaScriptEnabled") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 在后台线程启动Python服务器,无需Looper Thread { Log.i(tag, "Starting server") if (!Python.isStarted()) { Python.start(AndroidPlatform(this)) } try { val py = Python.getInstance() // 将当前Activity实例传递给Python模块 py.getModule("app").callAttr("run", this) } catch (e: Exception) { Log.e(tag, "Error starting Python server: ${e.message}") } }.start() // WebView初始化 Log.i(tag, "Starting webview") webView = findViewById(R.id.webview) webView.webViewClient = object : WebViewClient() { override fun onReceivedSslError( view: WebView?, handler: SslErrorHandler?, error: SslError? ) { handler?.proceed() // 仅测试环境使用,生产环境禁止忽略SSL错误 } } webView.settings.javaScriptEnabled = true webView.loadUrl("file:///android_asset/iframe/iframe.html") swipeRefreshLayout = findViewById(R.id.swipe_refresh_layout) swipeRefreshLayout.setOnRefreshListener { webView.reload() swipeRefreshLayout.isRefreshing = false } } // 确保UI操作强制在主线程执行 fun closeLoader() { runOnUiThread { Log.i(tag, "Close loader") webView.evaluateJavascript("closeLoader();", null) } } }
Python代码(app.py)
from quart import Quart, render_template app = Quart(__name__) activity = None # 存储从Kotlin传递来的合法Activity实例 @app.route('/') async def index(): return await render_template('index.html') @app.after_serving async def after_startup(): print("Server ready") # 调用Activity的closeLoader方法,该方法内部已保证主线程执行 activity.closeLoader() def run(activity_instance): global activity activity = activity_instance app.run(host="0.0.0.0", port=8000, debug=True)
内容的提问来源于stack exchange,提问作者Carl L
相关产品推荐
相关产品推荐

