├── .circleci └── config.yml ├── .github └── workflows │ ├── gradle.yml │ └── issues-stale.yml ├── .gitignore ├── .travis.yml ├── GIF.gif ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output-metadata.json └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── king │ │ └── location │ │ └── app │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── king │ │ │ └── location │ │ │ └── app │ │ │ └── MainActivity.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── king │ └── location │ └── app │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── location ├── .gitignore ├── build.gradle ├── consumer-rules.pro ├── gradle.properties ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── king │ │ └── location │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── king │ │ └── location │ │ ├── ILocationClient.kt │ │ ├── LocationClient.kt │ │ ├── LocationErrorCode.kt │ │ ├── LocationOption.kt │ │ ├── OnExceptionListener.kt │ │ └── OnLocationListener.kt │ └── test │ └── java │ └── com │ └── king │ └── location │ └── ExampleUnitTest.kt ├── settings.gradle └── versions.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | android: circleci/android@0.2.0 5 | 6 | jobs: 7 | build: 8 | executor: android/android 9 | 10 | steps: 11 | - checkout 12 | - run: 13 | command: ./gradlew build -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up JDK 1.8 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 1.8 19 | - name: Build with Gradle 20 | run: ./gradlew build -------------------------------------------------------------------------------- /.github/workflows/issues-stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | on: 3 | schedule: 4 | - cron: '0 16 * * *' 5 | 6 | jobs: 7 | stale: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v5 14 | with: 15 | stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' 16 | days-before-stale: 30 17 | days-before-close: 5 18 | exempt-all-pr-milestones: true 19 | exempt-issue-labels: 'help wanted' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | dist: trusty 3 | jdk: oraclejdk8 4 | sudo: false 5 | 6 | env: 7 | global: 8 | - ANDROID_API_LEVEL=30 9 | - ANDROID_BUILD_TOOLS_VERSION=30.0.3 10 | - TRAVIS_SECURE_ENV_VARS=true 11 | 12 | before_install: 13 | - chmod +x gradlew 14 | - mkdir "$ANDROID_HOME/licenses" || true 15 | # Hack to accept Android licenses 16 | - yes | sdkmanager "platforms;android-$ANDROID_API_LEVEL" 17 | 18 | 19 | android: 20 | components: 21 | # The BuildTools version used by your project 22 | - tools 23 | - platform-tools 24 | - build-tools-$ANDROID_BUILD_TOOLS_VERSION 25 | # The SDK version used to compile your project 26 | - android-$ANDROID_API_LEVEL 27 | - extra-android-m2repository 28 | - extra-google-android-support 29 | 30 | script: 31 | - ./gradlew clean 32 | - ./gradlew assembleRelease -------------------------------------------------------------------------------- /GIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenly1314/Location/4c0459ba5c2f4935419a6173fb89e0d81d0f50dc/GIF.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Jenly Yu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Location 2 | 3 | ![Image](app/src/main/ic_launcher-playstore.png) 4 | 5 | [![MavenCentral](https://img.shields.io/maven-central/v/com.github.jenly1314/location?logo=sonatype)](https://repo1.maven.org/maven2/com/github/jenly1314/Location) 6 | [![JitPack](https://img.shields.io/jitpack/v/github/jenly1314/Location?logo=jitpack)](https://jitpack.io/#jenly1314/Location) 7 | [![CI](https://img.shields.io/github/actions/workflow/status/jenly1314/Location/gradle.yml?logo=github)](https://github.com/jenly1314/Location/actions/workflows/gradle.yml) 8 | [![Download](https://img.shields.io/badge/download-APK-brightgreen?logo=github)](https://raw.githubusercontent.com/jenly1314/Location/master/app/release/app-release.apk) 9 | [![API](https://img.shields.io/badge/API-21%2B-brightgreen?logo=android)](https://developer.android.com/guide/topics/manifest/uses-sdk-element#ApiLevels) 10 | [![License](https://img.shields.io/github/license/jenly1314/Location?logo=open-source-initiative)](https://opensource.org/licenses/mit) 11 | 12 | 13 | Location 是一个通过 Android 自带的 LocationManager 来实现的定位功能。 14 | 15 | > 之所以写这个库的主要原因还需要从下面的场景说起:在开发某个App的过程当中,可能有个需求需要用到定位功能,但是又不那么重要; 16 | 这个时候如果选择使用第三方的定位服务,需要先去申请相关的appKey和集成定位相关的SDK,显得太繁琐了。杀鸡焉用宰牛刀,我们只是需要一个简单的定位功能,使用自带的实现不香吗?因此就有了 **Location**。 17 | 18 | ## 效果展示 19 | ![Image](GIF.gif) 20 | 21 | > 你也可以直接下载 [演示App](https://raw.githubusercontent.com/jenly1314/Location/master/app/release/app-release.apk) 体验效果 22 | 23 | ## 引入 24 | 25 | ### Gradle: 26 | 27 | 1. 在Project的 **build.gradle** 或 **setting.gradle** 中添加远程仓库 28 | 29 | ```gradle 30 | repositories { 31 | //... 32 | mavenCentral() 33 | } 34 | ``` 35 | 36 | 2. 在Module的 **build.gradle** 中添加依赖项 37 | 38 | ```gradle 39 | implementation 'com.github.jenly1314:location:1.0.0' 40 | ``` 41 | 42 | ## 使用 43 | 44 | ### 示例 45 | 46 | 主要方法调用示例 47 | ```kotlin 48 | 49 | //初始化实例 50 | val locationClient = LocationClient(this) 51 | 52 | //可根据具体需求设置定位配置参数 53 | val locationOption = locationClient.getLocationOption() 54 | .setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度 55 | .setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒 56 | .setOnceLocation(false)//设置是否只定位一次,默认为 false,当设置为 true 时,则只定位一次后,会自动停止定位 57 | 58 | //设置定位配置参数 59 | locationClient.setLocationOption(locationOption) 60 | 61 | //---------------------------------- 62 | 63 | //设置定位监听 64 | locationClient.setOnLocationListener(object: OnLocationListener(){ 65 | override fun onLocationChanged(location: Location) { 66 | //TODO 位置信息 67 | } 68 | 69 | }) 70 | 71 | //---------------------------------- 72 | 73 | //开始定位(建议先校验是否有定位权限,然后开始定位) 74 | locationClient.startLocation() 75 | 76 | //---------------------------------- 77 | 78 | //停止定位 79 | locationClient.stopLocation() 80 | 81 | ``` 82 | 83 | 完整示例 84 | ```kotlin 85 | class MainActivity : AppCompatActivity() { 86 | 87 | private val binding by lazy { 88 | ActivityMainBinding.inflate(layoutInflater) 89 | } 90 | 91 | private val locationClient by lazy { 92 | LocationClient(this) 93 | } 94 | 95 | private val geocoder by lazy { 96 | Geocoder(this) 97 | } 98 | 99 | private val simpleDateFormat by lazy { 100 | SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 101 | } 102 | 103 | private var isPermissionGranted = false 104 | 105 | companion object{ 106 | const val TAG = "MainActivity" 107 | const val REQ_LOCATION_PERMISSION = 0x01 108 | } 109 | 110 | override fun onCreate(savedInstanceState: Bundle?) { 111 | super.onCreate(savedInstanceState) 112 | setContentView(binding.root) 113 | 114 | binding.btnStartLocation.setOnClickListener { 115 | isPermissionGranted = checkLocationPermission() 116 | if(isPermissionGranted){ 117 | startLocation() 118 | }else{ 119 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQ_LOCATION_PERMISSION) 120 | } 121 | } 122 | 123 | binding.btnStopLocation.setOnClickListener { 124 | locationClient.stopLocation() 125 | binding.tvLocation.text = "" 126 | } 127 | 128 | binding.cbOnceLocation.setOnCheckedChangeListener { buttonView, isChecked -> 129 | locationClient.getLocationOption().setOnceLocation(isChecked) 130 | } 131 | 132 | binding.cbLastKnownLocation.setOnCheckedChangeListener { buttonView, isChecked -> 133 | locationClient.getLocationOption().setLastKnownLocation(isChecked) 134 | } 135 | 136 | //可根据具体需求设置定位配置参数(这里只列出一些主要的参数) 137 | val locationOption = locationClient.getLocationOption() 138 | .setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度 139 | .setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗 140 | .setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒 141 | .setMinDistance(0)//设置位置更新最小距离(单位:米);默认距离:0米 142 | .setOnceLocation(false)//设置是否只定位一次,默认为 false,当设置为 true 时,则只定位一次后,会自动停止定位 143 | .setLastKnownLocation(true)//设置是否获取最后一次缓存的已知位置,默认为 true 144 | //设置定位配置参数 145 | locationClient.setLocationOption(locationOption) 146 | 147 | //设置定位监听 148 | locationClient.setOnLocationListener(object: OnLocationListener(){ 149 | override fun onLocationChanged(location: Location) { 150 | //位置信息 151 | Log.d(TAG,"onLocationChanged(location = ${location})") 152 | val builder = StringBuilder() 153 | builder.append("Longitude: \t${location.longitude}\n") 154 | .append("Latitude: \t${location.latitude}\n") 155 | 156 | //根据坐标经纬度获取位置地址信息(WGS-84坐标系) 157 | val list = geocoder.getFromLocation(location.latitude,location.longitude,1) 158 | if(list.isNotEmpty()){ 159 | builder.append("Address: \t${list[0].getAddressLine(0)}\n") 160 | } 161 | 162 | builder.append("Time: \t${simpleDateFormat.format(Date(location.time))}\n") 163 | .append("Provider: \t${location.provider}\n") 164 | 165 | binding.tvLocation.text = builder.toString() 166 | } 167 | 168 | override fun onProviderEnabled(provider: String) { 169 | super.onProviderEnabled(provider) 170 | Log.d(TAG,"onProviderEnabled(provider = ${provider})") 171 | } 172 | 173 | override fun onProviderDisabled(provider: String) { 174 | super.onProviderDisabled(provider) 175 | Log.d(TAG,"onProviderDisabled(provider = ${provider})") 176 | } 177 | 178 | }) 179 | 180 | //设置异常监听 181 | locationClient.setOnExceptionListener(object : OnExceptionListener{ 182 | override fun onException(@LocationErrorCode errorCode: Int, e: Exception) { 183 | //定位出现异常 184 | Log.w(TAG,"onException(errorCode = ${errorCode}, e = ${e})") 185 | binding.tvLocation.text = e.message 186 | } 187 | }) 188 | } 189 | 190 | override fun onResume() { 191 | super.onResume() 192 | if(!isPermissionGranted){ 193 | if(checkLocationPermission()){ 194 | isPermissionGranted = true 195 | binding.tvLocation.text = "" 196 | } 197 | } 198 | } 199 | 200 | /** 201 | * 开始定位 202 | */ 203 | private fun startLocation(){ 204 | if(locationClient.isStarted()){//如果已经开始定位,则先停止定位 205 | locationClient.stopLocation() 206 | } 207 | binding.tvLocation.text = "Start location..." 208 | locationClient.startLocation() 209 | 210 | } 211 | 212 | override fun onRequestPermissionsResult( 213 | requestCode: Int, 214 | permissions: Array, 215 | grantResults: IntArray 216 | ) { 217 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 218 | if(requestCode == REQ_LOCATION_PERMISSION){ 219 | isPermissionGranted = verifyPermissions(grantResults) 220 | if(isPermissionGranted){//已授权 221 | startLocation() 222 | }else{//未授权 223 | binding.tvLocation.text = "Location permission has not been granted." 224 | showMissingPermissionDialog() 225 | } 226 | } 227 | } 228 | 229 | /** 230 | * 检测位置权限 231 | */ 232 | private fun checkLocationPermission(): Boolean{ 233 | return ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED 234 | } 235 | 236 | /** 237 | * 校验授权结果 238 | */ 239 | private fun verifyPermissions(grantResults: IntArray): Boolean{ 240 | for(result in grantResults){ 241 | if(result != PackageManager.PERMISSION_GRANTED){ 242 | return false 243 | } 244 | } 245 | return true 246 | } 247 | 248 | /** 249 | * 显示未授权提示对话框 250 | */ 251 | private fun showMissingPermissionDialog(){ 252 | val builder = AlertDialog.Builder(this) 253 | builder.setMessage("Location permission has not been granted.") 254 | .setNegativeButton("Cancel" 255 | ) { dialog, which -> 256 | 257 | } 258 | .setPositiveButton("Setting" 259 | ) { dialog, which -> 260 | startAppSettings() 261 | } 262 | builder.show() 263 | } 264 | 265 | 266 | /** 267 | * 跳转到 App 的设置详情界面 268 | */ 269 | private fun startAppSettings(){ 270 | val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 271 | intent.data = Uri.parse("package:${packageName}") 272 | startActivity(intent) 273 | } 274 | 275 | } 276 | 277 | ``` 278 | 279 | 在你项目的 **AndroidManifest.xml** 清单文件中添加定位相关权限 280 | ```xml 281 | 282 | 283 | 284 | 285 | 286 | ``` 287 | 288 | 更多使用详情,请查看[app](app)中的源码使用示例或直接查看[API帮助文档](https://jitpack.io/com/github/jenly1314/Location/latest/javadoc/) 289 | 290 | ## 相关推荐 291 | - [MapHelper](https://github.com/jenly1314/MapHelper) 一个整合了高德地图、百度地图、腾讯地图、谷歌地图等相关路线规划和导航的地图帮助类库。 292 | 293 | ## 版本日志 294 | 295 | #### v1.0.0:2021-9-9 296 | * Location初始版本 297 | 298 | --- 299 | 300 | ![footer](https://jenly1314.github.io/page/footer.svg) 301 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | compileSdkVersion build_versions.compileSdk 8 | buildToolsVersion build_versions.buildTools 9 | 10 | defaultConfig { 11 | applicationId "com.king.location.app" 12 | minSdkVersion build_versions.minSdk 13 | targetSdkVersion build_versions.targetSdk 14 | versionCode app_version.versionCode 15 | versionName app_version.versionName 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | kotlinOptions { 31 | jvmTarget = '1.8' 32 | } 33 | 34 | buildFeatures { 35 | viewBinding true 36 | } 37 | 38 | lintOptions { 39 | abortOnError false 40 | warning 'InvalidPackage' 41 | } 42 | } 43 | 44 | dependencies { 45 | 46 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$versions.kotlin" 47 | implementation "androidx.core:core-ktx:$versions.coreKtx" 48 | implementation "androidx.appcompat:appcompat:$versions.appcompat" 49 | implementation "com.google.android.material:material:$versions.material" 50 | implementation "androidx.constraintlayout:constraintlayout:$versions.constraintlayout" 51 | 52 | testImplementation "junit:junit:$versions.junit" 53 | androidTestImplementation "androidx.test.ext:junit:$versions.androidExtJunit" 54 | androidTestImplementation "androidx.test.espresso:espresso-core:$versions.espressoCore" 55 | 56 | implementation project(path: ':location') 57 | 58 | } 59 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenly1314/Location/4c0459ba5c2f4935419a6173fb89e0d81d0f50dc/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output-metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "artifactType": { 4 | "type": "APK", 5 | "kind": "Directory" 6 | }, 7 | "applicationId": "com.king.location.app", 8 | "variantName": "release", 9 | "elements": [ 10 | { 11 | "type": "SINGLE", 12 | "filters": [], 13 | "versionCode": 1, 14 | "versionName": "1.0.0", 15 | "outputFile": "app-release.apk" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/king/location/app/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.king.location.app 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.king.location.app", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenly1314/Location/4c0459ba5c2f4935419a6173fb89e0d81d0f50dc/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/king/location/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.king.location.app 2 | 3 | import android.Manifest 4 | import android.app.AlertDialog 5 | import android.content.Intent 6 | import android.content.pm.PackageManager 7 | import android.location.Criteria 8 | import android.location.Geocoder 9 | import android.location.Location 10 | import android.net.Uri 11 | import android.os.Bundle 12 | import android.provider.Settings 13 | import android.util.Log 14 | import androidx.appcompat.app.AppCompatActivity 15 | import androidx.core.app.ActivityCompat 16 | import com.king.location.* 17 | import com.king.location.app.databinding.ActivityMainBinding 18 | import java.text.SimpleDateFormat 19 | import java.util.* 20 | 21 | class MainActivity : AppCompatActivity() { 22 | 23 | private val binding by lazy { 24 | ActivityMainBinding.inflate(layoutInflater) 25 | } 26 | 27 | private val locationClient by lazy { 28 | LocationClient(this) 29 | } 30 | 31 | private val geocoder by lazy { 32 | Geocoder(this) 33 | } 34 | 35 | private val simpleDateFormat by lazy { 36 | SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 37 | } 38 | 39 | private var isPermissionGranted = false 40 | 41 | companion object{ 42 | const val TAG = "MainActivity" 43 | const val REQ_LOCATION_PERMISSION = 0x01 44 | } 45 | 46 | override fun onCreate(savedInstanceState: Bundle?) { 47 | super.onCreate(savedInstanceState) 48 | setContentView(binding.root) 49 | 50 | binding.btnStartLocation.setOnClickListener { 51 | isPermissionGranted = checkLocationPermission() 52 | if(isPermissionGranted){ 53 | startLocation() 54 | }else{ 55 | ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), REQ_LOCATION_PERMISSION) 56 | } 57 | } 58 | 59 | binding.btnStopLocation.setOnClickListener { 60 | locationClient.stopLocation() 61 | binding.tvLocation.text = "" 62 | } 63 | 64 | binding.cbOnceLocation.setOnCheckedChangeListener { buttonView, isChecked -> 65 | locationClient.getLocationOption().setOnceLocation(isChecked) 66 | } 67 | 68 | binding.cbLastKnownLocation.setOnCheckedChangeListener { buttonView, isChecked -> 69 | locationClient.getLocationOption().setLastKnownLocation(isChecked) 70 | } 71 | 72 | //可根据具体需求设置定位配置参数(这里只列出一些主要的参数) 73 | val locationOption = locationClient.getLocationOption() 74 | .setAccuracy(Criteria.ACCURACY_FINE)//设置位置精度:高精度 75 | .setPowerRequirement(Criteria.POWER_LOW) //设置电量消耗:低电耗 76 | .setMinTime(10000)//设置位置更新最小时间间隔(单位:毫秒); 默认间隔:10000毫秒,最小间隔:1000毫秒 77 | .setMinDistance(0)//设置位置更新最小距离(单位:米);默认距离:0米 78 | .setOnceLocation(false)//设置是否只定位一次,默认为 false,当设置为 true 时,则只定位一次后,会自动停止定位 79 | .setLastKnownLocation(true)//设置是否获取最后一次缓存的已知位置,默认为 true 80 | //设置定位配置参数 81 | locationClient.setLocationOption(locationOption) 82 | 83 | //设置定位监听 84 | locationClient.setOnLocationListener(object: OnLocationListener(){ 85 | override fun onLocationChanged(location: Location) { 86 | //位置信息 87 | Log.d(TAG,"onLocationChanged(location = ${location})") 88 | val builder = StringBuilder() 89 | builder.append("Longitude: \t${location.longitude}\n") 90 | .append("Latitude: \t${location.latitude}\n") 91 | 92 | //根据坐标经纬度获取位置地址信息(WGS-84坐标系) 93 | val list = geocoder.getFromLocation(location.latitude,location.longitude,1) 94 | if(list.isNotEmpty()){ 95 | builder.append("Address: \t${list[0].getAddressLine(0)}\n") 96 | } 97 | 98 | builder.append("Time: \t${simpleDateFormat.format(Date(location.time))}\n") 99 | .append("Provider: \t${location.provider}\n") 100 | 101 | binding.tvLocation.text = builder.toString() 102 | } 103 | 104 | override fun onProviderEnabled(provider: String) { 105 | super.onProviderEnabled(provider) 106 | Log.d(TAG,"onProviderEnabled(provider = ${provider})") 107 | } 108 | 109 | override fun onProviderDisabled(provider: String) { 110 | super.onProviderDisabled(provider) 111 | Log.d(TAG,"onProviderDisabled(provider = ${provider})") 112 | } 113 | 114 | }) 115 | 116 | //设置异常监听 117 | locationClient.setOnExceptionListener(object : OnExceptionListener{ 118 | override fun onException(@LocationErrorCode errorCode: Int, e: Exception) { 119 | //定位出现异常 120 | Log.w(TAG,"onException(errorCode = ${errorCode}, e = ${e})") 121 | binding.tvLocation.text = e.message 122 | } 123 | }) 124 | } 125 | 126 | override fun onResume() { 127 | super.onResume() 128 | if(!isPermissionGranted){ 129 | if(checkLocationPermission()){ 130 | isPermissionGranted = true 131 | binding.tvLocation.text = "" 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * 开始定位 138 | */ 139 | private fun startLocation(){ 140 | if(locationClient.isStarted()){//如果已经开始定位,则先停止定位 141 | locationClient.stopLocation() 142 | } 143 | binding.tvLocation.text = "Start location..." 144 | locationClient.startLocation() 145 | 146 | } 147 | 148 | override fun onRequestPermissionsResult( 149 | requestCode: Int, 150 | permissions: Array, 151 | grantResults: IntArray 152 | ) { 153 | super.onRequestPermissionsResult(requestCode, permissions, grantResults) 154 | if(requestCode == REQ_LOCATION_PERMISSION){ 155 | isPermissionGranted = verifyPermissions(grantResults) 156 | if(isPermissionGranted){//已授权 157 | startLocation() 158 | }else{//未授权 159 | binding.tvLocation.text = "Location permission has not been granted." 160 | showMissingPermissionDialog() 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * 检测位置权限 167 | */ 168 | private fun checkLocationPermission(): Boolean{ 169 | return ActivityCompat.checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED 170 | } 171 | 172 | /** 173 | * 校验授权结果 174 | */ 175 | private fun verifyPermissions(grantResults: IntArray): Boolean{ 176 | for(result in grantResults){ 177 | if(result != PackageManager.PERMISSION_GRANTED){ 178 | return false 179 | } 180 | } 181 | return true 182 | } 183 | 184 | /** 185 | * 显示未授权提示对话框 186 | */ 187 | private fun showMissingPermissionDialog(){ 188 | val builder = AlertDialog.Builder(this) 189 | builder.setMessage("Location permission has not been granted.") 190 | .setNegativeButton("Cancel" 191 | ) { dialog, which -> 192 | 193 | } 194 | .setPositiveButton("Setting" 195 | ) { dialog, which -> 196 | startAppSettings() 197 | } 198 | builder.show() 199 | } 200 | 201 | 202 | /** 203 | * 跳转到 App 的设置详情界面 204 | */ 205 | private fun startAppSettings(){ 206 | val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) 207 | intent.data = Uri.parse("package:${packageName}") 208 | startActivity(intent) 209 | } 210 | 211 | 212 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 |