You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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;
    }
}

关键说明

  1. 前置校验提前做:把所有UI相关的输入校验、数据检查都放在主线程的点击事件里,避免启动不必要的后台任务。
  2. 后台线程只做计算:所有Python代码的调用都放在doInBackground方法中,这个方法运行在独立的后台线程,不会阻塞主线程的UI渲染。
  3. UI操作仅在主线程:onPreExecute(显示加载动画)和onPostExecute(隐藏动画、跳转页面)都是在主线程执行的,确保UI更新及时生效。
  4. 异常处理:在后台线程捕获Python代码可能抛出的异常,避免应用崩溃,同时在主线程给用户明确的错误提示。

额外优化建议

  • 如果你的项目基于Android API 30+,AsyncTask已被标记为废弃,可以改用ExecutorService配合Handler,或者迁移到Kotlin使用Coroutines,核心逻辑都是一致的:耗时操作放后台,主线程处理UI。
  • 可以给ProgressBar添加旋转动画(比如系统默认的?android:attr/progressBarStyleLarge样式),让加载状态更直观。

内容的提问来源于stack exchange,提问作者Noman Omer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 03:52:36