設備的地理位置指的是設備所在的地理坐標位置,通常以經度,緯度和海拔高度的形式表示。地理位置信息能在許多業務場景中被應用,如導航、地圖服務、位置服務、社交媒體等。通過獲取設備的地理位置,開發者可以為用戶提供個性化的服務和信息,同時有助于進行地理位置相關的功能開發和數據分析。
本期筆者將以一個Demo為例,幫助有需要的開發者實現獲取設備地理位置的功能。
創建工程
打開DevEco Studio(開發工具的版本必須支持API9),創建一個新的project,相關勾選如下
UI設計
▍導入圖片資源
在工程文件目錄中打開目錄:src/main//rawfile, 添加兩張任意的圖片(可以在IDE中將待添加的圖片資源直接粘貼至rawfile目錄下,也可以在文件資源管理器中通過文件路徑打開rawfile目錄并添加圖片資源),分別命名為image1和image2。當然,圖片的格式沒有要求,只要在之后的步驟中能被正確引用即可。
▍添加UI描述
打開Index.ets,刪除build()中原有的所有代碼塊,增加新的UI聲明和自定義彈窗,并定義一些成員變量,相關代碼如下
@Entry
@Component
struct?Index?{
??title:string?=?'地理信息面板'
??@State?Geo_Info:string?=?''
?//用于存儲用戶是否授權的狀態信息
??@State?ifAccessible:boolean?=?false
??
???//new兩個彈窗控制器
??private?dialogController_Accessible?:?CustomDialogController?=?new?CustomDialogController({
????builder:dialog({
??????message:'已獲取權限'
????})
??})
??private?dialogController_Inaccessible?:?CustomDialogController?=?new?CustomDialogController({
????builder:dialog({
??????message:'獲取權限失敗?|?缺少相關權限'
????})
??})
??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(){
????????
?????????//兩個新的Button組件
??????????Button('獲取相關權限')
????????????.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設計后,可以打開預覽器查看界面效果
集成功能模塊
▍向用戶動態申請授權的基礎功能模塊
獲取設備地理位置信息的前提是用戶同意提供相關敏感權限,這意味著我們需要向用戶動態申請相關所需權限。而此次關于向用戶動態申請授權的模塊,筆者將把它們集成在Service目錄下的兩個TS文件里,分別是.ts與.ts。
關于如何集成向用戶動態申請授權的模塊,以及需要在module.json5中添加哪些權限,筆者在上期博客中已詳細闡述。
上期博客的鏈接:
之后,我們在Index.ets中對其進行調用
//導入common
import?common?from?'@ohos.app.ability.common'
//導入向用戶發起權限申請的模塊
import?Request_Permission_From_Users?from?'ets/Service/Applicant'
@Entry
@Component
struct?Index?{
?......
??
??//獲取上下文對象,?儲存在成員變量context中
??private?context?=?getContext(this)?as?common.UIAbilityContext
??//編寫異步方法,調用之前已寫好的模塊文件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('獲取相關權限')
????????????.width('90%')
????????????.fontSize(18)
????????????.backgroundColor(Color.Pink)
????????????//調用異步方法apply
????????????.onClick(()=>{
??????????????this.apply()
????????????})
?????????......
????????}
????????.height('100%')
????????.layoutWeight(4)
????????.backgroundColor(Color.White)
??????}
??????.height('11%')
??????.width('92%')
?......
??}
}
......
▍獲取設備地理信息的功能模塊
此功能模塊的目的是輸出設備所在地的經度,緯度,海拔高度和城市名,以及設備的速度(應該是瞬時的)。這需要先獲取設備所在的地理位置坐標,再將地理位置坐標轉化為具體的地理描述(即國家,城市等)。
在Service目錄下新建一個文件(右鍵Service目錄,選擇新建,再選擇),將其命名為Geo
在編輯器中打開目錄Geo.ts,加入以下代碼以集成獲取設備地理信息的功能,各代碼塊的具體功能已寫注解
//導入位置服務模塊
import?geoLocationManager?from?'@ohos.geoLocationManager';
//導入自定義的權限檢查模塊
import?Check_Access?from?'ets/Service/Detector'
//定義結點的標簽
const?TAG_NODE0?=?'------[Geo-Node0]?'
const?TAG_NODE1?=?'------[Geo-Node1]?'
const?TAG_NODE2?=?'------[Geo-Node2]?'
/*
?*結點函數1:獲取用戶設備當前所處位置的經度和緯度數據
?*/
async?function?Node1_Get_Geographical_Position(){
??//預定義返回值
??let?output?=?{
????'position_x':null,
????'position_y':null,
????'position_z':null,
????'cityName':'?未知',
????'speed':null,
????//結點函數的執行狀態,默認為失敗
????'isFinished':false,
????'error':'無'
??}
??//檢查定位功能是否可用
??if(!geoLocationManager.isLocationEnabled()){
????console.info(TAG_NODE1+'Location?module?loads?fail')
????output.error?=?'定位功能不可用,?請檢查設備或服務器'
????return
??}
??//定義需要輸入的請求參數
??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
????//結點函數的執行狀態修改為成功
????output.isFinished?=?true
??}).catch((error)?=>?{
????console.error(TAG_NODE1+'Get?current?location?failed,?error:?'?+?JSON.stringify(error));
????output.error?=?'地理位置獲取失敗'
??});
??return?output
}
/*
?*結點函數2:獲取用戶設備當前所處的城市名稱(中文)
?*/
async?function?Node2_Get_City_Name(input){
??//預定義返回值
??let?output?=?{
????'cityName':?'?未知',
????'position_x':input.position_x,
????'position_y':input.position_y,
????'position_z':input.position_z,
????'speed':input.speed,
????//結點函數的執行狀態,默認為失敗
????'isFinished':?false,
????'error':'無'
??}
??//判斷逆地理編碼轉換服務是否可用
??if(!geoLocationManager.isGeocoderAvailable()){
????console.error(TAG_NODE2+'Geocoder?module?loads?fail')
????output.error?=?'地理編碼轉化功能不可用,?請檢查設備或服務器'
????return?output
??}
??//定義需要輸入的請求參數,其中locale鍵對應的值’zh‘表示服務器將返回中文形式的信息
??let?reverseGeocodeRequest?=?{'locale':'zh',"latitude":?input.position_x,?"longitude":?input.position_y,?"maxItems":?1};
??//等待模塊完成逆地理編碼轉換的異步操作
??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
????//結點函數的執行狀態修改為成功
????output.isFinished?=?true
??}).catch((error)?=>?{
????console.error(TAG_NODE2+'Get?addresses?from?location:?error:?'?+?JSON.stringify(error));
????output.error?=?'逆地理編碼轉換失敗'
??});
??return?output
}
//導出可供調用的接口
export?async?function?Get_Geo_Data(){
??//模塊結點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':'設備未獲取相關權限'
????}
??}
??//模塊結點1
??let?output?=?await?Node1_Get_Geographical_Position()
??if?(!output.isFinished)?return?output
??//模塊結點2
??return?await?Node2_Get_City_Name(output)
}
通常,集成這類模塊需要優先考慮的問題是回調地獄。回調地獄是指,在使用回調函數處理異步操作時,由于多個異步操作的嵌套和依賴關系,導致代碼結構變得混亂和難以維護的情況。在Java中,我們可以通過創建線程和設置各線程優先級的方式,將原本的異步過程調整為線性的同步過程。而在中,除了傳統的設置線程的方法之外,我們還可以通過Promise或async/await來避免回調地獄,使代碼更加清晰和易于理解。
在上述代碼中,筆者聲明了兩個異步函數,分別是與,不妨稱它們為結點函數。其中,第一個結點函數用于獲取地理位置信息(包含坐標信息),第二個結點函數用于將所獲取的地理坐標信息轉換為地理描述,并且,它們在時間維度上有一個執行次序,即先執行第一個結點函數,當其執行完成并返回相關結果后,再執行第二個結點函數。顯然,第二個結點函數的輸入即為第一個結點函數的輸出,而這也是先執行第一個結點函數的原因。
要讓兩個異步的結點函數按次序線性執行,我們可以定義一個新的異步函數,在其函數體內添加合適的操作語句以調用這兩個結點函數。在上述代碼中,首先需判定相關的位置權限是否已被提供,接著調用第一個結點函數,并在調用時增加了關鍵字await。這意味著,在返回結果之前,函數體中剩下的未執行的操作語句是不會被執行的。當第一個結點函數執行結束后,再調用第二個結點函數,同樣地,要添加關鍵字await,否則在第二個結點函數成功響應前,就已經跑完了,這樣什么都不會被輸出。
接下來,筆者將討論兩個結點函數各自的代碼邏輯。
對于第一個結點函數,首先它預定義了需要輸出的變量output(對應一個Object型數據),其包含7個不同的鍵值對。接著,檢查定位功能是否可用,若可用,則通過導入的系統模塊的方法異步獲取設備的地理位置信息,并在then()中提取地理位置信息中所攜帶的經度,緯度,海拔高度,和實時速度。最后,輸出變量output。
至于第二個結點函數,首先它預定義了需要輸出的變量output,并且將傳入的參數input(即的輸出)的一些鍵對應的值拷貝到output中。之后, 判斷逆地理編碼轉換功能是否可用,若可用,則通過的tion方法異步獲取設備所在位置的地理描述信息(即國家,城市等),并在then()中提取地理描述信息中的城市名。最后,輸出變量output。
完成功能集成工作后,我們在Index.ets中調用此模塊
......
//導入獲取設備地理信息的模塊
import?{?Get_Geo_Data?}?from?'ets/Service/Geo'
@Entry
@Component
struct?Index?{
?......
??//編寫異步方法,調用之前已寫好的模塊文件Geo
??async?update_geo_data(){
????//判斷是否獲取所需權限
????if(!this.ifAccessible){
??????this.dialogController_Inaccessible.open()
??????return
????}
????let?info?=?await?Get_Geo_Data()
????this.Geo_Info?=?'???????---地理信息---\n'?+?'\n當前所在城市:'?+?info.cityName?+?'\n緯度:?'?+?info.position_x?+?'\n經度:?'?+?info.position_y?+?'\n海拔:?'?+?info.position_z?+?'\n速度:?'?+?info.speed?+?'?m/s\n'
????if(!info.isFinished)?this.Geo_Info?+=?'\n錯誤信息:?'?+?info.error
??}
??build()?{
????Column({space:10})?{
????.....
????
????????Column(){
????????
?????????......
??????????Button('獲取地理位置')
????????????.width('90%')
????????????.fontSize(18)
????????????.backgroundColor(Color.Pink)
????????????.margin({
??????????????top:14
????????????})
????????????//調用異步方法update_geo_data
????????????.onClick(()=>{
??????????????this.update_geo_data()
????????????})
????????}
????????.height('100%')
????????.layoutWeight(4)
????????.backgroundColor(Color.White)
??????}
??????.height('11%')
??????.width('92%')
???......
??}
}
......
真機&模擬機調試
Demo完成之后,我們需要用模擬機或真機來運行以查看效果。
可惜的是,模擬機里的逆地理編碼轉換服務是不可用的,所以其無法得到設備所在地的地理描述,因而無法輸出城市名。
相關日志如下,可見,逆地理編碼轉換服務被檢查為不可用。
如果在真機上運行,逆地理編碼轉換服務是沒什么問題的。下圖是筆者借用了的真機后得到的程序運行截圖
結語
文章所述僅代表個人理解,如有不足懇請讀者不吝賜教。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。