如何通过SharedPreferences从Service向Fragment传数据?步数值不更新
问题分析
你的代码核心逻辑问题很清晰:StepsFragment只在点击启动服务的瞬间读取了一次SharedPreferences的值,之后StepsService每秒更新SharedPreferences,但Fragment没有任何机制监听这个变化,所以TextView自然不会自动刷新最新的计步数据。另外,Service里的while(true)线程没有停止逻辑,会导致服务销毁后线程仍在运行,造成内存泄漏,这个也得一起修复。
解决方案
下面给你三种可行的修复方案,从直接适配现有代码到符合现代Android架构的都有:
方案1:监听SharedPreferences变化(最直接适配现有代码)
利用SharedPreferences.OnSharedPreferenceChangeListener接口,让Fragment监听指定键的变化,一旦值更新就同步到TextView。
修改StepsFragment.java:
public class StepsFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener { private TextView tv_steps; private SharedPreferences prefs; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.your_fragment_layout, container, false); tv_steps = view.findViewById(R.id.your_textview_id); // 初始化SharedPreferences prefs = getActivity().getSharedPreferences("DeviceToken", MODE_PRIVATE); return view; } @Override public void onResume() { super.onResume(); // 注册监听器 prefs.registerOnSharedPreferenceChangeListener(this); // 首次进入时加载当前存储的计步值 String currentSteps = prefs.getString("DeviceToken", "0"); tv_steps.setText(currentSteps); } @Override public void onPause() { super.onPause(); // 必须在生命周期结束时注销监听器,避免内存泄漏 prefs.unregisterOnSharedPreferenceChangeListener(this); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // 当指定键的值变化时更新TextView if ("DeviceToken".equals(key)) { String newSteps = sharedPreferences.getString(key, "0"); tv_steps.setText(newSteps); } } public void startService(View view) { Intent serviceIntent = new Intent(getActivity(), StepsService.class); getActivity().startService(serviceIntent); } }
方案2:用LocalBroadcastManager发送广播
Service每次更新计步数据后,发送一条本地广播,Fragment注册接收器接收广播并更新UI。
修改StepsService.java的run方法:
Runnable runnable = new Runnable() { public void run() { // 用Handler.postDelayed代替while(true)+sleep,更安全可控 Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // 更新SharedPreferences SharedPreferences.Editor editor = getSharedPreferences("DeviceToken", MODE_PRIVATE).edit(); String stepsStr = String.valueOf(stepsCounter); editor.putString("DeviceToken", stepsStr); editor.apply(); addNotification(stepsStr); // 发送本地广播通知Fragment更新 Intent intent = new Intent("com.yourpackage.STEPS_UPDATED"); intent.putExtra("steps", stepsStr); LocalBroadcastManager.getInstance(StepsService.this).sendBroadcast(intent); // 延迟1秒再次执行 handler.postDelayed(this, 1000); } }); } };
修改StepsFragment.java:
public class StepsFragment extends Fragment { private TextView tv_steps; private BroadcastReceiver stepsReceiver; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.your_fragment_layout, container, false); tv_steps = view.findViewById(R.id.your_textview_id); return view; } @Override public void onResume() { super.onResume(); // 注册广播接收器 stepsReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String newSteps = intent.getStringExtra("steps"); tv_steps.setText(newSteps); } }; IntentFilter filter = new IntentFilter("com.yourpackage.STEPS_UPDATED"); LocalBroadcastManager.getInstance(getActivity()).registerReceiver(stepsReceiver, filter); } @Override public void onPause() { super.onPause(); // 注销接收器,避免内存泄漏 LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(stepsReceiver); } public void startService(View view) { Intent serviceIntent = new Intent(getActivity(), StepsService.class); getActivity().startService(serviceIntent); // 首次加载当前存储的计步值 SharedPreferences prefs = getActivity().getSharedPreferences("DeviceToken", MODE_PRIVATE); String currentSteps = prefs.getString("DeviceToken", "0"); tv_steps.setText(currentSteps); } }
方案3:使用LiveData(推荐,符合Android架构组件)
如果你的项目使用Jetpack组件,用LiveData管理计步数据是最优解,它能自动感知Fragment的生命周期,避免内存泄漏,代码也更简洁。
第一步:创建单例LiveData类
public class StepsLiveData extends MutableLiveData<String> { private static StepsLiveData instance; public static StepsLiveData getInstance() { if (instance == null) { instance = new StepsLiveData(); } return instance; } }
修改StepsService.java:
@Override public int onStartCommand(Intent intent, int flags, int startId) { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { String stepsStr = String.valueOf(stepsCounter); // 更新LiveData,自动通知观察者 StepsLiveData.getInstance().setValue(stepsStr); // 同时更新SharedPreferences做持久化 SharedPreferences.Editor editor = getSharedPreferences("DeviceToken", MODE_PRIVATE).edit(); editor.putString("DeviceToken", stepsStr); editor.apply(); addNotification(stepsStr); handler.postDelayed(this, 1000); } }); return START_NOT_STICKY; }
修改StepsFragment.java:
public class StepsFragment extends Fragment { private TextView tv_steps; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.your_fragment_layout, container, false); tv_steps = view.findViewById(R.id.your_textview_id); return view; } @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 观察LiveData,自动更新UI StepsLiveData.getInstance().observe(getViewLifecycleOwner(), steps -> { tv_steps.setText(steps); }); } public void startService(View view) { Intent serviceIntent = new Intent(getActivity(), StepsService.class); getActivity().startService(serviceIntent); // 首次加载SharedPreferences里的缓存值 SharedPreferences prefs = getActivity().getSharedPreferences("DeviceToken", MODE_PRIVATE); String currentSteps = prefs.getString("DeviceToken", "0"); tv_steps.setText(currentSteps); } }
额外注意事项
- 替换代码里的
com.yourpackage为你自己的应用包名,广播Action要保证唯一。 - 方案1和2中,一定要在Fragment的
onPause或onDestroy里注销监听器/广播接收器,否则会造成内存泄漏。 - Service里的
while(true)+Thread.sleep是不安全的,建议用Handler.postDelayed或者Coroutine实现定时任务,这样在服务停止时可以移除回调,避免线程一直运行。
内容的提问来源于stack exchange,提问作者Jim Ng
相关产品推荐
相关产品推荐

