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

健身App切换Fragment后数据丢失:新线程实现问题排查

问题分析与解决方案

问题描述

我开发了一款健身App,通过SQLiteDatabase读取数据,在Home Fragment中填充TextView与RecyclerView,原本运行完全正常。由于原启动速度较慢,我将TotalSetsWeek和storeDatainArrays方法迁移至新线程类以提升性能,现在首次打开App时功能正常,但切换离开该Fragment再返回后,所有数据都会被清空变为空白。

原代码

package com.example.polemistesfitness;

import android.annotation.SuppressLint;
import android.database.Cursor;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import android.os.Handler;
import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;


public class HomeFragment extends Fragment {

    DatabaseHelper mydb;
    RecyclerView recyclerView;
    ArrayList<String> exercise_name, set_weight, set_reps, set_date;
    ArrayList<String> total_weight, total_reps;  //for the weekly stats

    //making instance of db adapter class
    DBAdapter dbadapter;
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
    String todaydate;
    String weekAgoDate;
    int totalMonthlyReps=0;
    int totalMonthlyWeight=0;
    int totalMonthSets=0;
    TextView weighttxt;
    TextView repstxt;
    TextView setstxt;
    TextView fitscoretxt;
    int fitnessScore;


    public HomeFragment() {
    }

    @SuppressLint("MissingInflatedId")
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        View view = inflater.inflate(R.layout.fragment_home, container, false);

        recyclerView = (RecyclerView) view.findViewById(R.id.homerecyclerView);

        //resetting in case a new set was added
        totalMonthlyReps=0;
        totalMonthlyWeight=0;
        totalMonthSets=0;
        fitnessScore=0;


        mydb = new DatabaseHelper(getContext());
        exercise_name = new ArrayList<>();
        set_weight = new ArrayList<>();
        set_reps = new ArrayList<>();
        set_date= new ArrayList<>();
        total_weight= new ArrayList<>();
        total_reps= new ArrayList<>();


        todaydate = sdf.format(new Date()).toString();
        ///weekAgoDate = sdf.format(new Date(System.currentTimeMillis() - 7L * 24 * 3600 * 1000)).toString();

        //System.out.println("Week Ago Date: "+weekAgoDate);
        //This two methods need to be on different threads
        storeDatainArrays();
        TotalSetsWeek(todaydate);

        dbadapter = new DBAdapter(getContext(),exercise_name ,set_weight,set_reps,set_date);
        recyclerView.setAdapter(dbadapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

        //for loop to get total sets and weight for the week and put into textviews
        for(int i=0;i<total_weight.size();i++) {
            totalMonthlyWeight += Integer.parseInt(total_weight.get(i));
            totalMonthlyReps += Integer.parseInt(total_reps.get(i));
            System.out.println("Total sets: " + total_weight.size());
        }
        //Fitness score is total number of sets by 2 in the month

        if(totalMonthlyWeight==0){
            fitnessScore=0;
        } else{
            fitnessScore = ((totalMonthlyReps+totalMonthlyWeight) /(total_weight.size()));
            fitnessScore = totalMonthlyReps+totalMonthlyWeight;

        }


        weighttxt =  view.findViewById(R.id.totalweighttxt);
        weighttxt.setText(String.valueOf(totalMonthlyWeight));
        repstxt =  view.findViewById(R.id.totalrepstxt);
        repstxt.setText(String.valueOf(totalMonthlyReps));
        setstxt =  view.findViewById(R.id.totalsetstxt);
        setstxt.setText(String.valueOf(total_weight.size()));
        fitscoretxt =  view.findViewById(R.id.fitscoretxt);
        fitscoretxt.setText(String.valueOf(fitnessScore));


        return view;

    }

    //Storing the data of the past week
    void TotalSetsWeek(String todaydate) {
        TotalSetsWeekThread thread2 = new TotalSetsWeekThread();
        thread2.start();
    }


    //Storing Todays Data
    void storeDatainArrays(){
        /*//calling the method in db java class and storing it in cursor
        Cursor cursor = mydb.readDate(todaydate);    //Im going to change this to read only todays Data
        if(cursor.getCount() == 0){
            Toast.makeText(getContext(), "Start a Workout Today", Toast.LENGTH_SHORT).show();
        }
        //Here we read the columns by their number
        else{
            while (cursor.moveToNext()){
                //these columindexes might need changing
                exercise_name.add(cursor.getString(1)); //storing column data in arraylist
                set_weight.add(cursor.getString(2));
                set_reps.add(cursor.getString(3));
                set_date.add(cursor.getString(4));

            }
        }*/

        StoreDatainArrays thread1 = new StoreDatainArrays();
        thread1.start();

    }

    class StoreDatainArrays extends Thread {

        StoreDatainArrays(){

        }

        @Override
        public void run(){
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    //calling the method in db java class and storing it in cursor
                    Cursor cursor = mydb.readDate(todaydate);    //Im going to change this to read only todays Data
                    if(cursor.getCount() == 0){
                        Toast.makeText(getContext(), "Start a Workout Today", Toast.LENGTH_SHORT).show();
                    }
                    //Here we read the columns by their number
                    else{
                        while (cursor.moveToNext()){
                            //these columindexes might need changing
                            exercise_name.add(cursor.getString(1)); //storing column data in arraylist
                            set_weight.add(cursor.getString(2));
                            set_reps.add(cursor.getString(3));
                            set_date.add(cursor.getString(4));

                        }
                    }
                }
            });
        }
    }


    class TotalSetsWeekThread extends Thread{
        TotalSetsWeekThread() {}

        @Override
        public void run(){
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {
                @Override
                public void run() {
                    //calling the method in db java class and storing it in cursor
                    Cursor cursor = mydb.readDate(todaydate);    //Im going to change this to read only todays Data
                    if(cursor.getCount() == 0){

                        Toast.makeText(getContext(), "Start a Workout Today", Toast.LENGTH_SHORT).show();
                    }
                    //Here we read the columns by their number
                    else{
                        while (cursor.moveToNext()){
                            //these columindexes might need changing
                            System.out.println(cursor.getString(2));
                            total_weight.add(cursor.getString(2));
                            total_reps.add(cursor.getString(3));


                        }
                    }
                }
            });
        }


    }

}

问题根源

  1. 异步线程完全失效:你创建的两个线程内部,通过Handler.post把数据库查询操作又切回了主线程,等于数据库操作还是在主线程执行,既没解决启动慢的问题,还因为线程调度延迟导致数据加载时机混乱。
  2. 数据计算与线程执行不同步:onCreateView中直接对空的ArrayList进行统计计算并设置TextView内容,首次打开时线程任务可能刚好在计算前完成,但切换返回时,线程任务还未执行,ArrayList仍为空,导致统计结果全为0。
  3. 未通知RecyclerView更新:线程往ArrayList添加数据后,没有调用dbadapter.notifyDataSetChanged(),即使数据加载完成,RecyclerView也不会刷新。
  4. 生命周期适配错误:每次返回Fragment都会重新执行onCreateView,创建新的集合和Adapter,但线程任务的执行与Fragment生命周期不同步,导致数据丢失或未及时加载。

解决方案

1. 真正实现异步数据库操作

将数据库查询放在子线程执行,仅更新UI的操作切回主线程:

class StoreDatainArrays extends Thread {
    @Override
    public void run() {
        // 子线程执行数据库查询
        Cursor cursor = mydb.readDate(todaydate);
        ArrayList<String> tempExerciseNames = new ArrayList<>();
        ArrayList<String> tempSetWeights = new ArrayList<>();
        ArrayList<String> tempSetReps = new ArrayList<>();
        ArrayList<String> tempSetDates = new ArrayList<>();

        if(cursor.getCount() == 0){
            // 主线程显示Toast
            new Handler(Looper.getMainLooper()).post(() -> 
                Toast.makeText(getContext(), "今天开始训练吧", Toast.LENGTH_SHORT).show()
            );
        } else {
            while (cursor.moveToNext()){
                tempExerciseNames.add(cursor.getString(1));
                tempSetWeights.add(cursor.getString(2));
                tempSetReps.add(cursor.getString(3));
                tempSetDates.add(cursor.getString(4));
            }
            cursor.close(); // 关闭Cursor避免内存泄漏
        }

        // 主线程更新数据与UI
        new Handler(Looper.getMainLooper()).post(() -> {
            exercise_name.clear();
            exercise_name.addAll(tempExerciseNames);
            set_weight.clear();
            set_weight.addAll(tempSetWeights);
            set_reps.clear();
            set_reps.addAll(tempSetReps);
            set_date.clear();
            set_date.addAll(tempSetDates);
            
            if(dbadapter != null){
                dbadapter.notifyDataSetChanged(); // 通知Adapter更新
            }
            calculateAndUpdateStats(); // 重新计算统计数据
        });
    }
}

2. 分离统计计算逻辑

将统计计算抽成单独方法,在线程加载完数据后调用:

private void calculateAndUpdateStats() {
    totalMonthlyReps = 0;
    totalMonthlyWeight = 0;
    fitnessScore = 0;

    for(int i = 0; i < total_weight.size(); i++) {
        totalMonthlyWeight += Integer.parseInt(total_weight.get(i));
        totalMonthlyReps += Integer.parseInt(total_reps.get(i));
    }

    fitnessScore = totalMonthlyWeight == 0 ? 0 : totalMonthlyReps + totalMonthlyWeight;

    weighttxt.setText(String.valueOf(totalMonthlyWeight));
    repstxt.setText(String.valueOf(totalMonthlyReps));
    setstxt.setText(String.valueOf(total_weight.size()));
    fitscoretxt.setText(String.valueOf(fitnessScore));
}

3. 修正TotalSetsWeekThread异步逻辑

class TotalSetsWeekThread extends Thread{
    @Override
    public void run(){
        Cursor cursor = mydb.readDate(todaydate);
        ArrayList<String> tempTotalWeight = new ArrayList<>();
        ArrayList<String> tempTotalReps = new ArrayList<>();

        if(cursor.getCount() == 0){
            new Handler(Looper.getMainLooper()).post(() -> 
                Toast.makeText(getContext(), "今天开始训练吧", Toast.LENGTH_SHORT).show()
            );
        } else{
            while (cursor.moveToNext()){
                tempTotalWeight.add(cursor.getString(2));
                tempTotalReps.add(cursor.getString(3));
            }
            cursor.close();
        }

        new Handler(Looper.getMainLooper()).post(() -> {
            total_weight.clear();
            total_weight.addAll(tempTotalWeight);
            total_reps.clear();
            total_reps.addAll(tempTotalReps);
            calculateAndUpdateStats(); // 更新统计数据
        });
    }
}

4. 优化Fragment初始化流程

@Override 
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_home, container, false);

    // 初始化View引用
    recyclerView = view.findViewById(R.id.homerecyclerView);
    weighttxt = view.findViewById(R.id.totalweighttxt);
    repstxt = view.findViewById(R.id.totalrepstxt);
    setstxt = view.findViewById(R.id.totalsetstxt);
    fitscoretxt = view.findViewById(R.id.fitscoretxt);

    // 初始化集合
    exercise_name = new ArrayList<>();
    set_weight = new ArrayList<>();
    set_reps = new ArrayList<>();
    set_date= new ArrayList<>();
    total_weight= new ArrayList<>();
    total_reps= new ArrayList<>();

    mydb = new DatabaseHelper(getContext());
    todaydate = sdf.format(new Date()).toString();

    // 初始化RecyclerView
    dbadapter = new DBAdapter(getContext(),exercise_name ,set_weight,set_reps,set_date);
    recyclerView.setAdapter(dbadapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

    // 启动异步线程加载数据
    storeDatainArrays();
    TotalSetsWeek(todaydate);

    return view;
}

5. 资源释放

在Fragment销毁时关闭数据库连接,避免内存泄漏:

@Override
public void onDestroy() {
    super.onDestroy();
    if(mydb != null){
        mydb.close(); // 假设DatabaseHelper实现了close方法
    }
}

额外建议

  • 推荐使用Android官方的Coroutines替代Thread+Handler,代码更简洁且更易管理生命周期。
  • 数据库操作务必关闭Cursor和数据库连接,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 08:37:05