健康监控应用通过对用户身体的多项指标进行监控,助力维持良好的呼吸与心脏健康。摔倒监测机制能够监测用户的动作。当摔倒发生时,可向应用中所有联系人发送警告。此外,当紧急情况发生时,集成的地图服务和位置服务可以搜寻和标记用户周围最近的医院;集成机器学习服务提供的图片分类功能,用户可以通过相机扫描食物来获取营养信息。
应用功能 | 华为移动服务 |
展示距离最近的医院 | 地图服务 |
搜寻医院 | 位置服务 |
识别用户的活动状态 | 定位服务 |
扫描食物提取营养信息 | 机器学习(图片分类) |
定位当前位置 | 定位服务 |
在这个Codelab中,您将创建一个示例工程,调用定位服务、地图服务、位置服务和机器学习服务的接口。通过示例工程,您可以:
在这个Codelab中,您将学到:
用户可以在应用的功能中心页面选择不同的操作,如计算自己的BMI,查询地域内新冠感染人数,记录日常步数,设置摔倒监测功能,校准传感器等。
用户还可以设置第一联系人和次要联系人。当摔倒发生时,应用会向联系人发出警告。
摔倒监测功能是应用的核心功能,用于监测用户的动作。当摔倒发生时,应用向紧急联系人发送警告信息。用户的动作能够以图表的方式被记录下来。
BMI功能用来检测和记录用户的身体健康状况。用户需要输入体重和身高(公制或英制)。
空气质量指数和天气功能便于用户在户外运动前了解环境状况。有呼吸系统困难的用户会十分重视空气质量指数。
地图服务为您提供了强大、便利的地图服务功能,您可以使用这些功能呈现个性化地图与实现轻松的交互方式。用于安卓应用开发的地图服务SDK提供了诸多用于开发地图功能的接口。您可以轻松地在自己的Android应用中加入地图相关的功能,包括:地图呈现、地图交互、在地图上绘制、自定义地图样式等功能。
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)
}
}
}
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)
}
如图所示,地图上展示了附近的医院。
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)
}
}
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))
}
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()
}
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秒内用户未在紧急求助页面取消发送警告,应用给紧急联系人报警。
干得好,您已成功构建出一款健康监控应用并学到了:
免责声明:本Codelab可用做单项目中集成多个HMS服务的参考。您需要验证并确认相关源码是否合法和安全。