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

如何正确按顺序使用后台线程优化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;
}

额外优化建议

  1. 批量更新优化:如果频繁调用notifyItemChanged()导致UI卡顿,可以累计多个更新任务,每隔200ms批量调用notifyItemRangeChanged(),平衡更新速度和流畅度。
  2. SugarORM查询优化:给常用查询字段添加索引,避免全表扫描;使用findById()、带条件的query()方法减少查询耗时。
  3. 线程池生命周期管理:在Activity销毁时调用singleThreadExecutor.shutdown(),避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:37:04