AsyncTask中doInBackground返回ArrayList但onPostExecute为空问题排查
搞定AsyncTask中Firebase数据传不到onPostExecute的问题
嘿,你遇到的这个坑其实很多人刚用Firebase的时候都踩过!核心问题不是AsyncTask的线程并行,而是Firebase数据库的查询本身就是异步操作——你在doInBackground里发起查询后,没等Firebase把数据拿回来就直接返回ArrayList了,这时候集合当然是空的。
先说说你的代码大概率存在的问题
假设你的代码大概是这样(毕竟你没贴全,但这是最常见的错误写法):
@Override protected ArrayList<Map<String, Object>> doInBackground(Void... voids) { ArrayList<Map<String, Object>> dataList = new ArrayList<>(); // 发起Firebase查询,但这个方法不会等数据加载完就往下走 FirebaseDatabase.getInstance().getReference("your_data_path") .addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // 这里才是真正拿到数据的地方,但此时doInBackground已经返回空集合了! for (DataSnapshot ds : snapshot.getChildren()) { Map<String, Object> map = (Map<String, Object>) ds.getValue(); dataList.add(map); } } @Override public void onCancelled(DatabaseError error) { // 忽略错误处理 } }); // 直接返回空集合,因为Firebase还没来得及把数据加进来 return dataList; }
给你两个解决方案,按需选
方案1:抛弃AsyncTask,用Firebase原生回调处理(强烈推荐)
现在AsyncTask已经被官方废弃了,而且Firebase的设计本来就适合直接在主线程用回调处理,根本不需要额外开后台线程:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.your_activity_layout); // 直接在主线程发起查询,用单次监听(只拿一次数据) FirebaseDatabase.getInstance().getReference("your_data_path") .addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { ArrayList<Map<String, Object>> dataList = new ArrayList<>(); for (DataSnapshot ds : snapshot.getChildren()) { Map<String, Object> map = (Map<String, Object>) ds.getValue(); dataList.add(map); } // 拿到数据直接处理,比如更新UI refreshUIWithData(dataList); } @Override public void onCancelled(DatabaseError error) { Toast.makeText(YourActivity.this, "数据加载失败啦", Toast.LENGTH_SHORT).show(); } }); } // 专门写个方法处理UI更新 private void refreshUIWithData(ArrayList<Map<String, Object>> dataList) { // 这里写你的列表展示、数据渲染逻辑就行 }
方案2:非要用AsyncTask?那用CountDownLatch等数据加载完
如果你因为某些原因必须保留AsyncTask,可以用CountDownLatch让后台线程等Firebase的回调完成后再返回数据:
@Override protected ArrayList<Map<String, Object>> doInBackground(Void... voids) { final ArrayList<Map<String, Object>> dataList = new ArrayList<>(); final CountDownLatch latch = new CountDownLatch(1); // 计数器初始为1 FirebaseDatabase.getInstance().getReference("your_data_path") .addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { for (DataSnapshot ds : snapshot.getChildren()) { Map<String, Object> map = (Map<String, Object>) ds.getValue(); dataList.add(map); } latch.countDown(); // 数据加载完,计数器减1 } @Override public void onCancelled(DatabaseError error) { latch.countDown(); // 就算出错也要减计数器,不然线程会一直等 } }); try { latch.await(); // 阻塞当前线程,直到计数器变为0 } catch (InterruptedException e) { e.printStackTrace(); } return dataList; // 现在返回的就是有数据的集合了 }
最后提个醒
- 尽量别用方案2,阻塞线程不符合Firebase的异步设计,而且AsyncTask早就被官方弃用了,现在更推荐用Kotlin协程、LiveData这些现代的异步工具。
- 用
addListenerForSingleValueEvent代替addValueEventListener,前者只会获取一次数据,后者会一直监听数据变化,如果你只是加载一次数据,前者更省资源。
内容的提问来源于stack exchange,提问作者Farid Abbasov
相关产品推荐
相关产品推荐

