在Android中使用Chaquopy调用Python代码时如何实现加载动画(解决主线程卡顿无响应问题)
问题分析
你遇到的核心问题是耗时的Python代码直接在Android主线程(UI线程)中执行,导致主线程被阻塞,无法及时刷新UI——你刚把ProgressBar设为VISIBLE,主线程就被Python计算占满了,根本没时间去渲染这个UI变化;等所有Python代码跑完,你又立刻把ProgressBar设为GONE,所以用户完全看不到加载动画,甚至可能触发ANR(应用无响应)弹窗。
解决方案:将Python代码移到后台线程执行
Android要求所有耗时操作必须放在后台线程,主线程只负责UI更新。我们可以用AsyncTask(适合Java项目,简单直接)把Python计算逻辑放到后台,同时在主线程正确控制ProgressBar的显示/隐藏。
修改后的完整代码示例
if (!Python.isStarted()) { Python.start(new AndroidPlatform(documentupload.this)); } b2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 先检查网络 if (!isConnected()) { Toast.makeText(documentupload.this,"No internet connection",Toast.LENGTH_SHORT).show(); return; } // 输入校验 String strUserName = et.getText().toString(); String numofdocword=docnum.getText().toString(); if(TextUtils.isEmpty(strUserName) && TextUtils.isEmpty(numofdocword)) { et.setError("Enter total number"); docnum.setError("Enter maximum number of words for document"); return; } else if(Integer.parseInt(strUserName)>100){ Toast.makeText(documentupload.this,"Number must be less than 100",Toast.LENGTH_SHORT).show(); return; } // 数据完整性校验 if(upload.length()==0) { Toast.makeText(documentupload.this, "Fill the required fields", Toast.LENGTH_SHORT).show(); return; } else if(adapter.getItemCount()==0){ Toast.makeText(documentupload.this, "Words must be present", Toast.LENGTH_SHORT).show(); return; } // 所有校验通过,启动异步任务 new PythonTask(strUserName, numofdocword, sf, Words.toString(), Definitions.toString()).execute(); } }); // 定义异步任务类,封装Python计算逻辑 private class PythonTask extends AsyncTask<Void, Void, ResultData> { private String strUserName; private String numofdocword; private String sf; private String wordsStr; private String definitionsStr; // 构造函数传递所需参数 public PythonTask(String strUserName, String numofdocword, String sf, String wordsStr, String definitionsStr) { this.strUserName = strUserName; this.numofdocword = numofdocword; this.sf = sf; this.wordsStr = wordsStr; this.definitionsStr = definitionsStr; } // 后台任务执行前:显示ProgressBar(主线程执行) @Override protected void onPreExecute() { super.onPreExecute(); pgb.setVisibility(View.VISIBLE); } // 后台线程执行:所有Python代码都在这里运行 @Override protected ResultData doInBackground(Void... voids) { try { Python py = Python.getInstance(); // 获取Python模块(轻量操作,可放在后台) final PyObject pyobj = py.getModule("count"); final PyObject pyspellcheck = py.getModule("grammer2"); final PyObject pywordsimilarity=py.getModule("forwordsimilarity"); final PyObject pysentencesimilarity = py.getModule("newsentencesimilarity"); final PyObject sencount=py.getModule("word2vec"); final PyObject pywordspell=py.getModule("spellcheck"); // 执行所有Python计算 PyObject obj = pyobj.callAttr("main", sf); String count=obj.toString(); PyObject spellobjgrammer = pyspellcheck.callAttr("main", sf); PyObject spellwordobj = pywordspell.callAttr("main", sf); List<PyObject> wordsimilarityobj = pywordsimilarity.callAttr("main",sf, wordsStr).asList(); String s=wordsimilarityobj.get(0).toString(); String res2 = wordsimilarityobj.get(1).toString(); String res3= wordsimilarityobj.get(2).toString(); List<PyObject> sentencesimilarityobj = pysentencesimilarity.callAttr("main",sf, definitionsStr).asList(); String similarsent=sentencesimilarityobj.get(0).toString(); String docsentcount=sentencesimilarityobj.get(1).toString(); PyObject sentence_countobj = sencount.callAttr("main", definitionsStr); // 封装结果返回给主线程 return new ResultData( count, spellobjgrammer.toString(), similarsent, s, res2, res3, sentence_countobj.toString(), strUserName, spellwordobj.toString(), docsentcount, numofdocword ); } catch (Exception e) { // 捕获Python执行异常,避免应用崩溃 e.printStackTrace(); return null; } } // 后台任务完成后:更新UI+启动新页面(主线程执行) @Override protected void onPostExecute(ResultData result) { super.onPostExecute(result); // 先隐藏加载动画 pgb.setVisibility(View.GONE); if (result == null) { // 处理执行失败的情况 Toast.makeText(documentupload.this, "任务执行失败,请重试", Toast.LENGTH_SHORT).show(); return; } // 跳转结果页面 Intent intent = new Intent(documentupload.this, result.class); intent.putExtra("numberwords", result.count); intent.putExtra("spellmistake", result.grammermistake); intent.putExtra("defitionsimilarity", result.similarsent); intent.putExtra("wordsimilarity", result.s); intent.putExtra("stop", result.res2); intent.putExtra("datacount", result.res3); intent.putExtra("sentence_count", result.sentence_count); intent.putExtra("totalnumber", result.strUserName); intent.putExtra("wordspelling", result.wordspellingmistake); intent.putExtra("docsentcount", result.docsentcount); intent.putExtra("filenumberofwords", result.numofdocword); startActivity(intent); finish(); } } // 辅助类:封装Python执行结果,方便传递数据 private static class ResultData { String count; String grammermistake; String similarsent; String s; String res2; String res3; String sentence_count; String strUserName; String wordspellingmistake; String docsentcount; String numofdocword; public ResultData(String count, String grammermistake, String similarsent, String s, String res2, String res3, String sentence_count, String strUserName, String wordspellingmistake, String docsentcount, String numofdocword) { this.count = count; this.grammermistake = grammermistake; this.similarsent = similarsent; this.s = s; this.res2 = res2; this.res3 = res3; this.sentence_count = sentence_count; this.strUserName = strUserName; this.wordspellingmistake = wordspellingmistake; this.docsentcount = docsentcount; this.numofdocword = numofdocword; } }
关键说明
- 前置校验提前做:把所有UI相关的输入校验、数据检查都放在主线程的点击事件里,避免启动不必要的后台任务。
- 后台线程只做计算:所有Python代码的调用都放在
doInBackground方法中,这个方法运行在独立的后台线程,不会阻塞主线程的UI渲染。 - UI操作仅在主线程:
onPreExecute(显示加载动画)和onPostExecute(隐藏动画、跳转页面)都是在主线程执行的,确保UI更新及时生效。 - 异常处理:在后台线程捕获Python代码可能抛出的异常,避免应用崩溃,同时在主线程给用户明确的错误提示。
额外优化建议
- 如果你的项目基于Android API 30+,
AsyncTask已被标记为废弃,可以改用ExecutorService配合Handler,或者迁移到Kotlin使用Coroutines,核心逻辑都是一致的:耗时操作放后台,主线程处理UI。 - 可以给ProgressBar添加旋转动画(比如系统默认的
?android:attr/progressBarStyleLarge样式),让加载状态更直观。
内容的提问来源于stack exchange,提问作者Noman Omer
相关产品推荐
相关产品推荐

