目錄
文章
分類
標籤
AI ai android bettercap Bitbucket C# C++ CDN Certbot ChatGPT Cloudflare Code Runner CPE CPU排程 cron Daphne Django DNS設定 Docker ftp fused-location Git Gmail API GPT hashcat HTML HTTPS hydra kotlin Line Bot Linux location-app Mac macOS MySQL n8n Next.js Nginx nmap NVIDIA OpenAI openssl permissions Python Python Django Python Email Pytorch pytorch PyTorch R2 rclone React Redis Replit SSL streamlit Swap Traefik Ubuntu Unity Vector Vera CPU VM設定 VPS VSCode WebSocket Wi-Fi WordPress Wordpress wordpress-備份 yolo YOLOv9 中位數 代理型 AI 企業市值 伺服器設定 修復 儲存空間 最佳住址 利率 前端 動態生成 即時通訊 卷積神經網路 台股 場景切換 學習 安全性 實作 專案管理 帳號建立 影像分類 後台 性能 惡意軟體 成績查詢 成績管理 技巧 投資 排序 操作系統 效率 教學 本地測試 殖利率 深度學習 清理 滲透測試 演算法 版本控制 物件偵測 神經網路 科技產業 程式解題 程式設計 競程 系統管理 終端機 統計 經濟成長 網域綁定 網站恢復 網頁框架 股票 自動化 自駕車 財務 資料共享 資料庫 資料結構 資訊安全 距離計算 路由 遊戲開發 選單 部署 郵件發送 開發 開發技巧 開發環境 開發者 雲端備份 靜態文件
目錄
2203 字
11 分鐘
Android 定位應用程式
這是一個練習定位權限的App,我將重要的部分記錄下來。
1<uses-permission android:name="android.permission.INTERNET" />2<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />3<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />4```kotlin5
6```xml7<uses-permission android:name="android.permission.INTERNET" />8<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />9<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />10```kotlin11
12- android.permission.INTERNET : 允許應用程式開啟**網路連線**13
14- android.permission.ACCESS\_COARSE\_LOCATION : 允許應用程式存取使用**粗略的位置**15
16- android.permission.ACCESS\_FINE\_LOCATION : 允許應用程式存取使用**精確的位置**17
18## LocationUtils.kt19
20hasLocationPermission function 回傳一個 `Boolean`→ `true` 或 `false`,代表有沒有定位權限。21
22```kotlin23class LocationUtils(val context: Context) {24 private val _fusedLocationClient: FusedLocationProviderClient =25 LocationServices.getFusedLocationProviderClient(context)26
27 @SuppressLint("MissingPermission")28 fun requestLocationUpdates(viewModel: LocationViewModel) {29 val locationCallback = object : LocationCallback() {30 override fun onLocationResult(locationResult: LocationResult) {31 super.onLocationResult(locationResult)32 locationResult.lastLocation?.let {33 val location = LocationData(latitude = it.latitude, longitude = it.longitude)34 viewModel.updateLocation(location)35 }36 }37 }38 val locationRequest = LocationRequest.Builder(39 Priority.PRIORITY_HIGH_ACCURACY, 100040 ).build()41 _fusedLocationClient.requestLocationUpdates(42 locationRequest,43 locationCallback,44 Looper.getMainLooper()45 )46 }47
48 fun hasLocationPermission(context: Context): Boolean {49 return ContextCompat.checkSelfPermission(50 context,51 Manifest.permission.ACCESS_FINE_LOCATION52 ) == PackageManager.PERMISSION_GRANTED53 &&54 ContextCompat.checkSelfPermission(55 context,56 Manifest.permission.ACCESS_COARSE_LOCATION57 ) == PackageManager.PERMISSION_GRANTED58 }59
60 fun reverseGeocodeLocation(location: LocationData): String {61 // The Geocoder constructor expects a java.util.Locale62 val geocoder = Geocoder(context, Locale.getDefault())63 val coordinate = LatLng(location.latitude, location.longitude)64 val addresses: MutableList<Address>? =65 geocoder.getFromLocation(coordinate.latitude, coordinate.longitude, 1)66
67 return if (addresses?.isNotEmpty() == true) {68 addresses[0]?.getAddressLine(0) ?: "Address line not found"69 } else {70 "Address not found"71 }72 }73}74```kotlin75
76```kotlin77class LocationUtils(val context: Context) {78 private val _fusedLocationClient: FusedLocationProviderClient =79 LocationServices.getFusedLocationProviderClient(context)80
81 @SuppressLint("MissingPermission")82 fun requestLocationUpdates(viewModel: LocationViewModel) {83 val locationCallback = object : LocationCallback() {84 override fun onLocationResult(locationResult: LocationResult) {85 super.onLocationResult(locationResult)86 locationResult.lastLocation?.let {87 val location = LocationData(latitude = it.latitude, longitude = it.longitude)88 viewModel.updateLocation(location)89 }90 }91 }92 val locationRequest = LocationRequest.Builder(93 Priority.PRIORITY_HIGH_ACCURACY, 100094 ).build()95 _fusedLocationClient.requestLocationUpdates(96 locationRequest,97 locationCallback,98 Looper.getMainLooper()99 )100 }101
102 fun hasLocationPermission(context: Context): Boolean {103 return ContextCompat.checkSelfPermission(104 context,105 Manifest.permission.ACCESS_FINE_LOCATION106 ) == PackageManager.PERMISSION_GRANTED107 &&108 ContextCompat.checkSelfPermission(109 context,110 Manifest.permission.ACCESS_COARSE_LOCATION111 ) == PackageManager.PERMISSION_GRANTED112 }113
114 fun reverseGeocodeLocation(location: LocationData): String {115 // The Geocoder constructor expects a java.util.Locale116 val geocoder = Geocoder(context, Locale.getDefault())117 val coordinate = LatLng(location.latitude, location.longitude)118 val addresses: MutableList<Address>? =119 geocoder.getFromLocation(coordinate.latitude, coordinate.longitude, 1)120
121 return if (addresses?.isNotEmpty() == true) {122 addresses[0]?.getAddressLine(0) ?: "Address line not found"123 } else {124 "Address not found"125 }126 }127}128```kotlin129
130```kotlin131private val _fusedLocationClient: FusedLocationProviderClient =132 LocationServices.getFusedLocationProviderClient(context)133```kotlin134
135```kotlin136private val _fusedLocationClient: FusedLocationProviderClient =137 LocationServices.getFusedLocationProviderClient(context)138```kotlin139
140這是 Google 提供的 **Fused Location Provider API**,能整合 GPS、Wi-Fi、藍牙與行動網路的定位來源,讓定位更準確且省電。`_fusedLocationClient` 用來執行「請求位置更新」、「停止更新」、「取得最後位置」等操作。141
142### requestLocationUpdates143
144```kotlin145@SuppressLint("MissingPermission")146fun requestLocationUpdates(viewModel: LocationViewModel) {147```kotlin148
149```kotlin150@SuppressLint("MissingPermission")151fun requestLocationUpdates(viewModel: LocationViewModel) {152```kotlin153
154⚠️ 注意:這裡用了 `@SuppressLint("MissingPermission")`,代表你**必須在呼叫這個函式前,確認已取得權限**(也就是前面 `LocationDisplay` 檢查的部分)。155
156意思是保證外層一定會先檢查 hasLocationPermission() 才會進來,所以不用再提醒我 MissingPermission。157
158這個函式會**每秒更新一次位置**(因為下面設定了 1000 毫秒),然後將新位置傳給你的 `LocationViewModel`。159
160```kotlin161val locationCallback = object : LocationCallback() {162 override fun onLocationResult(locationResult: LocationResult) {163 super.onLocationResult(locationResult)164 locationResult.lastLocation?.let {165 val location = LocationData(latitude = it.latitude, longitude = it.longitude)166 viewModel.updateLocation(location)167 }168 }169}170```kotlin171
172```kotlin173val locationCallback = object : LocationCallback() {174 override fun onLocationResult(locationResult: LocationResult) {175 super.onLocationResult(locationResult)176 locationResult.lastLocation?.let {177 val location = LocationData(latitude = it.latitude, longitude = it.longitude)178 viewModel.updateLocation(location)179 }180 }181}182```kotlin183
184當系統取得新的定位資訊時,會呼叫 `onLocationResult`。185
186`lastLocation` 是最新的 `Location` 物件(包含緯度與經度)。187
188```kotlin189package com.example.locationapp190
191data class LocationData(192 val latitude: Double,193 val longitude: Double194)195```kotlin196
197```kotlin198package com.example.locationapp199
200data class LocationData(201 val latitude: Double,202 val longitude: Double203)204```kotlin205
206`LocationData` 是我們定義的資料類別,用來包裝座標。207
208呼叫 `viewModel.updateLocation(location)` → 把資料傳給 ViewModel 更新 UI。209
210```kotlin211_fusedLocationClient.requestLocationUpdates(212 locationRequest,213 locationCallback,214 Looper.getMainLooper()215)216```kotlin217
218```kotlin219_fusedLocationClient.requestLocationUpdates(220 locationRequest,221 locationCallback,222 Looper.getMainLooper()223)224```kotlin225
226這行讓 app 持續監聽位置變化。227
228### hasLocationPermission229
230`LocationUtils` 要持有 `Context`,才能呼叫系統 API(如 `ContextCompat.checkSelfPermission`)來檢查權限。231
232return 這段是重點:`ContextCompat.checkSelfPermission()` 是Android官方提供的工具,用來檢查 App 是否擁有某個權限。它會回傳一個整數值(`PERMISSION_GRANTED` 或 `PERMISSION_DENIED`)。233
234這段程式碼的功能就像是你在檢查一個房間的門:235
236> 「門有沒有上鎖?如果鎖是開著的,就代表我可以進去。」237>238> **`ContextCompat` (檢查員):** 你可以把它想像成 Android 系統中的一位**通用檢查員**或**萬用工具箱**。它的工作就是確保你的程式碼在所有不同型號和版本的 Android 手機上都能正常運作(這就是 `Compat` 兼容性的意思)。239>240> **`.checkSelfPermission()` (檢查方法):** 這是檢查員專門用來**檢查特定權限狀態**的方法。241>242> - **參數一 (`context`):** 告訴檢查員「在哪個應用程式環境」下進行檢查。243>244> - **參數二 (`Manifest.permission.ACCESS_FINE_LOCATION`):** 告訴檢查員「要檢查哪一種權限」。245
246### 為什麼需要 `ContextCompat.checkSelfPermission()`?247
248我們不直接使用 `PackageManager.PERMISSION_GRANTED` 進行判斷,是因為 **`PackageManager.PERMISSION_GRANTED` 只是一個「結果程式碼」(密碼)**,而 **`ContextCompat.checkSelfPermission()` 才是「執行檢查的動作」**。249
250### reverseGeocodeLocation251
252```kotlin253fun reverseGeocodeLocation(location: LocationData): String {254 val geocoder = Geocoder(context, Locale.getDefault())255 val coordinate = LatLng(location.latitude, location.longitude)256 val addresses: MutableList<Address>? =257 geocoder.getFromLocation(coordinate.latitude, coordinate.longitude, 1)258```kotlin259
260```kotlin261fun reverseGeocodeLocation(location: LocationData): String {262 val geocoder = Geocoder(context, Locale.getDefault())263 val coordinate = LatLng(location.latitude, location.longitude)264 val addresses: MutableList<Address>? =265 geocoder.getFromLocation(coordinate.latitude, coordinate.longitude, 1)266```kotlin267
268用 `Geocoder` 根據經緯度查詢地點名稱。`getFromLocation()` 會回傳一個地址列表(可能含國家、城市、街道)。269
270## MainActivity.kt271
272```kotlin273@Composable274fun MyApp(modifier: Modifier) {275 val context = LocalContext.current276 val locationUtils = LocationUtils(context)277 LocationDisplay(locationUtils = locationUtils, context = context)278}279```kotlin280
281```kotlin282@Composable283fun MyApp(modifier: Modifier) {284 val context = LocalContext.current285 val locationUtils = LocationUtils(context)286 LocationDisplay(locationUtils = locationUtils, context = context)287}288```kotlin289
290```kotlin291class MainActivity : ComponentActivity() {292 override fun onCreate(savedInstanceState: Bundle?) {293 super.onCreate(savedInstanceState)294 enableEdgeToEdge()295 setContent {296 val viewModel: LocationViewModel = viewModel()297 LocationAppTheme {298 Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->299 MyApp(viewModel = viewModel,modifier = Modifier.padding(innerPadding))300 }301 }302 }303 }304}305```kotlin306
307```kotlin308class MainActivity : ComponentActivity() {309 override fun onCreate(savedInstanceState: Bundle?) {310 super.onCreate(savedInstanceState)311 enableEdgeToEdge()312 setContent {313 val viewModel: LocationViewModel = viewModel()314 LocationAppTheme {315 Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->316 MyApp(viewModel = viewModel,modifier = Modifier.padding(innerPadding))317 }318 }319 }320 }321}322```kotlin323
324**`Context`:** 是一個核心的 Android 系統類別,它提供了應用程式全域資訊,例如資源、資料庫、文件系統路徑,以及存取系統服務(如定位服務)的管道。325
326**`LocalContext.current`:** 這是 Jetpack Compose 框架提供的一個 **CompositionLocal**。它用於在可組合函數中**存取** Android 應用程式的 **`Context`** 物件。327
328`LocationDisplay(locationUtils = locationUtils, context = context)` 這行程式碼的作用是**調用**一個名為 `LocationDisplay` 的 **Compose UI 元件**,向它傳遞所需的資料和環境依賴。329
330```kotlin331@Composable332fun LocationDisplay(333 locationUtils: LocationUtils,334 viewModel: LocationViewModel,335 context: Context336) {337 val location = viewModel.location.value338
339 val address = location?.let {340 locationUtils.reverseGeocodeLocation(location)341 }342
343 val requestPermissionLauncher = rememberLauncherForActivityResult(344 contract = ActivityResultContracts.RequestMultiplePermissions(),345 onResult = { permissions ->346 if (permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true347 && permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true348 ) {349 // 擁有 location 權限350 locationUtils.requestLocationUpdates(viewModel = viewModel)351
352 } else {353 // 要求權限354 val rationaleRequired = ActivityCompat.shouldShowRequestPermissionRationale(355 context as MainActivity,356 Manifest.permission.ACCESS_FINE_LOCATION357 ) || ActivityCompat.shouldShowRequestPermissionRationale(358 context as MainActivity,359 Manifest.permission.ACCESS_COARSE_LOCATION360 )361
362 if (rationaleRequired) {363 Toast.makeText(364 context,365 "Location Permission is required for this feature to work",366 Toast.LENGTH_LONG367 ).show()368 } else {369 Toast.makeText(370 context,371 "Location Permission is required, Please enable from settings",372 Toast.LENGTH_LONG373 ).show()374 }375 }376 }377 )378
379 Column(380 modifier = Modifier.fillMaxSize(),381 horizontalAlignment = Alignment.CenterHorizontally,382 verticalArrangement = Arrangement.Center383 ) {384 if(location != null) {385 Text("Address: ${location.latitude} ${location.longitude} \n $address ")386 }387 Text(text = "Location not available")388
389 Button(onClick = {390 if (locationUtils.hasLocationPermission(context)) {391 locationUtils.requestLocationUpdates(viewModel)392 } else {393 requestPermissionLauncher.launch(394 arrayOf(395 Manifest.permission.ACCESS_FINE_LOCATION,396 Manifest.permission.ACCESS_COARSE_LOCATION397 )398 )399 }400 }) {401 Text(text = "Get Location")402 }403 }404}405```kotlin406
407```kotlin408@Composable409fun LocationDisplay(410 locationUtils: LocationUtils,411 viewModel: LocationViewModel,412 context: Context413) {414 val location = viewModel.location.value415
416 val address = location?.let {417 locationUtils.reverseGeocodeLocation(location)418 }419
420 val requestPermissionLauncher = rememberLauncherForActivityResult(421 contract = ActivityResultContracts.RequestMultiplePermissions(),422 onResult = { permissions ->423 if (permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true424 && permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true425 ) {426 // 擁有 location 權限427 locationUtils.requestLocationUpdates(viewModel = viewModel)428
429 } else {430 // 要求權限431 val rationaleRequired = ActivityCompat.shouldShowRequestPermissionRationale(432 context as MainActivity,433 Manifest.permission.ACCESS_FINE_LOCATION434 ) || ActivityCompat.shouldShowRequestPermissionRationale(435 context as MainActivity,436 Manifest.permission.ACCESS_COARSE_LOCATION437 )438
439 if (rationaleRequired) {440 Toast.makeText(441 context,442 "Location Permission is required for this feature to work",443 Toast.LENGTH_LONG444 ).show()445 } else {446 Toast.makeText(447 context,448 "Location Permission is required, Please enable from settings",449 Toast.LENGTH_LONG450 ).show()451 }452 }453 }454 )455
456 Column(457 modifier = Modifier.fillMaxSize(),458 horizontalAlignment = Alignment.CenterHorizontally,459 verticalArrangement = Arrangement.Center460 ) {461 if(location != null) {462 Text("Address: ${location.latitude} ${location.longitude} \n $address ")463 }464 Text(text = "Location not available")465
466 Button(onClick = {467 if (locationUtils.hasLocationPermission(context)) {468 locationUtils.requestLocationUpdates(viewModel)469 } else {470 requestPermissionLauncher.launch(471 arrayOf(472 Manifest.permission.ACCESS_FINE_LOCATION,473 Manifest.permission.ACCESS_COARSE_LOCATION474 )475 )476 }477 }) {478 Text(text = "Get Location")479 }480 }481}482```kotlin483
484主要公能:顯示一個文字 + 按鈕,並處理按下按鈕後檢查或請求定位權限。485
486contract = ActivityResultContracts.RequestMultiplePermissions()487
488表示這個 launcher 可以一次請求多個權限(這裡是 coarse + fine)。489
490`onResult = { permissions -> ... }`491
492系統對話框關閉後,會回傳使用者是否授權。493
494```kotlin495if (permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true496 && permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true497) {498 // 已擁有定位權限499} else {500 // 尚未取得權限,顯示提示501}502```kotlin503
504```kotlin505if (permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true506 && permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true507) {508 // 已擁有定位權限509} else {510 // 尚未取得權限,顯示提示511}512```kotlin513
514若兩個權限都有允許,目前就什麼都不做;否則,進入「沒有權限」的分支。515
516如果沒權限 → 顯示提示 Toast517
518```kotlin519val rationaleRequired = ActivityCompat.shouldShowRequestPermissionRationale(520 context as MainActivity,521 Manifest.permission.ACCESS_FINE_LOCATION522) || ActivityCompat.shouldShowRequestPermissionRationale(523 context as MainActivity,524 Manifest.permission.ACCESS_COARSE_LOCATION525)526```kotlin527
528```kotlin529val rationaleRequired = ActivityCompat.shouldShowRequestPermissionRationale(530 context as MainActivity,531 Manifest.permission.ACCESS_FINE_LOCATION532) || ActivityCompat.shouldShowRequestPermissionRationale(533 context as MainActivity,534 Manifest.permission.ACCESS_COARSE_LOCATION535)536```kotlin537
538shouldShowRequestPermissionRationale(…)539
540這是 Android 的 API,用來判斷:541
542- 使用者曾拒絕過權限(但沒勾「不再詢問」 → 回傳 `true`543
544- 使用者勾了「不再詢問」或 第一次詢問 → 回傳 `false`545
546```kotlin547if (rationaleRequired) {548 Toast.makeText(context, "Location Permission is required for this feature to work", Toast.LENGTH_LONG).show()549} else {550 Toast.makeText(context, "Location Permission is required, Please enable from settings", Toast.LENGTH_LONG).show()551}552```kotlin553
554```kotlin555if (rationaleRequired) {556 Toast.makeText(context, "Location Permission is required for this feature to work", Toast.LENGTH_LONG).show()557} else {558 Toast.makeText(context, "Location Permission is required, Please enable from settings", Toast.LENGTH_LONG).show()559}560```kotlin561
562563
564565
566567
568## LocationViewModel.kt569
570```kotlin571package com.example.locationapp572
573import androidx.compose.runtime.State574import androidx.compose.runtime.mutableStateOf575import androidx.lifecycle.ViewModel576
577class LocationViewModel: ViewModel() {578 private val _location = mutableStateOf<LocationData?>(null)579 val location: State<LocationData?> = _location580
581 fun updateLocation(newLocation: LocationData) {582 _location.value = newLocation583 }584
585}586```kotlin587
588```kotlin589package com.example.locationapp590
591import androidx.compose.runtime.State592import androidx.compose.runtime.mutableStateOf593import androidx.lifecycle.ViewModel594
595class LocationViewModel: ViewModel() {596 private val _location = mutableStateOf<LocationData?>(null)597 val location: State<LocationData?> = _location598
599 fun updateLocation(newLocation: LocationData) {600 _location.value = newLocation601 }602
603}604```kotlin605
606```kotlin607private val _location = mutableStateOf<LocationData?>(null)608```kotlin609
610```kotlin611private val _location = mutableStateOf<LocationData?>(null)612```kotlin613
614mutableStateOf 它包裝了一個變數,當值改變時,Compose UI 會**自動重新組合(recompose)**。615
616`LocationData?` → 泛型裡面的 `?` 代表可以是 `null`,表示「可能還沒取得位置」。617
618`_location` 是私有的可變,`location` 是公開的唯讀。619
620```kotlin621fun updateLocation(newLocation: LocationData) {622 _location.value = newLocation623}624```kotlin625
626```kotlin627fun updateLocation(newLocation: LocationData) {628 _location.value = newLocation629}630```xml631
632當 `LocationUtils` 拿到新座標後,會呼叫updateLocation更新座標。部分資訊可能已經過時
目錄
文章
分類
標籤
AI ai android bettercap Bitbucket C# C++ CDN Certbot ChatGPT Cloudflare Code Runner CPE CPU排程 cron Daphne Django DNS設定 Docker ftp fused-location Git Gmail API GPT hashcat HTML HTTPS hydra kotlin Line Bot Linux location-app Mac macOS MySQL n8n Next.js Nginx nmap NVIDIA OpenAI openssl permissions Python Python Django Python Email Pytorch pytorch PyTorch R2 rclone React Redis Replit SSL streamlit Swap Traefik Ubuntu Unity Vector Vera CPU VM設定 VPS VSCode WebSocket Wi-Fi WordPress Wordpress wordpress-備份 yolo YOLOv9 中位數 代理型 AI 企業市值 伺服器設定 修復 儲存空間 最佳住址 利率 前端 動態生成 即時通訊 卷積神經網路 台股 場景切換 學習 安全性 實作 專案管理 帳號建立 影像分類 後台 性能 惡意軟體 成績查詢 成績管理 技巧 投資 排序 操作系統 效率 教學 本地測試 殖利率 深度學習 清理 滲透測試 演算法 版本控制 物件偵測 神經網路 科技產業 程式解題 程式設計 競程 系統管理 終端機 統計 經濟成長 網域綁定 網站恢復 網頁框架 股票 自動化 自駕車 財務 資料共享 資料庫 資料結構 資訊安全 距離計算 路由 遊戲開發 選單 部署 郵件發送 開發 開發技巧 開發環境 開發者 雲端備份 靜態文件
目錄