通过MVVM和体系结构组件,正确的方式?

Fal*_*Boy 10 android mvvm bluetooth-lowenergy

我正在使用Bluetooth LE设备,正在考虑当前的方法和最佳实践。当前,我有一个处理连接和GattCallbacks的活动,我决定重组代码以得到更好的概览和可维护性,从而使它实际上很混乱。

我从NordicSemiconductor https://github.com/NordicSemiconductor/Android-BLE-Library/找到了BleManager

它是与BLE设备连接的基本步骤的抽象,它处理GattCallbacks +提供适当的接口,以从服务或ViewModel中使用它。

我想使用ViewModel方法,但对MVC,MVP,MVVM模式不太熟悉,有些问题我仍然无法回答

此类扩展了BleManager(BlinkyManager.java

它显示了如何利用BleManager,因此我采用了该类并对其进行了调用ECountBleManager

编辑:
最近6天,我确实收获颇多,尤其是面对MVVM模式和体系结构组件。不幸的是,我仍然无法回答很多问题。但是我真的想变得更好,所以我草拟了我当前的概念。希望您能帮助我回答问题并改善我的项目。

我对最佳做法特别感兴趣。

这是我的草稿:

在此处输入图片说明

这是我的类实现:
ECountActivity.java

public class ECountActivity extends AppCompatActivity {

    private ECountViewModel viewModel;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.detail_view);

        // hide magnifier icon
        GifImageView customLoader = findViewById(R.id.progressBar);
        customLoader.setVisibility(View.GONE);

        // Get additional data from previous activity
        final BluetoothDevice device = getIntent().getParcelableExtra("device");

        initViewModel();
        viewModel.connect(device);
    }

    private void initViewModel() {
        viewModel = ViewModelProviders.of(this).get(ECountViewModel.class);
        subscribeDataStreams(viewModel);
    }

    private void subscribeDataStreams(ECountViewModel viewModel) {
        viewModel.isDeviceReady().observe(this, deviceReady -> openOptionsFragment());


        viewModel.isConnected().observe(this, status -> {
            // Todo: ...
        });
    }

    private void openOptionsFragment() {
        // load options fragment
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.replace(R.id.contentFragment, new OptionsFragment());
        ft.commitNow();
    }

}
Run Code Online (Sandbox Code Playgroud)

OtaFragment.java

public class OtaFragment extends Fragment implements FolderChooserDialog.FolderCallback,
        FileChooserDialog.FileCallback {

    private Button partialOtaButton;
    private Button fullOtaButton;
    private Button submitButton;
    private SeekBar mtuSeekBar;
    private EditText mtuInput;
    private LinearLayout stacklayout;
    private Button browseAppButton;
    private TextView folderPathText;
    private TextView appFileNameText;

    private MaterialDialog otaPrepareDialog;
    private MaterialDialog otaProgressDialog;

    private ECountViewModel viewModel;
    private OtaViewModel otaViewModel;


    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initViewModel();
    }

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // inflate the layout for this fragment
        View view = inflater.inflate(R.layout.ota_fragment, container, false);
        initViews(view);
        return view;
    }

    @Override
    public void onFolderSelection(@NonNull FolderChooserDialog dialog, @NonNull File folder) {
        final String otaFolderPath = folder.toString();
        otaViewModel.setOtaFolderPath(otaFolderPath);
        folderPathText.setText(otaFolderPath.substring(otaFolderPath.lastIndexOf("/")));
        // enable app browse
        browseAppButton.setClickable(true);
        browseAppButton.setEnabled(true);
    }

    @Override
    public void onFolderChooserDismissed(@NonNull FolderChooserDialog dialog) {}

    @Override
    public void onFileSelection(@NonNull FileChooserDialog dialog, @NonNull File file) {
        final String otaAppFilePath = file.toString();
        otaViewModel.setOtaAppFilePath(otaAppFilePath);
        appFileNameText.setText(otaAppFilePath.substring(otaAppFilePath.lastIndexOf("/")));
        // enable submitButton button
        submitButton.setClickable(true);
        submitButton.setEnabled(true);
    }

    @Override
    public void onFileChooserDismissed(@NonNull FileChooserDialog dialog) {}

    private void subscribeDataStreams(ECountViewModel viewModel) {
        viewModel.isOtaMode().observe(this, otaMode -> {
            otaPrepareDialog.dismiss();
            initOtaProgressDialog();
            otaProgressDialog.show();

            // Todo: how can i get mtu?
            viewModel.requestMtu(512);
        });
    }

    private void initViewModel() {
        viewModel = ViewModelProviders.of(getActivity()).get(ECountViewModel.class);
        otaViewModel = ViewModelProviders.of(getActivity()).get(OtaViewModel.class);
        subscribeDataStreams(viewModel);
    }

    private void initViews(View view) {
        // get resources
        final Button browseFolderButton = view.findViewById(R.id.browseFolder);
        final Button cancelButton = view.findViewById(R.id.ota_cancel);
        final SeekBar prioritySeekBar = view.findViewById(R.id.connection_seekBar);
        partialOtaButton = view.findViewById(R.id.radio_ota);
        fullOtaButton = view.findViewById(R.id.radio_ota_full);
        browseAppButton = view.findViewById(R.id.browseApp);
        folderPathText = view.findViewById(R.id.folderPathText);
        appFileNameText = view.findViewById(R.id.appFileNameText);
        stacklayout = view.findViewById(R.id.stacklayout);
        submitButton = view.findViewById(R.id.ota_proceed);
        mtuSeekBar = view.findViewById(R.id.mtu_seekBar);
        mtuInput = view.findViewById(R.id.mtu_value);

        // set initial states
        mtuSeekBar.setMax(512-23);
        mtuSeekBar.setProgress(244);
        prioritySeekBar.setMax(2);
        prioritySeekBar.setProgress(1);
        browseAppButton.setClickable(false);
        browseAppButton.setEnabled(false);
        submitButton.setClickable(false);
        submitButton.setEnabled(false);

        mtuInput.setOnEditorActionListener((v, actionId, event) -> {
            final Editable mtuText = mtuInput.getText();
            if (mtuText != null) {
                int mtu = Integer.valueOf(mtuText.toString());
                if (mtu < 23)  mtu = 23;
                if (mtu > 512) mtu = 512;
                mtuSeekBar.setProgress(mtu);
                viewModel.setMtu(mtu);
            }
            return false;
        });


        mtuSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                mtuInput.setText(String.valueOf(progress));
                viewModel.setMtu(progress);
            }
        });

        prioritySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener(){
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {}

            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                viewModel.setPriority(progress);
            }
        });

        browseFolderButton.setOnClickListener(v -> new FolderChooserDialog.Builder(getActivity())
                .chooseButton(R.string.positiveTextChoose)
                .tag("#folder")
                .show(getChildFragmentManager()));

        browseAppButton.setOnClickListener(v -> new FileChooserDialog.Builder(getActivity())
                .initialPath(otaViewModel.getOtaFolderPath())
                .extensionsFilter(".ebl")
                .tag("#app")
                .show(getChildFragmentManager()));

        cancelButton.setOnClickListener(v -> Log.i("ota", "cancelButton"));

        submitButton.setOnClickListener(v -> {
            // disable OTA submitButton button
            submitButton.setClickable(false);
            submitButton.setEnabled(false);
            // init OTA process
            viewModel.initOtaMode();
            // show OTA preparing dialog
            otaPrepareDialog.show();
        });

        fullOtaButton.setOnClickListener(v -> {
            stacklayout.setVisibility(View.VISIBLE);
            partialOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
            fullOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
        });

        partialOtaButton.setOnClickListener(v -> {
            stacklayout.setVisibility(View.GONE);
            partialOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimaryDark));
            fullOtaButton.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
        });

        otaPrepareDialog = new MaterialDialog.Builder(getActivity())
                .title(R.string.otaDialogHeaderText)
                .content(R.string.waiting)
                .progress(true, 0)
                .progressIndeterminateStyle(true)
                .build();

        otaProgressDialog = new MaterialDialog.Builder(getActivity())
                .title("test")
                .customView(R.layout.ota_progress2, false)
                .build();
    }

    private void initOtaProgressDialog() {
        // Todo: ...
    }
}
Run Code Online (Sandbox Code Playgroud)

ECountViewModel.java

public class ECountViewModel extends AndroidViewModel implements ECountBleManagerCallbacks {
    private final ECountBleManager eCountBleManager;

    // Connection states Connecting, Connected, Disconnecting, Disconnected etc.
    private final MutableLiveData<String> connectionState = new MutableLiveData<>();

    // Flag to determine if the device is connected
    private final MutableLiveData<Boolean> isConnected = new MutableLiveData<>();

    // Flag to determine if the device is ready
    private final MutableLiveData<Void> onDeviceReady = new MutableLiveData<>();

    // Flag to determine if the device is in OTA mode
    private final MutableLiveData<Void> onOtaMode = new MutableLiveData<>();


    public LiveData<Void> isDeviceReady() {
        return onDeviceReady;
    }

    public LiveData<Void> isOtaMode() {
        return onOtaMode;
    }

    public LiveData<String> getConnectionState() {
        return connectionState;
    }

    public LiveData<Boolean> isConnected() {
        return isConnected;
    }

    public ECountViewModel(@NonNull final Application application) {
        super(application);

        // Initialize the manager
        eCountBleManager = new ECountBleManager(getApplication());
        eCountBleManager.setGattCallbacks(this);
    }

    /**
     * Connect to peripheral
     */
    public void connect(final BluetoothDevice device) {
        eCountBleManager.connect(device);
    }

    /**
     * Disconnect from peripheral
     */
    private void disconnect() {
        eCountBleManager.disconnect();
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if (eCountBleManager.isConnected()) {
            disconnect();
        }
    }

    @Override
    public void onDeviceConnecting(BluetoothDevice device) {
    }

    @Override
    public void onDeviceConnected(BluetoothDevice device) {
        isConnected.postValue(true);
    }

    @Override
    public void onDeviceDisconnecting(BluetoothDevice device) {
        isConnected.postValue(false);
    }

    @Override
    public void onDeviceDisconnected(BluetoothDevice device) {
        isConnected.postValue(false);
    }

    @Override
    public void onLinklossOccur(BluetoothDevice device) {
        isConnected.postValue(false);
    }

    @Override
    public void onServicesDiscovered(BluetoothDevice device, boolean optionalServicesFound) {
    }

    @Override
    public void onDeviceReady(BluetoothDevice device) {
        onDeviceReady.postValue(null);
    }

    @Override
    public void onOptionalServiceSupported(BluetoothDevice device) {
        onOtaMode.postValue(null);
    }

    @Override
    public void onBondingRequired(BluetoothDevice device) {

    }

    @Override
    public void onBonded(BluetoothDevice device) {

    }

    @Override
    public void onError(BluetoothDevice device, String message, int errorCode) {

    }

    @Override
    public void onDeviceNotSupported(BluetoothDevice device) {
        disconnect();
    }

    // delegate call from options fragment to ECountBleManager
    public String getDeviceId() {
        return BinaryUtils.byteArrayToHexString(eCountBleManager.getDeviceId());
    }

    // delegate call from ota fragment to ECountBleManager
    public void setMtu(final int value) {
        eCountBleManager.setMtu(value);
    }

    public void setPriority(final int value) {
        eCountBleManager.setPriority(value);
    }
Run Code Online (Sandbox Code Playgroud)

ECountBleManager.java

public class ECountBleManager extends BleManager<BleManagerCallbacks> {

    private static final String TAG = ECountBleManager.class.getSimpleName();
    private final Handler handler;

    private BluetoothGattCharacteristic authCharacteristic;
    private BluetoothGattCharacteristic deviceIdCharacteristic;
    private BluetoothGattCharacteristic deviceVersionCharacteristic;
    private BluetoothGattCharacteristic configIdCharacteristic;
    private BluetoothGattCharacteristic configTransmissionIntervalCharacteristic;
    private BluetoothGattCharacteristic configKeepAliveIntervalCharacteristic;
    private BluetoothGattCharacteristic configRadioModeCharacteristic;
    private BluetoothGattCharacteristic configGpsCharacteristic;
    private BluetoothGattCharacteristic configRadarCharacteristic;
    private BluetoothGattCharacteristic configOperationModeCharacteristic;
    private BluetoothGattCharacteristic configLoRaAppEuiCharacteristic;
    private BluetoothGattCharacteristic configLoRaAppKeyCharacteristic;
    private BluetoothGattCharacteristic configLoRaDeviceEuiCharacteristic;
    private BluetoothGattCharacteristic operationCmdCharacteristic;
    private BluetoothGattCharacteristic nemeusStatusCharacteristic;
    private BluetoothGattCharacteristic gmrStatusCharacteristic;
    private BluetoothGattCharacteristic radarStatusCharacteristic;
    private BluetoothGattCharacteristic otaControlCharacteristic;
    private BluetoothGattCharacteristic otaDataCharacteristic;

    private byte[] configTransmissionInterval;
    private byte[] configKeepAliveInterval;
    private byte[] configRadioMode;
    private byte[] configOperationMode;
    private byte[] configId;
    private byte[] deviceId;
    private byte[] deviceVersion;
    private byte[] configGps;
    private byte[] configRadar;
    private byte[] configLoRaAppEui;
    private byte[] configLoRaAppKey;
    private byte[] configLoRaDeviceEui;
    private byte[] operationCmd;
    private byte[] nemeusStatus;
    private byte[] gmrStatus;
    private byte[] radarStatus;

    // OTA flags
    private boolean isOtaProcessing = false;
    private boolean isReconnectRequired = false;
    private MutableLiveData<Boolean> isOtaMode = new MutableLiveData<>();
    // OTA variables
    private int mtu = 512;
    private int priority = BluetoothGatt.CONNECTION_PRIORITY_HIGH;
    private byte[] otaAppFileStream;
    ////////////////////////////

    public ECountBleManager(Context context) {
        super(context);
        handler = new Handler();
    }

    @Override
    protected BleManagerGattCallback getGattCallback() {
        return gattCallback;
    }

    @Override
    protected boolean shouldAutoConnect() {
        return true;
    }

    /**
     * BluetoothGatt callbacks for connection/disconnection, service discovery, receiving indication, etc
     */
    private final BleManagerGattCallback gattCallback = new BleManagerGattCallback() {

        @Override
        protected void onDeviceReady() {
            super.onDeviceReady();
        }

        @Override
        protected void onOptionalServiceSupported() {
            super.onOptionalServiceSupported();
            isOtaMode.postValue(true);
        }

        @Override
        protected boolean isOptionalServiceSupported(BluetoothGatt gatt) {
            final BluetoothGattService otaService = gatt.getService(DC_UUID.otaService);
            otaDataCharacteristic = otaService.getCharacteristic(DC_UUID.otaData);
            return otaDataCharacteristic != null;
        }

        @Override
        protected boolean isRequiredServiceSupported(BluetoothGatt gatt) {
            final BluetoothGattService dcService = gatt.getService(DC_UUID.dcService);
            final BluetoothGattService otaService = gatt.getService(DC_UUID.otaService);

            if (dcService == null || otaService == null) return false;

            authCharacteristic = dcService.getCharacteristic(DC_UUID.authentication);
            deviceIdCharacteristic = dcService.getCharacteristic(DC_UUID.deviceId);
            deviceVersionCharacteristic = dcService.getCharacteristic(DC_UUID.deviceVersion);
            configIdCharacteristic = dcService.getCharacteristic(DC_UUID.configId);
            configTransmissionIntervalCharacteristic = dcService.getCharacteristic(DC_UUID.configTransmissionInterval);
            configKeepAliveIntervalCharacteristic = dcService.getCharacteristic(DC_UUID.configKeepAliveInterval);
            configRadioModeCharacteristic = dcService.getCharacteristic(DC_UUID.configRadioMode);
            configGpsCharacteristic = dcService.getCharacteristic(DC_UUID.configGps);
            configRadarCharacteristic = dcService.getCharacteristic(DC_UUID.configRadar);
            configOperationModeCharacteristic = dcService.getCharacteristic(DC_UUID.configOperationMode);
            configLoRaAppEuiCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaAppEui);
            configLoRaAppKeyCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaAppKey);
            configLoRaDeviceEuiCharacteristic = dcService.getCharacteristic(DC_UUID.configLoRaDeviceEui);
            operationCmdCharacteristic = dcService.getCharacteristic(DC_UUID.operationCmd);
            nemeusStatusCharacteristic = dcService.getCharacteristic(DC_UUID.nemeusStatus);
            gmrStatusCharacteristic = dcService.getCharacteristic(DC_UUID.gmrStatus);
            radarStatusCharacteristic = dcService.getCharacteristic(DC_UUID.radarStatus);
            otaControlCharacteristic = otaService.getCharacteristic(DC_UUID.otaControl);

            return authCharacteristic != null &&
                    deviceIdCharacteristic != null &&
                    deviceVersionCharacteristic != null&&
                    configIdCharacteristic != null &&
                    configTransmissionIntervalCharacteristic != null &&
                    configKeepAliveIntervalCharacteristic != null &&
                    configRadioModeCharacteristic != null &&
                    configGpsCharacteristic != null &&
                    configRadarCharacteristic != null &&
                    configOperationModeCharacteristic != null &&
                    configLoRaAppEuiCharacteristic != null &&
                

Max*_*off 1

我对 BLE 和架构的 5 美分:

  • 您无需混淆应用程序架构和 BLE 层。
  • 只需将 BLE 视为数据源/存储库(基于 Rx、协程)
  • 将您的功能分为几个部分:BLE 扫描器、BLE 连接器、BLE 命令执行器。使用 BLE-Fasade 覆盖这些部分。
  • BLE 层通常是异步的东西(而且太复杂,无法使它们 100% 可测试)
  • BLE层深入的看就是字节的线程安全处理。不要在主线程上处理它。