服务场景描述

健康监控应用通过对用户身体的多项指标进行监控,助力维持良好的呼吸与心脏健康。摔倒监测机制能够监测用户的动作。当摔倒发生时,可向应用中所有联系人发送警告。此外,当紧急情况发生时,集成的地图服务和位置服务可以搜寻和标记用户周围最近的医院;集成机器学习服务提供的图片分类功能,用户可以通过相机扫描食物来获取营养信息。

应用功能

华为移动服务

展示距离最近的医院

地图服务

搜寻医院

位置服务

识别用户的活动状态

定位服务

扫描食物提取营养信息

机器学习(图片分类)

定位当前位置

定位服务

您将建立什么

在这个Codelab中,您将创建一个示例工程,调用定位服务、地图服务、位置服务和机器学习服务的接口。通过示例工程,您可以:

您将学到什么

在这个Codelab中,您将学到:

硬件要求

软件要求

参考:
集成准备
集成地图服务
集成位置服务
集成定位服务
集成机器学习服务

  1. 登录AppGallery Connect,选择"我的项目",在"项目设置"页面中,选择"API管理"并开通下述服务的API权限。

用户可以在应用的功能中心页面选择不同的操作,如计算自己的BMI,查询地域内新冠感染人数,记录日常步数,设置摔倒监测功能,校准传感器等。
用户还可以设置第一联系人和次要联系人。当摔倒发生时,应用会向联系人发出警告。

摔倒监测功能是应用的核心功能,用于监测用户的动作。当摔倒发生时,应用向紧急联系人发送警告信息。用户的动作能够以图表的方式被记录下来。
BMI功能用来检测和记录用户的身体健康状况。用户需要输入体重和身高(公制或英制)。

空气质量指数和天气功能便于用户在户外运动前了解环境状况。有呼吸系统困难的用户会十分重视空气质量指数。

类图

集成地图服务

地图服务为您提供了强大、便利的地图服务功能,您可以使用这些功能呈现个性化地图与实现轻松的交互方式。用于安卓应用开发的地图服务SDK提供了诸多用于开发地图功能的接口。您可以轻松地在自己的Android应用中加入地图相关的功能,包括:地图呈现、地图交互、在地图上绘制、自定义地图样式等功能。

  1. 在应用启动的Nearest Hospital activity中,调用initViews方法初始化服务。
    Java
    fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this); settingsClient = LocationServices.getSettingsClient(this); mLocationRequest = new LocationRequest(); mLocationRequest.setInterval(10000); mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); mLocationCallback = new LocationCallback() { @Override public void onLocationResult(LocationResult locationResult) { if (locationResult != null) { List locations = locationResult.getLocations(); if (!locations.isEmpty()) { for (Location location : locations) { mCurrentLocation.setLatitude(location.getLatitude()); mCurrentLocation.setLongitude(location.getLongitude()); if (!isCalledHospitalApi && location.getAccuracy() < 35) { isCalledHospitalApi = true; callNearByHospitalApi(mRadius); } Log.i(TAG, "onLocationResult location[Longitude,Latitude,Accuracy]:" + location.getLongitude() + "," + location.getLatitude() + "," + location.getAccuracy()); } } } } @Override public void onLocationAvailability(LocationAvailability locationAvailability) { if (locationAvailability != null) { boolean flag = locationAvailability.isLocationAvailable(); Log.i(TAG, "onLocationAvailability isLocationAvailable:" + flag); } } };

    Kotlin

    fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) settingsClient = LocationServices.getSettingsClient(this) mLocationRequest = LocationRequest() mLocationRequest!!.interval = Constants.INTERVAL_10000.toLong() mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY mLocationCallback = object : LocationCallback() { override fun onLocationResult(locationResult: LocationResult) { if (locationResult != null) { val locations = locationResult.locations if (!locations.isEmpty()) { for (location in locations) { mCurrentLocation.latitude = location.latitude mCurrentLocation.longitude = location.longitude if (!isCalledHospitalApi && location.accuracy < Constants.MAX_ACCURACY) { isCalledHospitalApi = true callNearByHospitalApi(mRadius) } Log.d(TAG, getString(R.string.on_location_result) + location.longitude + Constants.STR_COMMA + location.latitude + Constants.STR_COMMA + location.accuracy) } } } } override fun onLocationAvailability(locationAvailability: LocationAvailability) { if (locationAvailability != null) { val flag = locationAvailability.isLocationAvailable Log.d(TAG, getString(R.string.on_location_availability_is_location_available) + flag) } } }
  2. 调用callNearByHospitalApi方法获取附近的医院列表,调用addHospitalMarkerToMap方法在华为地图上添加附近医院标记。

    Java

    private void callNearByHospitalApi(int mRadius) { NearbySearchRequest mHospitalRequest = new NearbySearchRequest(); mHospitalRequest.setLocation(new Coordinate(mCurrentLocation.getLatitude(), mCurrentLocation.getLongitude())); mHospitalRequest.setRadius(mRadius * Constants.INIT_1000); mHospitalRequest.setPoiType(LocationType.HOSPITAL); mHospitalRequest.setLanguage(String.valueOf(Constants.LANGUAGE_EN)); SearchResultListener mListener = new SearchResultListener() { @Override public void onSearchResult(NearbySearchResponse nearbySearchResponse) { mSites = new ArrayList<>(); mSites = (ArrayList) nearbySearchResponse.getSites(); if (mSites != null) { for (int i = INIT_ZERO; i < mSites.size(); i++) { addHospitalMarkerToMap(mSites.get(i)); } } else { Log.d(TAG, getString(R.string.no_near_hospital)); } } @Override public void onSearchError(SearchStatus searchStatus) { } }; mSearchService.nearbySearch(mHospitalRequest, mListener); }

    Kotlin
    private fun callNearByHospitalApi(mRadius: Int) {
    val mHospitalRequest = NearbySearchRequest()
    mHospitalRequest.location = Coordinate(mCurrentLocation.latitude, mCurrentLocation.longitude)
    mHospitalRequest.radius = mRadius * Constants.INIT_1000
    mHospitalRequest.poiType = LocationType.HOSPITAL
    mHospitalRequest.language = Constants.LANGUAGE_EN
    val mListener: SearchResultListener<NearbySearchResponse> = object : SearchResultListener<NearbySearchResponse> {

    private fun callNearByHospitalApi(mRadius: Int) { val mHospitalRequest = NearbySearchRequest() mHospitalRequest.location = Coordinate(mCurrentLocation.latitude, mCurrentLocation.longitude) mHospitalRequest.radius = mRadius * Constants.INIT_1000 mHospitalRequest.poiType = LocationType.HOSPITAL mHospitalRequest.language = Constants.LANGUAGE_EN val mListener: SearchResultListener<NearbySearchResponse> = object : SearchResultListener<NearbySearchResponse> { override fun onSearchResult(nearbySearchResponse: NearbySearchResponse) { mSites = ArrayList() mSites = nearbySearchResponse.sites as ArrayList if (mSites != null) { for (i in Constants.INIT_ZERO until mSites!!.size) { addHospitalMarkerToMap(mSites!![i]) } } else { Log.d(TAG, getString(R.string.no_near_hospital)) } } override fun onSearchError(searchStatus: SearchStatus) {} } mSearchService!!.nearbySearch(mHospitalRequest, mListener) }

    如图所示,地图上展示了附近的医院。

集成机器学习服务

  1. 获取MLApplication的实例,在FoodDetectionActivity的onCreate方法中设置应用的APiKey。

    Java

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_food_detection); mPreviewView = findViewById(R.id.camera); captureImage = findViewById(R.id.btncapture); mDetectedFood = findViewById(R.id.tv_detectedFood); alertDialog = new Dialog(this); MLApplication.getInstance().setApiKey ("API_KEY"); if (allPermissionsGranted()) { startCamera(); //若用户已授权,启用摄像头。 } else { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS); } }

    Kotlin

    private var alertDialog: Dialog? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_food_detection) mPreviewView = findViewById(R.id.camera) captureImage = findViewById(R.id.btncapture) mDetectedFood = findViewById(R.id.tv_detectedFood) alertDialog = Dialog(this) MLApplication.getInstance().apiKey = R.string.API_KEY.toString() if (allPermissionsGranted()) { startCamera() //若用户已授权,启用摄像头。 } else { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS) } }
  2. 打开摄像头捕获图片,并将图片保存在文件夹下,否则将无法识别食物及其营养信息。

    Java

    private void startCamera() { final ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(this); cameraProviderFuture.addListener(new Runnable() { @Override public void run() { try { ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); bindPreview(cameraProvider); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); } } }, ContextCompat.getMainExecutor(this)); }

    Kotlin

    private fun startCamera() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener(Runnable { try { val cameraProvider = cameraProviderFuture.get() bindPreview(cameraProvider) } catch (e: ExecutionException) { e.printStackTrace(); } catch (e: InterruptedException) { e.printStackTrace(); } }, ContextCompat.getMainExecutor(this)) }
  3. 图片保存到文件夹下后,获取图片路径并作为参数传递给MLObjectAnalyzerSetting方法来获取图片分类名称。

    Java

    Bitmap myBitmap = BitmapFactory.decodeFile(file.getAbsolutePath()); MLObjectAnalyzerSetting setting = new MLObjectAnalyzerSetting.Factory() .setAnalyzerType(MLObjectAnalyzerSetting.TYPE_PICTURE) .allowMultiResults() .allowClassification() .create(); objectAnalyzer = MLAnalyzerFactory.getInstance() getLocalObjectAnalyzer(setting); final MLFrame frame = MLFrame.fromBitmap(myBitmap); Task<List<MLObject>> task = objectAnalyzer.asyncAnalyseFrame(frame); // Asynchronously process the result returned by the object detector. task.addOnSuccessListener(new OnSuccessListener<List<MLObject>>() { @Override public void onSuccess(List<MLObject> objects) { SparseArray<MLObject> objectSparseArray = objectAnalyzer.analyseFrame(frame); for (int i = 0; i < objectSparseArray.size(); i++) { if (objectSparseArray.valueAt(i).getTypeIdentity() == MLObject.TYPE_FOOD) { // Toast.makeText(CameraActivity.this, "It is FOOD", Toast.LENGTH_SHORT).show(); // IMAGE Classification ... MLRemoteClassificationAnalyzerSetting cloudSetting = new MLRemoteClassificationAnalyzerSetting.Factory() .setMinAcceptablePossibility(0.8f) .create(); cloudImageClassificationAnalyzer = MLAnalyzerFactory.getInstance(). getRemoteImageClassificationAnalyzer(cloudSetting); MLFrame frame = MLFrame.fromBitmap(myBitmap); Task<List<MLImageClassification>> task = cloudImageClassificationAnalyzer.asyncAnalyseFrame(frame); task.addOnSuccessListener(new OnSuccessListener<List<MLImageClassification>>() { @Override public void onSuccess(List<MLImageClassification> classifications) { dismissLoadingDialog(); // String result = ""; ArrayList<String> result = new ArrayList<>(); for (MLImageClassification classification : classifications) { result.add(classification.getName()); } Log.d(TAG, "onSuccess: " + result); StringBuilder detectedFood = new StringBuilder(); for (String details : result) { detectedFood.append(details).append(","); } mDetectedFood.setText(detectedFood.toString()); //Toast.makeText(FoodDetectionActivity.this, "" + result.get(0) + "," + result.get(result.size() - 1), Toast.LENGTH_LONG).show(); } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { try { MLException mlException = (MLException) e; int errorCode = mlException.getErrCode(); String errorMessage = mlException.getMessage(); Toast.makeText(FoodDetectionActivity.this, "" + errorMessage, Toast.LENGTH_SHORT).show(); } catch (Exception error) { // 处理转换异常。 Toast.makeText(FoodDetectionActivity.this, "" + error.getMessage(), Toast.LENGTH_SHORT).show(); } } }); } else { dismissLoadingDialog(); Toast.makeText(FoodDetectionActivity.this, "NOT FOOD", Toast.LENGTH_SHORT).show(); } } } }).addOnFailureListener(new OnFailureListener() { @Override public void onFailure(Exception e) { dismissLoadingDialog(); // 检测是否失败 Toast.makeText(FoodDetectionActivity.this, "Detection Failed", Toast.LENGTH_SHORT).show(); } });

    Kotlin

    var myBitmap: Bitmap? = null try { myBitmap = BitmapFactory.decodeFile(finalFile.canonicalPath) } catch (e: IOException) { ExceptionHandling.PrintExceptionInfo(getString(R.string.exception_str_io), e) } val setting = MLObjectAnalyzerSetting.Factory() .setAnalyzerType(MLObjectAnalyzerSetting.TYPE_PICTURE) .allowMultiResults() .allowClassification() .create() objectAnalyzer = MLAnalyzerFactory.getInstance().getLocalObjectAnalyzer(setting) val frame = MLFrame.fromBitmap(myBitmap) val task = objectAnalyzer?.asyncAnalyseFrame(frame) // 异步处理对象检测器返回的结果。 val finalMyBitmap = myBitmap task?.addOnSuccessListener { val objectSparseArray = objectAnalyzer?.analyseFrame(frame) for (i in Constants.INIT_ZERO until objectSparseArray?.size()!!) { if (objectSparseArray?.valueAt(i)?.typeIdentity == MLObject.TYPE_FOOD) { // 图片分类 ... val cloudSetting = MLRemoteClassificationAnalyzerSetting.Factory() .setMinAcceptablePossibility(Constants.FLOAT_8F) .create() cloudImageClassificationAnalyzer = MLAnalyzerFactory.getInstance().getRemoteImageClassificationAnalyzer(cloudSetting) val frame = MLFrame.fromBitmap(finalMyBitmap) val task = cloudImageClassificationAnalyzer?.asyncAnalyseFrame(frame) task?.addOnSuccessListener { classifications -> dismissLoadingDialog() val result = ArrayList<String>() for (classification in classifications) { result.add(classification.name) } val detectedFood = StringBuilder() for (details in result) { detectedFood.append(details).append(Constants.STR_COMMA) } mDetectedFood!!.text = detectedFood.toString() }?.addOnFailureListener { e -> try { val mlException = e as MLException val errorCode = mlException.errCode val errorMessage = mlException.message Toast.makeText(this@FoodDetectionActivity, errorMessage, Toast.LENGTH_SHORT).show() } catch (error: Exception) { // 处理转换异常。 Toast.makeText(this@FoodDetectionActivity, error.message, Toast.LENGTH_SHORT).show() } } } else { dismissLoadingDialog() Toast.makeText(this@FoodDetectionActivity, getString(R.string.not_food), Toast.LENGTH_SHORT).show() } } }?.addOnFailureListener { dismissLoadingDialog() // 检测失败。 Toast.makeText(this@FoodDetectionActivity, R.string.detection_failed, Toast.LENGTH_SHORT).show() }
  4. 基于用户设置的阈值实现摔倒监测逻辑。

    Java

    double[] gravity_updated_values = new double[3]; double[] gravity = new double[3]; gravity_updated_values[ARRAY_INDEX_FIRST] = gravity[ARRAY_INDEX_FIRST]; gravity_updated_values[ARRAY_INDEX_SECOND] = gravity[ARRAY_INDEX_SECOND]; gravity_updated_values[ARRAY_INDEX_THIRD] = gravity[ARRAY_INDEX_THIRD]; gravity[ARRAY_INDEX_FIRST] = event.values[ARRAY_INDEX_FIRST]; gravity[ARRAY_INDEX_SECOND] = event.values[ARRAY_INDEX_SECOND]; gravity[ARRAY_INDEX_THIRD] = event.values[ARRAY_INDEX_THIRD]; double updatedAmount = Math.pow((gravity[ARRAY_INDEX_FIRST] - gravity_updated_values[ARRAY_INDEX_FIRST]), Constants.INIT_2) + Math.pow((gravity[ARRAY_INDEX_SECOND] - gravity_updated_values[ARRAY_INDEX_SECOND]), Constants.INIT_2) + Math.pow((gravity[ARRAY_INDEX_THIRD] - gravity_updated_values[ARRAY_INDEX_THIRD]), Constants.INIT_2); if (!firstChange && updatedAmount >= thresholdBoundary) { startActivity(new Intent(this, SOSActivity.class)); finish(); }

    Kotlin

    val gravity_updated_values = DoubleArray(3) val gravity = DoubleArray(3) gravity_updated_values[ARRAY_INDEX_FIRST] = gravity[ARRAY_INDEX_FIRST] gravity_updated_values[ARRAY_INDEX_SECOND] = gravity[ARRAY_INDEX_SECOND] gravity_updated_values[ARRAY_INDEX_THIRD] = gravity[ARRAY_INDEX_THIRD] gravity[ARRAY_INDEX_FIRST] = event.values[ARRAY_INDEX_FIRST].toDouble() gravity[ARRAY_INDEX_SECOND] = event.values[ARRAY_INDEX_SECOND].toDouble() gravity[ARRAY_INDEX_THIRD] = event.values[ARRAY_INDEX_THIRD].toDouble() val updatedAmount = Math.pow(gravity[ARRAY_INDEX_FIRST] - gravity_updated_values[ARRAY_INDEX_FIRST], Constants.INIT_2.toDouble()) + Math.pow(gravity[ARRAY_INDEX_SECOND] - gravity_updated_values[ARRAY_INDEX_SECOND], Constants.INIT_2.toDouble()) + Math.pow(gravity[ARRAY_INDEX_THIRD] - gravity_updated_values[ARRAY_INDEX_THIRD], Constants.INIT_2.toDouble()) if (!firstChange && updatedAmount >= thresholdBoundary) { startActivity(Intent(this, SOSActivity::class.java)) finish() }

若摔倒未发生,应用错误识别摔倒的情况下,用户可以取消向紧急联系人发送警报。若识别摔倒后15秒内用户未在紧急求助页面取消发送警告,应用给紧急联系人报警。

干得好,您已成功构建出一款健康监控应用并学到了:

  1. 地图服务
  2. 位置服务
  3. 机器学习服务
  4. 定位服务
本Codelab中的实例代码下载地址如下:
源码下载

免责声明:本Codelab可用做单项目中集成多个HMS服务的参考。您需要验证并确认相关源码是否合法和安全。

Code copied