Задача: создать прототип приложения, которое позволяло бы получать частоту сердцебиения с часов на Wear OS и передавать эти данные на смартфон под управлением Android.
Подготовительные работы:
- Создать эмулятор с предустановленными Google Play сервисами
- Установить на эмулятор приложение из Google Play Wear OS (и с не официальных источников https://apkpure.com/wear-os-by-google-smartwatch/com.google.android.wearable.app/download)
- Создать эмулятор для Wear OS (у меня в примере использовался API 30)
- Выполнить сопряжение, используя следующую статью https://developer.android.com/training/wearables/get-started/connect-phone
Результат: будут созданы два приложения, один для часов, другой для смартфона и при этом из package name должен быть одинаковым. А судя из статьи https://stackoverflow.com/questions/48921165/syncing-data-items-between-mobile-device-and-wear ещё и подписи.
- Создаём проем под Android (c одним Активити)
Класс MainActivity должен содержать следующий код:
public class MainActivity extends AppCompatActivity implements DataClient.OnDataChangedListener { ... @Override protected void onCreate(Bundle savedInstanceState) { .... } @Override protected void onResume() { super.onResume(); Wearable.getDataClient(this).addListener(this); } @Override protected void onPause() { super.onPause(); Wearable.getDataClient(this).removeListener(this); } @Override public void onDataChanged(@NonNull DataEventBuffer dataEventBuffer) { for (DataEvent event : dataEventBuffer) { if (event.getType() == DataEvent.TYPE_DELETED) { Log.d("SENSORS", "DataItem deleted: " + event.getDataItem().getUri()); } else if (event.getType() == DataEvent.TYPE_CHANGED) { // Тут, как раз пример получения данных с Wear OS DataItem item = event.getDataItem(); DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap(); Log.d("SENSORS", "DataItem changed: " + event.getDataItem().getUri() + dataMap.getInt(Sensor.STRING_TYPE_HEART_RATE)); } } } }
В Andriod манифест добавляем разрешение:
<uses-permission android:name="android.permission.BODY_SENSORS" />
Примечание: у меня есть сомнение, что это нужно в приложении для смартфона
В gruble нужно добавить ссылку на:
# play-services-wearable dependencies { ... implementation libs.play.services.wearable ... }
2. В созданный проект добавляем модуль для Wear OS с пустым активити
Создаём активити:
public class MainActivity extends AppCompatActivity implements View.OnClickListener, SensorEventListener, DataClient.OnDataChangedListener { public static final String TAG = "HEART_RATE"; protected final ActivityResultLauncher<String[]> mPermissionResultLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), this::onSensorsPermission); private Sensor mHeartRateSensor; private SensorManager mSensorManager; private Button mManagerMonitor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); mManagerMonitor = findViewById(R.id.manager_monitor); mManagerMonitor.setOnClickListener(this); Wearable.getDataClient(this).addListener(this); } @Override protected void onStop() { super.onStop(); Wearable.getDataClient(this).removeListener(this); } @Override public void onClick(View view) { if(view.getId() == R.id.manager_monitor) { if(mManagerMonitor.getText() == getString(R.string.start)) { permissionRequest(); } else { stopMonitor(); } } } @Override public void onSensorChanged(SensorEvent sensorEvent) { if (sensorEvent.sensor.getType() == Sensor.TYPE_HEART_RATE) { String msg = " Value sensor: " + (int)sensorEvent.values[0]; Log.d(TAG, msg); DataClient dataClient = Wearable.getDataClient(this); PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/message").setUrgent(); putDataMapReq.getDataMap().putInt(Sensor.STRING_TYPE_HEART_RATE, (int)sensorEvent.values[0]); PutDataRequest putDataReq = putDataMapReq.asPutDataRequest().setUrgent(); Task<DataItem> putDataTask = dataClient.putDataItem(putDataReq); putDataTask.addOnSuccessListener(new OnSuccessListener<DataItem>() { @Override public void onSuccess(DataItem dataItem) { Log.d(TAG, "sending :)"); } }); } } @Override public void onAccuracyChanged(Sensor sensor, int i) { Log.d(TAG, "onAccuracyChanged - accuracy: " + i); } private void permissionRequest() { if (checkSelfPermission(android.Manifest.permission.BODY_SENSORS) != PackageManager.PERMISSION_GRANTED) { mPermissionResultLauncher.launch(new String[] { android.Manifest.permission.BODY_SENSORS }); } else{ Log.d(TAG,"ALREADY GRANTED"); initSensors(); startMonitor(); } } protected void onSensorsPermission(@NonNull Map<String, Boolean> result) { boolean areAllGranted = true; for (Boolean b : result.values()) { areAllGranted = areAllGranted && b; } if (areAllGranted) { initSensors(); startMonitor(); } else { Snackbar.make(findViewById(R.id.main), "Недостаточно прав для приложения", Snackbar.LENGTH_LONG).setAction("...", view -> { startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getPackageName()))); }).show(); } } private void startMonitor() { mManagerMonitor.setText(getString(R.string.stop)); if (mSensorManager != null && mHeartRateSensor != null) { mSensorManager.registerListener(this, mHeartRateSensor, SensorManager.SENSOR_DELAY_FASTEST); } } private void stopMonitor() { mManagerMonitor.setText(getString(R.string.start)); if (mSensorManager != null) mSensorManager.unregisterListener(this); } private void initSensors() { mSensorManager = (SensorManager)this.getSystemService(Context.SENSOR_SERVICE); mHeartRateSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE); } @Override protected void onDestroy() { stopMonitor(); super.onDestroy(); } @Override public void onDataChanged(@NonNull DataEventBuffer dataEventBuffer) { for (DataEvent event : dataEventBuffer) { if (event.getType() == DataEvent.TYPE_DELETED) { Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri()); } else if (event.getType() == DataEvent.TYPE_CHANGED) { Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri()); } } } }
Этот кусок кода получает ЧСС и передаёт их на смартфон.
Примеры брал из:
- https://developer.android.com/training/wearables/data/events?hl=ru
- https://stackoverflow.com/questions/48921165/syncing-data-items-between-mobile-device-and-wear
- https://stackoverflow.com/questions/44337896/get-heart-rate-from-android-wear
Манифест выглядит следующим образом:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"> <uses-feature android:name="android.hardware.type.watch" /> <uses-permission android:name="android.permission.BODY_SENSORS" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.AppCompat.Light"> <activity android:name=".MainActivity" android:exported="true" android:taskAffinity="" android:theme="@style/Theme.AppCompat.Light.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <meta-data android:name="com.google.android.wearable.standalone" android:value="true" /> </application> </manifest>
build.gradle
plugins { alias(libs.plugins.android.application) } android { namespace 'com.mobwal.budibase.heart' compileSdk 34 defaultConfig { applicationId "com.example.myapplication" minSdk 30 targetSdk 34 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation libs.play.services.wearable implementation libs.appcompat implementation libs.material implementation libs.activity implementation libs.constraintlayout }
Пример проекта тут https://github.com/akrasnov87/heart-rate-monitor