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
相关产品推荐
相关产品推荐

