问题背景如下:
按钮有两种状态,分别是出水、回水。如果当前状态是出水,当点击按钮时,会发送一条指令,在另一个线程中判断状态是否切换成功,如果已经切换为出水状态,则将按钮文字修改为”回水“。需要做到的就是能够在点击按钮后,自动更新按钮状态。另外需要注意的是,按钮和回复线程不在同一个Activity,这涉及到跨 Activity 的线程间通信以及 UI 更新。
该问题可以有两种方式去解决:
一、使用 BroadcastReceiver
- 在发送数据的 Activity 中发送广播
// 在发送数据的 Activity 中
private void sendUpdateBroadcast() {
Intent intent = new Intent("UPDATE_ACTION");
intent.putExtra("waterTemControlType", 1); // 传递数据
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
btn1.setOnClickListener(v -> {
send(new byte[]{0x01,0x10,0x00,0x00,0x00,0x03,0x06,0x00,0x03,0x00,0x04,0x00,0x05,0x23,0x42});
Log.d("TAG", "onCreateView: " + UserSetDate.waterTemControlType);
// 在后台线程中更新数据
new Thread(() -> {
UserSetDate.waterTemControlType = 1;
// 发送广播通知其他 Activity 更新 UI
sendUpdateBroadcast();
}).start();
});
- 在接收数据的 Activity 中注册 BroadcastReceiver
public class ReceivingActivity extends AppCompatActivity {
private Button btn1;
private BroadcastReceiver updateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("UPDATE_ACTION".equals(intent.getAction())) {
int waterTemControlType = intent.getIntExtra("waterTemControlType", 0);
if (waterTemControlType == 1) {
btn1.setText("回水");
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_receiving);
btn1 = findViewById(R.id.btn1);
// 注册 BroadcastReceiver
LocalBroadcastManager.getInstance(this).registerReceiver(updateReceiver, new IntentFilter("UPDATE_ACTION"));
}
@Override
protected void onDestroy() {
super.onDestroy();
// 注销 BroadcastReceiver
LocalBroadcastManager.getInstance(this).unregisterReceiver(updateReceiver);
}
}
二、使用 LiveData 和 ViewModel
- 创建 ViewModel
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Integer> waterTemControlType = new MutableLiveData<>();
public LiveData<Integer> getWaterTemControlType() {
return waterTemControlType;
}
public void setWaterTemControlType(int value) {
waterTemControlType.setValue(value);
}
}
- 在发送数据的 Activity 中更新 ViewModel
public class SendingActivity extends AppCompatActivity {
private SharedViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sending);
viewModel = new ViewModelProvider(this).get(SharedViewModel.class);
btn1.setOnClickListener(v -> {
send(new byte[]{0x01,0x10,0x00,0x00,0x00,0x03,0x06,0x00,0x03,0x00,0x04,0x00,0x05,0x23,0x42});
Log.d("TAG", "onCreateView: " + UserSetDate.waterTemControlType);
// 在后台线程中更新数据
new Thread(() -> {
UserSetDate.waterTemControlType = 1;
// 更新 ViewModel 中的数据
viewModel.setWaterTemControlType(1);
}).start();
});
}
}
- 在接收数据的 Activity 中观察 ViewModel
public class ReceivingActivity extends AppCompatActivity {
private SharedViewModel viewModel;
private Button btn1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_receiving);
btn1 = findViewById(R.id.btn1);
viewModel = new ViewModelProvider(this).get(SharedViewModel.class);
// 观察 ViewModel 中的数据
viewModel.getWaterTemControlType().observe(this, value -> {
if (value != null && value == 1) {
btn1.setText("回水");
}
});
}
}
在调试过程中,发现使用 LiveData 和 ViewModel 时,btn1按钮的文字并没有发生修改,最后发现在 Activity 和 Fragment 之间共享的ViewModel 实例不同导致的。
如果在调试过程中遇到问题,问题原因可能如下:
1.LiveData 数据没有正确更新:确保你在正确的地方更新了 LiveData 数据。
2.Fragment 的生命周期:确保 Fragment 在观察 LiveData 时处于活动状态。
3.ViewModel 的作用域:确保你使用的 ViewModel 是正确的,并且在 Activity 和 Fragment 之间共享相同的 ViewModel 实例。
排查步骤如下:
- 确保 LiveData 数据正确更新
确保在更新 LiveData 数据的地方,你已经正确地调用了 setValue 或 postValue 方法。例如:
viewModel.setWaterTemControlType(1);
- 确保 Fragment 正确注册了观察者
确保你在 Fragment 的 onCreateView 或 onViewCreated 方法中注册了观察者,而不是在 onCreate 中,因为 Fragment 在 onCreate 中可能还没有完全初始化。下面是一个正确的示例:
public class YourFragment extends Fragment {
private SharedViewModel viewModel;
private Button btn1;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_your, container, false);
btn1 = view.findViewById(R.id.btn1);
// 获取 ViewModel
viewModel = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
// 注册观察者
viewModel.getWaterTemControlType().observe(getViewLifecycleOwner(), value -> {
Log.d("TAG", "onCreateView进来了: ");
if (value != null && value == 1) {
btn1.setText("回水");
}
});
return view;
}
}
注意:使用 getViewLifecycleOwner() 来注册观察者,以确保观察者的生命周期与 Fragment 的视图生命周期一致。这可以避免在视图被销毁后尝试更新 UI 的问题。
- 确保 ViewModel 的作用域
确保你在 Activity 和 Fragment 中使用的是同一个 ViewModel 实例。如果你在 Activity 和 Fragment 中分别创建了不同的 ViewModel 实例,它们之间的 LiveData 更新是不会相互影响的。
通常,你可以通过 ViewModelProvider(requireActivity()) 在 Fragment 中获取 Activity 作用域的 ViewModel 实例,确保 Fragment 和 Activity 使用同一个 ViewModel 实例。
- 检查 Fragment 的生命周期
确保 Fragment 没有被销毁或隐藏,这会导致观察者的回调无法触发。在 Fragment 被创建和视图被创建时注册观察者,避免在 Fragment 被销毁或视图被销毁时更新 UI。
总结
使用 BroadcastReceiver 是一种简单的方式,适合在 Activity 之间进行通知和数据传递。它是基于广播的机制,适合传递较为简单的数据和触发事件。
使用 LiveData 和 ViewModel 是现代 Android 开发中的推荐方式,适合在同一进程内的不同 Activity 或 Fragment 之间共享数据和状态。它更为强大和灵活,特别是对于较复杂的应用架构和数据观察场景。