【木棉花】儿童手表定位,活动范围监控 原创 精华
前言
场景设想:孩子戴着手表去上补习班、在小区内玩耍等等,手表可以将实时位置发送给家长,超出设置的活动范围内时提醒家长,实现孩子安全防患于未然。
本次项目由于缺少鸿蒙手表设备,故用手机或平板替代。
概述
孩子端设备界面如下,每隔五秒钟将实时位置发送给家长端设备:
家长端设备每隔五秒钟将孩子端设备的实时位置显示出来:
孩子端设备不在设定的经纬度范围时会显示“孩子已超出设定范围”,并发出声音提醒家长:
孩子端设备在设定的经纬度范围时会显示“孩子没有超出设定范围”,并停止发出声音:
当没有设定范围或设定的经纬度范围不合理时即经度小于0°大于180°,纬度小于0°大于90°时,显示“没有设定范围或者输入的经度不在0到180之间、输入的纬度不在0到90之间”:
正文
一、创建一个空白的工程
1. 安装和配置DevEco Studio 2.1 Release
DevEco Studio 2.1 Release下载、DevEco Studio 2.1 Release安装
2. 创建一个Empty Java Phone应用
DevEco Studio下载安装成功后,打开DevEco Studio,点击左上角的File,点击New,再选择New Project,选择Empty Ability(Java)选项,点击Next按钮。
将文件命名为ChildrenLocation(文件名不能出现中文或者特殊字符,否则将无法成功创建项目文件),选择保存路径,选择API5,设备勾选Phone、Tablet,最后点击Finish按钮。
3. 准备工作
在entry>src>main>config.json文件中最下方"launchType": "standard"的后面添加以下代码,这样就可以实现去掉应用上方的标签栏了。
config.json最下方部分代码:
"orientation": "unspecified",
"name": "com.test.qixifestival.MainAbility",
"icon": "$media:icon",
"description": "$string:mainability_description",
"label": "$string:entry_MainAbility",
"type": "page",
"launchType": "standard",
"metaData": {
"customizeData": [
{
"name": "hwc-theme",
"value": "androidhwext:style/Theme.Emui.Light.NoTitleBar",
"extra": ""
}
]
}
}
]
}
}
二、实现界面布局
1. 孩子端设备界面
在ChildrenLocation>entry>src>main>resources>base>layout>ability_main.xml添加布局代码。
删除已有的Text组件。
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="center"
ohos:orientation="vertical">
</DirectionalLayout>
添加一个名为ability_wearable.xml布局文件,布局代码如下,将相应图片文件放到文件夹media中,并且命名为image。
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:orientation="vertical">
<Image
ohos:height="match_parent"
ohos:width="match_parent"
ohos:image_src="$media:image"
ohos:layout_alignment="center"/>
</DirectionalLayout>
2. 家长端设备界面
添加一个名为ability_phone.xml布局文件,布局代码如下。
<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
xmlns:ohos="http://schemas.huawei.com/res/ohos"
ohos:height="match_parent"
ohos:width="match_parent"
ohos:alignment="top|center"
ohos:orientation="vertical">
<Text
ohos:id="$+id:Longitude"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:text="---"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#78C6C5"/>
<Text
ohos:id="$+id:Latitude"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:text="---"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#78C6C5"/>
<Text
ohos:id="$+id:CountryName"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:text="---"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#78C6C5"/>
<Text
ohos:id="$+id:PlaceName"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:text="---"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#78C6C5"
ohos:multiple_lines="true"/>
<TextField
ohos:id="$+id:tf_MinLongitude"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:hint="请输入最小经度......"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#BEBEBE"/>
<TextField
ohos:id="$+id:tf_MaxLongitude"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:hint="请输入最大经度......"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#BEBEBE"/>
<TextField
ohos:id="$+id:tf_MinLatitude"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:hint="请输入最小纬度......"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#BEBEBE"/>
<TextField
ohos:id="$+id:tf_MaxLatitude"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:hint="请输入最大纬度......"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#BEBEBE"/>
<Text
ohos:id="$+id:IDIDID"
ohos:height="match_content"
ohos:width="match_parent"
ohos:margin="10vp"
ohos:padding="3vp"
ohos:text="---"
ohos:text_size="28fp"
ohos:text_color="#000000"
ohos:text_alignment="left|vertical_center"
ohos:background_element="#78C6C5"
ohos:multiple_lines="true"/>
</DirectionalLayout>
在ChildrenLocation>entry>src>main>java>com.test.qixifestival>slice>MainAbilitySlice.java编写以下代码。
1)如果是不同类型的设备,例如手机和平板,那可以根据设备类型来区分家长端和孩子端。
2)如果是相同类型不同型号的设备,例如HUAWEI P40和nova 5 Pro,那可以根据设备型号来区分家长端和孩子端。
3)如果是相同类型相同型号的设备,那只能根据设备的id来区分家长端和孩子端,但是不推荐此方法。
public void onStart(Intent intent) {
super.onStart(intent);
//1)根据设备类型来区分家长端和孩子端
/*String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getType();
if(s.equals("14")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
}*/
//2)根据设备型号来区分家长端和孩子端
String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getName();
if(s.equals("HUAWEI P40")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
}
//3)根据设备的id来区分家长端和孩子端
/*String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId();
if(s.equals("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
}*/
}
三、获取孩子端设备实时位置
1. 添加权限
在config.json添加权限。
"reqPermissions": [
{
"name": "ohos.permission.LOCATION"
}
]
2. 获取孩子端位置
在MainAbility.java中动态获取权限,并创建一个静态成员变量用于获取位置信息。
public class MainAbility extends Ability {
Locator locator;
MyLocatorCallback locatorCallback;
RequestParam requestParam;
public static Location location = null;
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
// 动态判断权限
if (verifySelfPermission("ohos.permission.LOCATION") != IBundleManager.PERMISSION_GRANTED) {
// 应用未被授予权限
if (canRequestPermission("ohos.permission.LOCATION")) {
// 是否可以申请弹框授权(首次申请或者用户未选择禁止且不再提示)
requestPermissionsFromUser(new String[]{"ohos.permission.LOCATION"}, 0);
}
} else {
initLocator();
}
}
private void initLocator() {
locator = new Locator(this);
requestParam = new RequestParam(RequestParam.SCENE_NAVIGATION);
locatorCallback = new MyLocatorCallback();
locator.startLocating(requestParam, locatorCallback);
}
@Override
protected void onStop() {
super.onStop();
locator.stopLocating(locatorCallback);
}
public class MyLocatorCallback implements LocatorCallback {//获取定位信息
@Override
public void onLocationReport(Location location) {
if (location != null) {
MainAbility.location = location;
}
}
@Override
public void onStatusChanged(int type) {
}
@Override
public void onErrorReport(int type) {
}
}
}
3. 添加一个分布式数据库
在config.json添加权限。
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
},
{
"name": "ohos.permission.LOCATION"
}
]
在MainAbility.java中动态申请权限。
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
// 动态判断权限
if (verifySelfPermission("ohos.permission.LOCATION") != IBundleManager.PERMISSION_GRANTED || verifySelfPermission("ohos.permission.DISTRIBUTED_DATASYNC") != IBundleManager.PERMISSION_GRANTED) {
// 应用未被授予权限
if (canRequestPermission("ohos.permission.LOCATION") || canRequestPermission("ohos.permission.DISTRIBUTED_DATASYNC")) {
// 是否可以申请弹框授权(首次申请或者用户未选择禁止且不再提示)
requestPermissionsFromUser(new String[]{"ohos.permission.LOCATION", "ohos.permission.DISTRIBUTED_DATASYNC"}, 0);
}
} else {
initLocator();
}
}
在MainAbilitySlice.java中编写以下代码。
添加四个变量Longitude、Latitude、PlaceName和CountryName,并初始化为“null”。
创建分布式数据库管理器实例KvManager,并借助KvManager创建SINGLE_VERSION分布式数据库,最后订阅分布式数据库中数据变化。
public static final String STORE_ID = "contact_db1";//分布式数据库标识
public static SingleKvStore singleKvStore;
public static KvManager kvManager;
private static String Longitude = "null";
private static String Latitude = "null";
private static String PlaceName = "null";
private static String CountryName = "null";
public void initDbManager() {
kvManager = createManager();
singleKvStore = createDb(kvManager);
subscribeDb(singleKvStore);
}
public KvManager createManager() {//创建分布式数据库管理器实例KvManager
KvManager manager = null;
try {
KvManagerConfig config = new KvManagerConfig(this);
manager = KvManagerFactory.getInstance().createKvManager(config);
}
catch (KvStoreException exception) {
}
return manager;
}
private SingleKvStore createDb(KvManager kvManager) {//借助KvManager创建SINGLE_VERSION分布式数据库
SingleKvStore kvStore = null;
try {
Options options = new Options();
options.setCreateIfMissing(true).setEncrypt(false).setKvStoreType(KvStoreType.SINGLE_VERSION);
kvStore = kvManager.getKvStore(options, STORE_ID);
} catch (KvStoreException exception) {
}
return kvStore;
}
private void subscribeDb(SingleKvStore singleKvStore) {//订阅分布式数据库中数据变化
class KvStoreObserverClient implements KvStoreObserver {
@Override
public void onChange(ChangeNotification notification) {
Longitude = queryContact("Longitude");
Latitude = queryContact("Latitude");
PlaceName = queryContact("PlaceName");
CountryName = queryContact("CountryName");
}
}
KvStoreObserver kvStoreObserverClient = new KvStoreObserverClient();
singleKvStore.subscribe(SubscribeType.SUBSCRIBE_TYPE_ALL, kvStoreObserverClient);
}
public static void writeData(String key, String value) {//数据插入
if (key == null || key.isEmpty() || value == null || value.isEmpty()) {
return;
}
singleKvStore.putString(key, value);
}
public static String queryContact(String key) {//数据查询
String value = singleKvStore.getString(key);
return value;
}
4. 将实时位置写入分布式数据库中
通过location.getLongitude()方法获取经度,location.getLatitude()方法获取纬度,geoAddress.getPlaceName()方法获取位置,geoAddress.getCountryName()方法获取国家。
创建时间任务,每隔五秒钟获取一次孩子端设备位置信息,并将信息写入分布式数据库中。
import static com.test.childrenlocation.MainAbility.location;
private Timer timer_wearable;
public void onStart(Intent intent) {
super.onStart(intent);
//1)根据设备类型来区分家长端和孩子端
/*String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getType();
if(s.equals("14")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
}*/
//2)根据设备型号来区分家长端和孩子端
String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getName();
initDbManager();
if(s.equals("HUAWEI P40")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
setLocation();
}
//3)根据设备的id来区分家长端和孩子端
/*String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId();
if(s.equals("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
}*/
}
private void setLocation(){
timer_wearable = new Timer();
timer_wearable.schedule(new TimerTask() {
@Override
public void run() {
getUITaskDispatcher().asyncDispatch(new Runnable() {
@Override
public void run() {
setSingleLocation();
writeData("Longitude", Longitude);
writeData("Latitude", Latitude);
writeData("PlaceName", PlaceName);
writeData("CountryName", CountryName);
}
});
}
}, 0, 5000);
}
private void setSingleLocation(){
if(location == null){
Longitude = "null";
Latitude = "null";
return;
}
Longitude = Double.toString(location.getLongitude());
Latitude = Double.toString(location.getLatitude());
GeoConvert geoConvert = new GeoConvert();
try {
List<GeoAddress> list = geoConvert.getAddressFromLocation(location.getLatitude(),location.getLongitude(),1);
GeoAddress geoAddress = list.get(0);
if(geoAddress == null){
PlaceName = "null";
CountryName = "null";
return;
}
PlaceName = geoAddress.getPlaceName();
CountryName = geoAddress.getCountryName();
} catch (IOException e) {
e.printStackTrace();
}
}
四、显示孩子端设备实时信息
1. 通过service播放声音
添加一个ServiceAbility.java,将音频文件放到文件夹rawfile中,并命名为text.mp3。在onStart中播放音频,在onStop中停止播放音频。
public class ServiceAbility extends Ability {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
private Player player = new Player(this);
@Override
public void onStart(Intent intent) {
HiLog.error(LABEL_LOG, "ServiceAbility::onStart");
super.onStart(intent);
EventRunner eventRunner =EventRunner.create(true);
EventHandler eventHandler = new EventHandler(eventRunner);
eventHandler.postSyncTask(new Runnable() {
@Override
public void run() {
try{
RawFileDescriptor rawFileDescriptor = getResourceManager()
.getRawFileEntry("resources/rawfile/text.mp3")
.openRawFileDescriptor();
Source source = new Source(rawFileDescriptor.getFileDescriptor(),
rawFileDescriptor.getStartPosition(),
rawFileDescriptor.getFileSize());
player.setSource(source);
player.prepare();
player.play();
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
@Override
public void onBackground() {
super.onBackground();
HiLog.info(LABEL_LOG, "ServiceAbility::onBackground");
}
@Override
public void onStop() {
super.onStop();
HiLog.info(LABEL_LOG, "ServiceAbility::onStop");
if(player.isNowPlaying()){
player.stop();
player.release();
}
}
@Override
public void onCommand(Intent intent, boolean restart, int startId) {
}
@Override
public IRemoteObject onConnect(Intent intent) {
return null;
}
@Override
public void onDisconnect(Intent intent) {
}
}
2. 显示实时位置
在MainAbilitySlice.java中编写以下代码。
通过查询分布式数据库中的key值得到孩子端设备实时位置信息,并写到文本中。
private void getSingleLocation(){
Text text_Longitude = (Text) findComponentById(ResourceTable.Id_Longitude);
Text text_Latitude = (Text) findComponentById(ResourceTable.Id_Latitude);
Text text_PlaceName = (Text) findComponentById(ResourceTable.Id_PlaceName);
Text text_CountryName = (Text) findComponentById(ResourceTable.Id_CountryName);
Longitude = queryContact("Longitude");
Latitude = queryContact("Latitude");
PlaceName = queryContact("PlaceName");
CountryName = queryContact("CountryName");
text_Longitude.setText("经度:" + Longitude);
text_Latitude.setText("纬度:" + Latitude);
text_PlaceName.setText("位置:" + PlaceName);
text_CountryName.setText("国家:" + CountryName);
}
3. 添加活动范围监听事件
从四个文本输入框中获取设定的位置范围,并与实时位置判断,如果超出设定的位置范围则通过startAbility方法播放音频,否则通过stopAbility方法停止播放音频。
private Timer timer_phone;
public void onStart(Intent intent) {
super.onStart(intent);
//1)根据设备类型来区分家长端和孩子端
/*String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getType();
if(s.equals("14")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
}*/
//2)根据设备型号来区分家长端和孩子端
String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getName();
initDbManager();
if(s.equals("HUAWEI P40")){
super.setUIContent(ResourceTable.Layout_ability_phone);
getLocation();
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
setLocation();
}
//3)根据设备的id来区分家长端和孩子端
/*String s = KvManagerFactory.getInstance().createKvManager(new KvManagerConfig(this)).getLocalDeviceInfo().getId();
if(s.equals("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")){
super.setUIContent(ResourceTable.Layout_ability_phone);
}else{
super.setUIContent(ResourceTable.Layout_ability_wearable);
}*/
}
private void getLocation(){
writeData("Longitude", Longitude);
writeData("Latitude", Latitude);
writeData("PlaceName", PlaceName);
writeData("CountryName", CountryName);
timer_phone = new Timer();
timer_phone.schedule(new TimerTask() {
@Override
public void run() {
getUITaskDispatcher().asyncDispatch(new Runnable() {
@Override
public void run() {
getSingleLocation();
getRange();
}
});
}
},0,5000);
}
private void getSingleLocation(){
Text text_Longitude = (Text) findComponentById(ResourceTable.Id_Longitude);
Text text_Latitude = (Text) findComponentById(ResourceTable.Id_Latitude);
Text text_PlaceName = (Text) findComponentById(ResourceTable.Id_PlaceName);
Text text_CountryName = (Text) findComponentById(ResourceTable.Id_CountryName);
Longitude = queryContact("Longitude");
Latitude = queryContact("Latitude");
PlaceName = queryContact("PlaceName");
CountryName = queryContact("CountryName");
text_Longitude.setText("经度:" + Longitude);
text_Latitude.setText("纬度:" + Latitude);
text_PlaceName.setText("位置:" + PlaceName);
text_CountryName.setText("国家:" + CountryName);
}
private void getRange(){
TextField tf_MinLongitude = (TextField) findComponentById(ResourceTable.Id_tf_MinLongitude);
TextField tf_MaxLongitude = (TextField) findComponentById(ResourceTable.Id_tf_MaxLongitude);
TextField tf_MinLatitude = (TextField) findComponentById(ResourceTable.Id_tf_MinLatitude);
TextField tf_MaxLatitude = (TextField) findComponentById(ResourceTable.Id_tf_MaxLatitude);
Text text = (Text) findComponentById(ResourceTable.Id_IDIDID);
double MinLongitude;
double MaxLongitude;
double MinLatitude;
double MaxLatitude;
double Lon;
double Lat;
if(tf_MinLongitude.getText().equals("")){
MinLongitude = 190;
}else {
MinLongitude = Double.parseDouble(tf_MinLongitude.getText());
}
if(tf_MaxLongitude.getText().equals("")){
MaxLongitude = 190;
}else {
MaxLongitude = Double.valueOf(tf_MaxLongitude.getText());
}
if(tf_MinLatitude.getText().equals("")){
MinLatitude = 190;
}else {
MinLatitude = Double.valueOf(tf_MinLatitude.getText());
}
if(tf_MaxLatitude.getText().equals("")){
MaxLatitude = 190;
}else {
MaxLatitude = Double.valueOf(tf_MaxLatitude.getText());
}
Intent serviceintent = new Intent();
Operation serviceOperation = new Intent.OperationBuilder()
.withDeviceId("")
.withBundleName(getBundleName())
.withAbilityName(ServiceAbility.class.getName())
.build();
serviceintent.setOperation(serviceOperation);
if(!(Longitude.equals("null") && Latitude.equals("null"))){
Lon = Double.valueOf(Longitude);
Lat = Double.valueOf(Latitude);
if(0 <= MinLongitude && MinLongitude <= 180 && 0 <= MaxLongitude && MaxLongitude <= 180 && 0 <= MinLatitude && MinLatitude <= 90 && 0 <= MaxLatitude && MaxLatitude <= 930){
if(!(MinLongitude <= Lon && Lon <= MaxLongitude && MinLatitude <= Lat && Lat <= MaxLatitude)){
text.setText("孩子已超出设定范围");
startAbility(serviceintent);
}else{
text.setText("孩子没有超出设定范围");
stopAbility(serviceintent);
}
}else {
text.setText("没有设定范围或者输入的经度不在0到180之间、输入的纬度不在0到90之间");
stopAbility(serviceintent);
}
}else {
text.setText("无法获取孩子的位置");
stopAbility(serviceintent);
}
}
写在最后
更多资料请关注我们的项目 : Awesome-Harmony_木棉花
本项目会长期更新 ,希望随着鸿蒙一同成长变强的既有我们,也有正在看着这个项目的你。明年3月,深大校园内的木棉花会盛开,那时,鸿蒙也会变的更好,愿这花开,有你我的一份。
补一个楼主修改版的链接:https://harmonyos.51cto.com/posts/7914