獲取設(shè)備的地理位置
想了解更多關(guān)于開源的內(nèi)容,請?jiān)L問:
前言
設(shè)備的地理位置指的是設(shè)備所在的地理坐標(biāo)位置,通常以經(jīng)度,緯度和海拔高度的形式表示。地理位置信息能在許多業(yè)務(wù)場景中被應(yīng)用,如導(dǎo)航、地圖服務(wù)、位置服務(wù)、社交媒體等。通過獲取設(shè)備的地理位置,開發(fā)者可以為用戶提供個(gè)性化的服務(wù)和信息,同時(shí)有助于進(jìn)行地理位置相關(guān)的功能開發(fā)和數(shù)據(jù)分析。
本期筆者將以一個(gè)Demo為例,幫助有需要的HarmonyOS開發(fā)者實(shí)現(xiàn)獲取設(shè)備地理位置的功能。
正文
創(chuàng)建工程
打開DevEco Studio(開發(fā)工具的版本必須支持API9),創(chuàng)建一個(gè)新的project,相關(guān)勾選如下
UI設(shè)計(jì)
導(dǎo)入圖片資源
在工程文件目錄中打開目錄:src/main/resources/rawfile, 添加兩張任意的圖片(可以在IDE中將待添加的圖片資源直接粘貼至rawfile目錄下,也可以在文件資源管理器中通過文件路徑打開rawfile目錄并添加圖片資源),分別命名為image1和image2。當(dāng)然,圖片的格式?jīng)]有要求,只要在之后的步驟中能被正確引用即可。
添加UI描述
打開Index.ets,刪除build()中原有的所有代碼塊,增加新的UI聲明和自定義彈窗,并定義一些成員變量,相關(guān)代碼如下
@Entry
@Component
struct Index {
title:string = '地理信息面板'
@State Geo_Info:string = ''
//用于存儲(chǔ)用戶是否授權(quán)的狀態(tài)信息
@State ifAccessible:boolean = false
//new兩個(gè)彈窗控制器
private dialogController_Accessible : CustomDialogController = new CustomDialogController({
builder:dialog({
message:'已獲取權(quán)限'
})
})
private dialogController_Inaccessible : CustomDialogController = new CustomDialogController({
builder:dialog({
message:'獲取權(quán)限失敗 | 缺少相關(guān)權(quán)限'
})
})
build() {
Column({space:10}) {
//新的Text組件
Text(this.title)
.fontSize(26)
.fontWeight(800)
.margin({
top:20,
bottom:20
})
.fontColor('#e699cc')
Row(){
//條件渲染image組件
if(this.ifAccessible == true){
Image($rawfile('image2.png'))
.objectFit(ImageFit.Contain)
.layoutWeight(1)
}else{
Image($rawfile('image1.png'))
.objectFit(ImageFit.Contain)
.layoutWeight(1)
}
Column(){
//兩個(gè)新的Button組件
Button('獲取相關(guān)權(quán)限')
.width('90%')
.fontSize(18)
.backgroundColor(Color.Pink)
Button('獲取地理位置')
.width('90%')
.fontSize(18)
.backgroundColor(Color.Pink)
.margin({
top:14
})
}
.height('100%')
.layoutWeight(4)
.backgroundColor(Color.White)
}
.height('11%')
.width('92%')
//新的TextArea組件
TextArea({
text:this.Geo_Info
})
.width('94%')
.height('50%')
.fontSize(18)
.backgroundColor('#F0F0F0')
.margin({
top:20
})
}
.width('100%')
}
}
//自定義彈窗
@CustomDialog
struct dialog{
controller:CustomDialogController
@State message:string = ''
build(){
Column() {
Text(this.message)
.fontSize(20)
.height(40)
.fontColor(Color.White)
}
.width('100%')
.backgroundColor(Color.Gray)
}
}
完成Demo的UI設(shè)計(jì)后,可以打開預(yù)覽器查看界面效果:
集成功能模塊
向用戶動(dòng)態(tài)申請授權(quán)的基礎(chǔ)功能模塊
獲取設(shè)備地理位置信息的前提是用戶同意提供相關(guān)敏感權(quán)限,這意味著我們需要向用戶動(dòng)態(tài)申請相關(guān)所需權(quán)限。而此次關(guān)于向用戶動(dòng)態(tài)申請授權(quán)的模塊,筆者將把它們集成在Service目錄下的兩個(gè)TS文件里,分別是Applicant.ts與Detector.ts。
關(guān)于如何集成向用戶動(dòng)態(tài)申請授權(quán)的模塊,以及需要在module.json5中添加哪些權(quán)限,筆者在上期博客中已詳細(xì)闡述。
之后,我們在Index.ets中對其進(jìn)行調(diào)用。
//導(dǎo)入common
import common from '@ohos.app.ability.common'
//導(dǎo)入向用戶發(fā)起權(quán)限申請的模塊
import Request_Permission_From_Users from 'ets/Service/Applicant'
@Entry
@Component
struct Index {
......
//獲取上下文對象, 儲(chǔ)存在成員變量context中
private context = getContext(this) as common.UIAbilityContext
//編寫異步方法,調(diào)用之前已寫好的模塊文件Applicant
async apply(){
let res = await Request_Permission_From_Users(this.context)
this.ifAccessible = res
if(res){
this.dialogController_Accessible.open()
}else{
this.dialogController_Inaccessible.open()
}
}
build() {
Column({space:10}) {
......
Column(){
Button('獲取相關(guān)權(quán)限')
.width('90%')
.fontSize(18)
.backgroundColor(Color.Pink)
//調(diào)用異步方法apply
.onClick(()=>{
this.apply()
})
......
}
.height('100%')
.layoutWeight(4)
.backgroundColor(Color.White)
}
.height('11%')
.width('92%')
......
}
}
......
獲取設(shè)備地理信息的功能模塊
此功能模塊的目的是輸出設(shè)備所在地的經(jīng)度,緯度,海拔高度和城市名,以及設(shè)備的速度(應(yīng)該是瞬時(shí)的)。這需要先獲取設(shè)備所在的地理位置坐標(biāo),再將地理位置坐標(biāo)轉(zhuǎn)化為具體的地理描述(即國家,城市等)。
在Service目錄下新建一個(gè)TypeScript文件(右鍵Service目錄,選擇新建,再選擇TypeScript),將其命名為Geo
在編輯器中打開目錄Geo.ts,加入以下代碼以集成獲取設(shè)備地理信息的功能,各代碼塊的具體功能已寫注解。
//導(dǎo)入位置服務(wù)模塊
import geoLocationManager from '@ohos.geoLocationManager';
//導(dǎo)入自定義的權(quán)限檢查模塊
import Check_Access from 'ets/Service/Detector'
//定義結(jié)點(diǎn)的標(biāo)簽
const TAG_NODE0 = '------[Geo-Node0] '
const TAG_NODE1 = '------[Geo-Node1] '
const TAG_NODE2 = '------[Geo-Node2] '
/*
*結(jié)點(diǎn)函數(shù)1:獲取用戶設(shè)備當(dāng)前所處位置的經(jīng)度和緯度數(shù)據(jù)
*/
async function Node1_Get_Geographical_Position(){
//預(yù)定義返回值
let output = {
'position_x':null,
'position_y':null,
'position_z':null,
'cityName':' 未知',
'speed':null,
//結(jié)點(diǎn)函數(shù)的執(zhí)行狀態(tài),默認(rèn)為失敗
'isFinished':false,
'error':'無'
}
//檢查定位功能是否可用
if(!geoLocationManager.isLocationEnabled()){
console.info(TAG_NODE1+'Location module loads fail')
output.error = '定位功能不可用, 請檢查設(shè)備或服務(wù)器'
return
}
//定義需要輸入的請求參數(shù)
let requestInfo = {'priority': 0x203, 'scenario': 0x300,'maxAccuracy': 0}
//等待模塊完成獲取地理位置的異步操作
await geoLocationManager.getCurrentLocation(requestInfo).then((result) => {
console.info(TAG_NODE1+'Succeed! Current location => latitude: ' + result.latitude+'; longitude: '+result.longitude+';');
//記錄獲取的地理信息
output.position_x = result.latitude
output.position_y = result.longitude
output.position_z = result.altitude
output.speed = result.speed
//結(jié)點(diǎn)函數(shù)的執(zhí)行狀態(tài)修改為成功
output.isFinished = true
}).catch((error) => {
console.error(TAG_NODE1+'Get current location failed, error: ' + JSON.stringify(error));
output.error = '地理位置獲取失敗'
});
return output
}
/*
*結(jié)點(diǎn)函數(shù)2:獲取用戶設(shè)備當(dāng)前所處的城市名稱(中文)
*/
async function Node2_Get_City_Name(input){
//預(yù)定義返回值
let output = {
'cityName': ' 未知',
'position_x':input.position_x,
'position_y':input.position_y,
'position_z':input.position_z,
'speed':input.speed,
//結(jié)點(diǎn)函數(shù)的執(zhí)行狀態(tài),默認(rèn)為失敗
'isFinished': false,
'error':'無'
}
//判斷逆地理編碼轉(zhuǎn)換服務(wù)是否可用
if(!geoLocationManager.isGeocoderAvailable()){
console.error(TAG_NODE2+'Geocoder module loads fail')
output.error = '地理編碼轉(zhuǎn)化功能不可用, 請檢查設(shè)備或服務(wù)器'
return output
}
//定義需要輸入的請求參數(shù),其中l(wèi)ocale鍵對應(yīng)的值’zh‘表示服務(wù)器將返回中文形式的信息
let reverseGeocodeRequest = {'locale':'zh',"latitude": input.position_x, "longitude": input.position_y, "maxItems": 1};
//等待模塊完成逆地理編碼轉(zhuǎn)換的異步操作
await geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest).then((result) => {
console.info(TAG_NODE2+'City name : ' + result[0].locality);
//記錄獲取的城市名
let cityName = result[0].locality
if(cityName.charAt(cityName.length-1) == '市') cityName.replace('市','')
output.cityName = cityName
//結(jié)點(diǎn)函數(shù)的執(zhí)行狀態(tài)修改為成功
output.isFinished = true
}).catch((error) => {
console.error(TAG_NODE2+'Get addresses from location: error: ' + JSON.stringify(error));
output.error = '逆地理編碼轉(zhuǎn)換失敗'
});
return output
}
//導(dǎo)出可供調(diào)用的接口
export async function Get_Geo_Data(){
//模塊結(jié)點(diǎn)0
if(!Check_Access()){
console.error(TAG_NODE0+'Insufficient required permissions')
return {
'position_x':null,
'position_y':null,
'position_z':null,
'cityName':' 未知',
'speed':null,
'isFinished':false,
'error':'設(shè)備未獲取相關(guān)權(quán)限'
}
}
//模塊結(jié)點(diǎn)1
let output = await Node1_Get_Geographical_Position()
if (!output.isFinished) return output
//模塊結(jié)點(diǎn)2
return await Node2_Get_City_Name(output)
}
通常,集成這類模塊需要優(yōu)先考慮的問題是回調(diào)地獄。回調(diào)地獄是指,在使用回調(diào)函數(shù)處理異步操作時(shí),由于多個(gè)異步操作的嵌套和依賴關(guān)系,導(dǎo)致代碼結(jié)構(gòu)變得混亂和難以維護(hù)的情況。在Java中,我們可以通過創(chuàng)建線程和設(shè)置各線程優(yōu)先級(jí)的方式,將原本的異步過程調(diào)整為線性的同步過程。而在TypeScript中,除了傳統(tǒng)的設(shè)置線程的方法之外,我們還可以通過Promise或async/await來避免回調(diào)地獄,使代碼更加清晰和易于理解。
在上述代碼中,筆者聲明了兩個(gè)異步函數(shù),分別是Node1_Get_Geographical_Position與Node2_Get_City_Name,不妨稱它們?yōu)榻Y(jié)點(diǎn)函數(shù)。其中,第一個(gè)結(jié)點(diǎn)函數(shù)用于獲取地理位置信息(包含坐標(biāo)信息),第二個(gè)結(jié)點(diǎn)函數(shù)用于將所獲取的地理坐標(biāo)信息轉(zhuǎn)換為地理描述,并且,它們在時(shí)間維度上有一個(gè)執(zhí)行次序,即先執(zhí)行第一個(gè)結(jié)點(diǎn)函數(shù),當(dāng)其執(zhí)行完成并返回相關(guān)結(jié)果后,再執(zhí)行第二個(gè)結(jié)點(diǎn)函數(shù)。顯然,第二個(gè)結(jié)點(diǎn)函數(shù)的輸入即為第一個(gè)結(jié)點(diǎn)函數(shù)的輸出,而這也是先執(zhí)行第一個(gè)結(jié)點(diǎn)函數(shù)的原因。
要讓兩個(gè)異步的結(jié)點(diǎn)函數(shù)按次序線性執(zhí)行,我們可以定義一個(gè)新的異步函數(shù)Get_Geo_Data,在其函數(shù)體內(nèi)添加合適的操作語句以調(diào)用這兩個(gè)結(jié)點(diǎn)函數(shù)。在上述代碼中,Get_Geo_Data首先需判定相關(guān)的位置權(quán)限是否已被提供,接著調(diào)用第一個(gè)結(jié)點(diǎn)函數(shù)Node1_Get_Geographical_Position,并在調(diào)用時(shí)增加了關(guān)鍵字await。這意味著,在
Node1_Get_Geographical_Position返回結(jié)果之前,Get_Geo_Data函數(shù)體中剩下的未執(zhí)行的操作語句是不會(huì)被執(zhí)行的。當(dāng)?shù)谝粋€(gè)結(jié)點(diǎn)函數(shù)執(zhí)行結(jié)束后,Get_Geo_Data再調(diào)用第二個(gè)結(jié)點(diǎn)函數(shù)Node2_Get_City_Name,同樣地,要添加關(guān)鍵字await,否則在第二個(gè)結(jié)點(diǎn)函數(shù)成功響應(yīng)前,Get_Geo_Data就已經(jīng)跑完了,這樣什么都不會(huì)被輸出。
接下來,筆者將討論兩個(gè)結(jié)點(diǎn)函數(shù)各自的代碼邏輯。
對于第一個(gè)結(jié)點(diǎn)函數(shù)Node1_Get_Geographical_Position,首先它預(yù)定義了需要輸出的變量output(對應(yīng)一個(gè)Object型數(shù)據(jù)),其包含7個(gè)不同的鍵值對。接著,檢查定位功能是否可用,若可用,則通過導(dǎo)入的系統(tǒng)模塊geoLocationManager的getCurrentLocation方法異步獲取設(shè)備的地理位置信息,并在then()中提取地理位置信息中所攜帶的經(jīng)度,緯度,海拔高度,和實(shí)時(shí)速度。最后,輸出變量output。
至于第二個(gè)結(jié)點(diǎn)函數(shù)Node2_Get_City_Name,首先它預(yù)定義了需要輸出的變量output,并且將傳入的參數(shù)input(即Node1_Get_Geographical_Position的輸出)的一些鍵對應(yīng)的值拷貝到output中。之后, 判斷逆地理編碼轉(zhuǎn)換功能是否可用,若可用,則通過geoLocationManager的getAddressesFromLocation方法異步獲取設(shè)備所在位置的地理描述信息(即國家,城市等),并在then()中提取地理描述信息中的城市名。最后,輸出變量output。
完成功能集成工作后,我們在Index.ets中調(diào)用此模塊。
......
//導(dǎo)入獲取設(shè)備地理信息的模塊
import { Get_Geo_Data } from 'ets/Service/Geo'
@Entry
@Component
struct Index {
......
//編寫異步方法,調(diào)用之前已寫好的模塊文件Geo
async update_geo_data(){
//判斷是否獲取所需權(quán)限
if(!this.ifAccessible){
this.dialogController_Inaccessible.open()
return
}
let info = await Get_Geo_Data()
this.Geo_Info = ' ---地理信息---\n' + '\n當(dāng)前所在城市:' + info.cityName + '\n緯度: ' + info.position_x + '\n經(jīng)度: ' + info.position_y + '\n海拔: ' + info.position_z + '\n速度: ' + info.speed + ' m/s\n'
if(!info.isFinished) this.Geo_Info += '\n錯(cuò)誤信息: ' + info.error
}
build() {
Column({space:10}) {
.....
Column(){
......
Button('獲取地理位置')
.width('90%')
.fontSize(18)
.backgroundColor(Color.Pink)
.margin({
top:14
})
//調(diào)用異步方法update_geo_data
.onClick(()=>{
this.update_geo_data()
})
}
.height('100%')
.layoutWeight(4)
.backgroundColor(Color.White)
}
.height('11%')
.width('92%')
......
}
}
......
真機(jī)&模擬機(jī)調(diào)試
Demo完成之后,我們需要用模擬機(jī)或真機(jī)來運(yùn)行以查看效果。
可惜的是,模擬機(jī)里的逆地理編碼轉(zhuǎn)換服務(wù)是不可用的,所以其無法得到設(shè)備所在地的地理描述,因而無法輸出城市名。
相關(guān)日志如下,可見,逆地理編碼轉(zhuǎn)換服務(wù)被檢查為不可用。
如果在真機(jī)上運(yùn)行,逆地理編碼轉(zhuǎn)換服務(wù)是沒什么問題的。下圖是筆者借用了roommate的真機(jī)后得到的程序運(yùn)行截圖
文章相關(guān)附件可以點(diǎn)擊下面的原文鏈接前往下載:
https://ost.51cto.com/resource/3053