如何正确按顺序使用后台线程优化RecyclerView数据加载?
问题描述
- 现有问题:应用使用单个后台线程遍历400条RecyclerView数据,流程为遍历列表、调用方法补全信息、存入ArrayList,完成后通过Handler调用
notifyDataSetChanged(),整体耗时达10秒。 - 需求目标:实现顺序执行的后台线程架构——主后台线程遍历列表,调用的数据库查询方法(从SugarORM获取信息)也通过顺序线程执行,每个线程通过Handler与UI线程安全通信,用
notifyItemChanged(_position)替代全量更新,缩短加载时间。 - 当前困境:自行编写的顺序线程测试代码无法正常工作。
测试代码
ThreadGroup mMainThreadForLoopThreadGroup; ThreadGroup mThreadGroupMethodOne; ThreadGroup mThreadGroupMethodTwo; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); callMainThread(); } private void callMainThread() { mMainThreadForLoopThreadGroup = new ThreadGroup("mMainThreadForLoopThreadGroup"); startMainThreadForLoop(mMainThreadForLoopThreadGroup, "mMainThreadForLoop"); } private void startMainThreadForLoop(ThreadGroup _threadGroup, String _threadName) { if (_threadName != null && !_threadName.isEmpty() && _threadGroup!=null) { final String mThreadName = _threadName; Thread mThread = new Thread(_threadGroup, new Runnable() { @Override public void run() { final Message mMessage = mHandler.obtainMessage(); Bundle mBundle = new Bundle(); SimpleDateFormat mDateFormat = new SimpleDateFormat("HH:mm:ss dd/MM/yyyy", Locale.getDefault()); String mDateString = mDateFormat.format(new Date()) + " /startMainThreadForLoop"; mBundle.putString("messageString", mDateString); mBundle.putBoolean(_threadName + "Boolean", true); try { for (int n=0; n<=4; n++) { if (n % 2==0){ methodOne(); } else { methodTwo(); } } try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception ex) { } mMessage.setData(mBundle); mHandler.sendMessage(mMessage); } }, mThreadName); mThread.start(); } } private void methodOne() { mThreadGroupMethodOne = new ThreadGroup("mThreadGroupMethodOne"); for (int n=0; n<=4; n++) { if (n % 2==0){ startThreadMethodOneA(mThreadGroupMethodOne, "mThreadMethodOneA"); } else { startThreadMethodOneB(mThreadGroupMethodOne, "mThreadMethodOneB"); } } } private void methodTwo() { mThreadGroupMethodTwo = new ThreadGroup("mThreadGroupMethodTwo"); for (int n=0; n<=4; n++) { if (n % 2==0){ startThreadMethodTwoA(mThreadGroupMethodTwo, "mThreadMethodTwoA"); } else { startThreadMethodTwoB(mThreadGroupMethodTwo, "mThreadMethodTwoB"); } } } private void startThreadMethodOneA(ThreadGroup _threadGroup, String _threadName) { if (_threadName != null && !_threadName.isEmpty() && _threadGroup!=null) { final String mThreadName = _threadName; Thread mThread = new Thread(_threadGroup, new Runnable() { @Override public void run() { final Message mMessage = mHandler.obtainMessage(); Bundle mBundle = new Bundle(); SimpleDateFormat mDateFormat = new SimpleDateFormat("HH:mm:ss dd/MM/yyyy", Locale.getDefault()); String mDateString = mDateFormat.format(new Date()) + " /startThreadMethodOneA"; mBundle.putString("messageString", mDateString); mBundle.putBoolean(_threadName + "Boolean", true); try { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception ex) { } mMessage.setData(mBundle); mHandler.sendMessage(mMessage); } }, mThreadName); mThread.start(); } } private void startThreadMethodOneB(ThreadGroup _threadGroup, String _threadName) { if (_threadName != null && !_threadName.isEmpty() && _threadGroup!=null) { final String mThreadName = _threadName; Thread mThread = new Thread(_threadGroup, new Runnable() { @Override public void run() { final Message mMessage = mHandler.obtainMessage(); Bundle mBundle = new Bundle(); SimpleDateFormat mDateFormat = new SimpleDateFormat("HH:mm:ss dd/MM/yyyy", Locale.getDefault()); String mDateString = mDateFormat.format(new Date()) + " /startThreadMethodOneB"; mBundle.putString("messageString", mDateString); mBundle.putBoolean(_threadName + "Boolean", true); try { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception ex) { } mMessage.setData(mBundle); mHandler.sendMessage(mMessage); } }, mThreadName); mThread.start(); } } private void startThreadMethodTwoA(ThreadGroup _threadGroup, String _threadName) { if (_threadName != null && !_threadName.isEmpty() && _threadGroup!=null) { final String mThreadName = _threadName; Thread mThread = new Thread(_threadGroup, new Runnable() { @Override public void run() { final Message mMessage = mHandler.obtainMessage(); Bundle mBundle = new Bundle(); SimpleDateFormat mDateFormat = new SimpleDateFormat("HH:mm:ss dd/MM/yyyy", Locale.getDefault()); String mDateString = mDateFormat.format(new Date()) + " /startThreadMethodTwoA"; mBundle.putString("messageString", mDateString); mBundle.putBoolean(_threadName + "Boolean", true); try { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception ex) { } mMessage.setData(mBundle); mHandler.sendMessage(mMessage); } }, mThreadName); mThread.start(); } } private void startThreadMethodTwoB(ThreadGroup _threadGroup, String _threadName) { if (_threadName != null && !_threadName.isEmpty() && _threadGroup!=null) { final String mThreadName = _threadName; Thread mThread = new Thread(_threadGroup, new Runnable() { @Override public void run() { final Message mMessage = mHandler.obtainMessage(); Bundle mBundle = new Bundle(); SimpleDateFormat mDateFormat = new SimpleDateFormat("HH:mm:ss dd/MM/yyyy", Locale.getDefault()); String mDateString = mDateFormat.format(new Date()) + " /startThreadMethodTwoB"; mBundle.putString("messageString", mDateString); mBundle.putBoolean(_threadName + "Boolean", true); try { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception ex) { } mMessage.setData(mBundle); mHandler.sendMessage(mMessage); } }, mThreadName); mThread.start(); } } @SuppressLint("HandlerLeak") Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { Bundle mBundle = msg.getData(); String mNewString = mBundle.getString("messageString"); boolean mBooleanMainThreadForLoopValue = mBundle.getBoolean("mMainThreadForLoopBoolean"); boolean mBooleanThreadMethodOneAValue = mBundle.getBoolean("mThreadMethodOneABoolean"); boolean mBooleanThreadMethodOneBValue = mBundle.getBoolean("mThreadMethodOneBBoolean"); boolean mBooleanThreadMethodTwoAValue = mBundle.getBoolean("mThreadMethodTwoABoolean"); boolean mBooleanThreadMethodTwoBValue = mBundle.getBoolean("mThreadMethodTwoBBoolean"); if (mBooleanMainThreadForLoopValue) { if (mBundle.getString("messageString")!=null && !mBundle.getString("messageString").isEmpty() ) { Log.d("Handler Response", "\nmMainThreadForLoopBoolean Finished, value: " + mBundle.getString("messageString")); dateTextView.setText(mBundle.getString("messageString")); } } else if (mBooleanThreadMethodOneAValue) { if (mBundle.getString("messageString")!=null && !mBundle.getString("messageString").isEmpty() ) { Log.d("Handler Response", "\nmThreadMethodOneABoolean Finished, value: " + mBundle.getString("messageString")); dateTextViewSecond.setText(mBundle.getString("messageString")); } } else if (mBooleanThreadMethodOneBValue) { if (mBundle.getString("messageString")!=null && !mBundle.getString("messageString").isEmpty() ) { Log.d("Handler Response", "\nmThreadMethodOneBBoolean Finished, value: " + mBundle.getString("messageString")); dateTextViewSecond.setText(mBundle.getString("messageString")); } } else if (mBooleanThreadMethodTwoAValue) { if (mBundle.getString("messageString")!=null && !mBundle.getString("messageString").isEmpty() ) { Log.d("Handler Response", "\nmThreadMethodTwoABoolean Finished, value: " + mBundle.getString("messageString")); dateTextViewSecond.setText(mBundle.getString("messageString")); } } else if (mBooleanThreadMethodTwoBValue) { if (mBundle.getString("messageString")!=null && !mBundle.getString("messageString").isEmpty() ) { Log.d("Handler Response", "\nmThreadMethodTwoBBoolean Finished, value: " + mBundle.getString("messageString")); dateTextViewSecond.setText(mBundle.getString("messageString")); } } } };
代码问题分析与解决方案
核心问题:线程未真正顺序执行
测试代码中,methodOne()和methodTwo()直接循环启动多个线程,这些线程会并行执行而非顺序执行——调用start()后线程立即运行,没有等待上一个线程完成的逻辑;同时主线程的for循环也不会等待methodOne()/methodTwo()内的所有线程完成,直接进入下一次循环。
可行实现方案
方案1:使用单线程线程池(推荐)
用Executors.newSingleThreadExecutor()创建单线程池,所有任务会按提交顺序依次执行,无需手动管理线程等待:
// 初始化单线程池 private ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); // 消息常量 private static final int MSG_UPDATE_ITEM = 1; private void callMainThread() { singleThreadExecutor.execute(new Runnable() { @Override public void run() { // 遍历实际数据列表,此处用模拟循环替代 for (int n = 0; n <= 4; n++) { final int position = n; if (n % 2 == 0) { // 提交methodOne的数据库查询任务到线程池 singleThreadExecutor.execute(new Runnable() { @Override public void run() { // 从SugarORM查询数据的逻辑 Object queryResult = queryDataFromSugarORMForTypeOne(position); // 通知UI更新指定位置 mHandler.obtainMessage(MSG_UPDATE_ITEM, position, -1, queryResult).sendToTarget(); } }); } else { // 提交methodTwo的数据库查询任务到线程池 singleThreadExecutor.execute(new Runnable() { @Override public void run() { Object queryResult = queryDataFromSugarORMForTypeTwo(position); mHandler.obtainMessage(MSG_UPDATE_ITEM, position, -1, queryResult).sendToTarget(); } }); } } } }); } // 模拟SugarORM查询方法 private Object queryDataFromSugarORMForTypeOne(int position) { // 实际查询逻辑:比如根据position关联的ID查询数据 return new Object(); } private Object queryDataFromSugarORMForTypeTwo(int position) { return new Object(); } // Handler处理UI更新(避免内存泄漏,改用静态内部类+WeakReference) private static class MyHandler extends Handler { private WeakReference<YourActivity> mActivityRef; public MyHandler(YourActivity activity) { mActivityRef = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { YourActivity activity = mActivityRef.get(); if (activity == null) return; switch (msg.what) { case MSG_UPDATE_ITEM: int position = msg.arg1; Object data = msg.obj; // 更新数据源对应位置 activity.mDataList.set(position, data); // 更新指定位置的RecyclerView项 activity.mAdapter.notifyItemChanged(position); break; } } } // 在Activity中初始化Handler private MyHandler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mHandler = new MyHandler(this); callMainThread(); }
方案2:手动线程等待(Thread.join())
如果坚持使用Thread类,需要在启动线程后调用thread.join()等待线程完成,确保顺序执行:
private void methodOne() { for (int n=0; n<=4; n++) { Thread thread = null; if (n % 2==0){ thread = startThreadMethodOneA("mThreadMethodOneA-" + n); } else { thread = startThreadMethodOneB("mThreadMethodOneB-" + n); } try { // 等待当前线程完成后再执行下一个 if (thread != null) thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } // 修改startThread方法,返回创建的Thread对象 private Thread startThreadMethodOneA(String _threadName) { Thread mThread = new Thread(new Runnable() { @Override public void run() { // 业务逻辑:SugarORM查询+通知UI // ... } }, _threadName); mThread.start(); return mThread; }
额外优化建议
- 批量更新优化:如果频繁调用
notifyItemChanged()导致UI卡顿,可以累计多个更新任务,每隔200ms批量调用notifyItemRangeChanged(),平衡更新速度和流畅度。 - SugarORM查询优化:给常用查询字段添加索引,避免全表扫描;使用
findById()、带条件的query()方法减少查询耗时。 - 线程池生命周期管理:在Activity销毁时调用
singleThreadExecutor.shutdown(),避免内存泄漏。
内容的提问来源于stack exchange,提问作者DevPeter
相关产品推荐
相关产品推荐

