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

Android Studio中结合ExecutorService使用ProgressBar实现后台加载及空指针问题解决

问题根因与修复方案

空指针直接触发原因

  • 后台任务执行过程中如果用户退出当前Activity,pro 这个ProgressBar实例已经被系统回收,此时后台线程的Runnable还持有Activity引用,调用pro.setVisibility()就会触发空指针。
  • 隐性违规操作:你在Executor的后台子线程里执行了findViewById获取TableLayout、创建表头View、调用addView添加布局的操作,所有UI操作都必须在主线程执行,这部分违规操作也可能引发空指针或渲染异常。

其他影响体验的问题

  • ProgressBar隐藏时机错误:你在数据还没开始渲染到TableLayout的时候就把ProgressBar关了,用户看不到加载过程,优化交互的目的没达到。
  • 冗余代码拖慢速度:你在遍历Cursor的循环里加了一层for(int l=0;l<=cursor.getColumnCount();l++)的循环,每一行数据会重复创建N次TableRow,完全是无效代码,会大幅增加渲染耗时。

修复后的代码

Vocab.java

public class Vocab extends AppCompatActivity {
    String idd, word, wmean, wsynon, wsmean;
    TableLayout tLayout3;
    ProgressBar pro;
    ExecutorService executor;
    Handler mainHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_vocab);
        // 所有View初始化统一放在主线程的onCreate里
        pro = findViewById(R.id.progressBar);
        tLayout3 = findViewById(R.id.vtable);
        mainHandler = new Handler(Looper.getMainLooper());
        executor = Executors.newSingleThreadExecutor();
        
        pro.setVisibility(View.VISIBLE);
        executor.execute(() -> {
            // 后台线程只做数据加载的耗时操作,不碰UI
            OpenDatabase myDbHelper = new OpenDatabase(Vocab.this);
            Cursor cursor = null;
            try {
                myDbHelper.createDataBase();
                myDbHelper.openDataBase();
                cursor = myDbHelper.viewData();
            } catch (Exception e) {
                e.printStackTrace();
            }
            final Cursor dataCursor = cursor;
            // 所有UI操作切到主线程执行
            mainHandler.post(() -> {
                if (isFinishing() || isDestroyed()) {
                    // Activity已经销毁,直接返回避免空指针
                    return;
                }
                // 先加表头
                TableRow tbrow0 = new TableRow(Vocab.this);
                tbrow0.setPadding(10,10,10,10);
                TextView tv0 = new TextView(Vocab.this);
                tv0.setText(" No ");
                tv0.setTypeface(tv0.getTypeface(), Typeface.BOLD);
                tv0.setTextSize(18);
                tv0.setTextColor(Color.rgb(0,72,186));
                tv0.setPadding(0,0,0,0);
                tbrow0.addView(tv0);

                TextView tv1 = new TextView(Vocab.this);
                tv1.setText("   Word      ");
                tv1.setGravity(0);
                tv1.setTextSize(18);
                tv1.setTextColor(Color.rgb(0,72,186));
                tv1.setTypeface(tv1.getTypeface(), Typeface.BOLD);
                tv1.setPadding(45,0,0,0);
                tbrow0.addView(tv1);

                TextView tv2 = new TextView(Vocab.this);
                tv2.setText("Meaning        ");
                tv2.setTypeface(tv2.getTypeface(), Typeface.BOLD);
                tv2.setTextSize(18);
                tv2.setTextColor(Color.rgb(0,72,186));
                tv2.setPadding(63,0,0,0);
                tbrow0.addView(tv2);

                TextView tv3 = new TextView(Vocab.this);
                tv3.setText("Antonym    ");
                tv3.setTypeface(tv3.getTypeface(), Typeface.BOLD);
                tv3.setTextSize(18);
                tv3.setTextColor(Color.rgb(0,72,186));
                tv3.setPadding(75,0,9,0);
                tbrow0.addView(tv3);

                TextView tv4 = new TextView(Vocab.this);
                tv4.setText("         Meaning      ");
                tv4.setTextSize(18);
                tv4.setTextColor(Color.rgb(0,72,186));
                tv4.setTypeface(tv4.getTypeface(), Typeface.BOLD);
                tv4.setPadding(13,0,9,0);
                tbrow0.addView(tv4);
                tLayout3.addView(tbrow0);

                // 渲染数据行,删掉多余的for循环
                if (dataCursor != null && dataCursor.moveToFirst()) {
                    do {
                        idd = dataCursor.getString(0);
                        word = dataCursor.getString(1);
                        wmean = dataCursor.getString(2);
                        wsynon = dataCursor.getString(3);
                        wsmean = dataCursor.getString(4);

                        TableRow tbrow = new TableRow(Vocab.this);
                        tbrow.setPadding(10,10,10,10);
                        TextView tid = new TextView(Vocab.this);
                        tid.setText(" "+idd+" ");
                        tid.setTextColor(Color.rgb(0,0,0));
                        tid.setPadding(9,0,0,0);
                        tbrow.addView(tid);

                        TextView tbase = new TextView(Vocab.this);
                        tbase.setText(" " + word);
                        tbase.setTextColor(Color.rgb(255, 140, 0));
                        tbase.setPadding(45,0,0,0);
                        tbrow.addView(tbase);

                        TextView tmean = new TextView(Vocab.this);
                        tmean.setText(" " + wmean+" ");
                        tmean.setPadding(61,0,0,0);
                        tmean.setTextColor(Color.rgb(255, 140, 0));
                        tbrow.addView(tmean);

                        TextView tpast = new TextView(Vocab.this);
                        tpast.setText(" " + wsynon);
                        tpast.setPadding(75,0,9,0);
                        tpast.setTextColor(Color.rgb(175,0,42));
                        tbrow.addView(tpast);

                        TextView tparti = new TextView(Vocab.this);
                        tparti.setText("     " + wsmean +"     ");
                        tparti.setPadding(32,0,9,0);
                        tparti.setTextColor(Color.rgb(175,0,42));
                        tbrow.addView(tparti);

                        tLayout3.addView(tbrow);
                    } while (dataCursor.moveToNext());
                    dataCursor.close();
                }
                // 所有内容渲染完再隐藏ProgressBar
                pro.setVisibility(View.GONE);
            });
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Activity销毁时关闭线程池,避免内存泄漏和空指针
        if (executor != null) {
            executor.shutdownNow();
        }
    }
}

额外优化说明

  • 加了Activity销毁判断,后台任务执行时如果Activity已经关了就不执行UI操作,从根源避免空指针
  • 后台线程只做数据库读写的纯耗时操作,所有UI相关逻辑统一放到主线程执行,符合Android线程规范
  • 删掉了多余的循环代码,渲染速度会提升很多
  • Activity销毁时主动关闭线程池,避免内存泄漏

内容的提问来源于stack exchange,提问作者Abid khan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 05:24:04