一、请求获取设备坐标的权限
1.1 对应的权限需要申请在AndroidManifest
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
1.2 新建一个LocationManagerUtils类
class LocationManagerUtils(val context: Context, ) {
private val permissionRequestCode = 1
interface PermissionCallback {
fun onPermissionGranted()
fun onPermissionDenied()
}
private var permissionCallback: PermissionCallback? = null
fun setPermissionCallback(callback: PermissionCallback) {
permissionCallback = callback
}
private fun checkLocationPermissions() : Boolean {
val fineLocationPermission =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
val coarseLocationPermission =
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
return (fineLocationPermission == PackageManager.PERMISSION_GRANTED
&& coarseLocationPermission == PackageManager.PERMISSION_GRANTED)
}
fun requestLocationPermission(activity: Activity) {
if(checkLocationPermissions()) {
ActivityCompat.requestPermissions(activity, arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
), permissionRequestCode)
}
}
fun onRequestPermissionsResult(
requestCode: Int,
grantResults: IntArray
) {
if (requestCode == permissionRequestCode) {
var allPermissionsGranted = true
for (result in grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
allPermissionsGranted = false
break
}
}
if (!allPermissionsGranted) {
permissionCallback?.onPermissionDenied()
} else {
permissionCallback?.onPermissionGranted()
}
}
}
}
这个LocationManagerUtils
类是一个用于处理位置权限和位置更新的实用工具类。
它提供了一种方便的方法来检查和请求位置权限,并通过回调函数来处理权限请求的结果和位置更新。
下面是这个类的一些关键部分的解释:
context: Context
: 这是一个构造函数参数,用于获取应用的上下文。
permissionRequestCode = 1:
这是一个私有变量,用于标识权限请求的请求码。
PermissionCallback
: 这是一个内部接口,定义了两个个方法:onPermissionGranted()
、onPermissionDenied()
。这些方法分别用于处理权限被授予、权限被拒绝的情况。
permissionCallback: PermissionCallback? = null
: 这是一个私有变量,用于存储PermissionCallback的实例。
setPermissionCallback(callback: PermissionCallback)
: 这是一个公开方法,用于设置PermissionCallback的实例。
checkLocationPermissions()
: 这是一个私有方法,用于检查应用是否已经获得了细粒度和粗粒度位置权限。它返回一个布尔值,表示是否已经获得了这两个权限。
requestLocationPermission(activity: Activity)
: 这是一个公开方法,用于请求位置权限。如果应用尚未获得位置权限,它会调用ActivityCompat.requestPermissions()方法来请求权限。
onRequestPermissionsResult(requestCode: Int, grantResults: IntArray)
: 这是一个公开方法,用于处理权限请求的结果。它会检查请求码是否匹配,并根据grantResults数组中的结果来调用PermissionCallback的相应方法。
总的来说,这个类提供了一种封装好的方式来处理位置权限的请求和结果,以及位置更新。通过设置一个PermissionCallback实例,你可以在权限被授予或被拒绝时收到通知,并在位置发生变化时收到更新。
1.3 在MainActivity里:
class MainActivity : AppCompatActivity(), LocationManagerUtils.PermissionCallback {
private lateinit var locationManagerUtils: LocationManagerUtils
override fun onCreate(savedInstanceState: Bundle?) {
locationManagerUtils = LocationManagerUtils(this).apply {
setPermissionCallback(this@MainActivity)
requestLocationPermission(this@MainActivity)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
locationManagerUtils.onRequestPermissionsResult(requestCode, grantResults)
}
override fun onPermissionGranted() {
Log.d(tag, "onPermissionGranted")
locationManagerUtils.requestNetworkLocationUpdates()
}
override fun onPermissionDenied() {
Toast.makeText(this, R.string.requiredPermissionPrompt, Toast.LENGTH_SHORT).show()
ActivityCompat.finishAffinity(this)
}
}
在MainActivity
中,LocationManagerUtils
类被用来处理位置权限和位置更新。这里是MainActivity
中使用LocationManagerUtils
的步骤:
MainActivity
继承了AppCompatActivity
并实现了LocationManagerUtils.PermissionCallback
接口。这意味着MainActivity
需要实现PermissionCallback
接口中的方法。
在MainActivity
中,有一个locationManagerUtils
属性,它被延迟初始化(lateinit),这意味着它将在后面被初始化。
在onCreate
方法中,locationManagerUtils
被初始化为一个LocationManagerUtils
实例,并通过apply函数设置了权限回调(setPermissionCallback(this@MainActivity))并请求位置权限(requestLocationPermission(this@MainActivity))。
onRequestPermissionsResult
方法被重写以处理权限请求的结果。它首先调用父类的onRequestPermissionsResult
方法,然后将结果传递给locationManagerUtils
的onRequestPermissionsResult
方法。
onPermissionGranted
方法被实现以处理权限被授予的情况。在这个方法中,日志被记录,并且调用了locationManagerUtils
的requestNetworkLocationUpdates
方法来请求网络位置更新。
onPermissionDenied
方法被实现以处理权限被拒绝的情况。在这个方法中,一个提示被显示,告诉用户权限被拒绝,并且使用ActivityCompat.finishAffinity
方法来结束当前的Activity及其所有关联的任务。
总的来说,MainActivity
使用LocationManagerUtils
来请求位置权限,并在权限被授予时请求位置更新。如果权限被拒绝,则显示提示并结束Activity
。
二、获取坐标
class LocationManagerUtils(val context: Context, ) {
//...
interface PermissionCallback {
//...
fun onLocationChanged(location: Location)
}
private val locationManager: LocationManager = context.getSystemService(
Context.LOCATION_SERVICE) as LocationManager
private val locationListener = object : LocationListener {
override fun onLocationChanged(location: Location) {
permissionCallback?.onLocationChanged(location)
}
@Deprecated("Deprecated in Java")
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
}
override fun onProviderEnabled(provider: String) {
}
override fun onProviderDisabled(provider: String) {
}
}
@SuppressLint("MissingPermission")
fun requestNetworkLocationUpdates() {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
1000L,
1000f,
locationListener)
}
}
这个LocationManagerUtils
类是一个用于管理和监听Android
设备位置更新的实用工具类。它提供了一种方便的方法来请求网络提供的位置更新,并通过一个回调接口来处理位置变化。下面是这个类的一些关键部分的解释:
locationManager: LocationManager
: 这是一个私有属性,用于获取系统的定位服务。
locationListener
: 这是一个私有属性,是一个匿名内部类,实现了LocationListener接口。它用于监听位置更新,并在位置发生变化时调用PermissionCallback
的onLocationChanged
方法。
requestNetworkLocationUpdates()
: 这是一个公开方法,用于请求网络提供的位置更新。它使用LocationManager
的requestLocationUpdates
方法来注册位置监听器,并设置了更新频率(每1000毫秒或1秒更新一次,位置变化至少1000米时更新)。
总的来说,这个类提供了一种封装好的方式来请求和监听网络提供的位置更新,并通过设置一个PermissionCallback实例来处理位置更新。
在MainAcitivity里,继承onLocationChanged
来获取坐标变化
class MainActivity : AppCompatActivity(), LocationManagerUtils.PermissionCallback {
override fun onLocationChanged(location: Location) {
}
}
三、天气坐标api的添加
3.1 添加opeanweathermap接口
interface WeatherService {
/* https://openweathermap.org/current */
@GET("weather")
fun getWeatherByCityName(
@Query("q") cityName : String,
@Query("appid") apiKey : String
) : Call<WeatherResponse>
@GET("weather")
fun getWeatherByLocation(
@Query("lat") latitude: Double,
@Query("lon") longitude: Double,
@Query("appid") apkKey: String
) : Call<WeatherResponse>
/* https://openweathermap.org/forecast5 */
@GET("forecast")
fun getForecastByCityName(
@Query("q") cityName : String,
@Query("appid") apiKey : String
) : Call<ForecastResponse>
@GET("forecast")
fun getForecastByLocation(
@Query("lat") latitude: Double,
@Query("lon") longitude: Double,
@Query("appid") apkKey: String
) : Call<ForecastResponse>
}
WeatherService
接口定义了一组用于从天气服务 API(如 OpenWeatherMap
)访问天气和预报数据的方法。以下是这些方法及其参数的详细说明:
getWeatherByCityName
:此方法通过城市名称获取当前天气数据。它需要两个参数:cityName
和 apiKey
。cityName
是城市的名称,apiKey
是进行身份验证所需的 API 密钥。
getWeatherByLocation
:此方法通过纬度和经度获取当前天气数据。它需要三个参数:latitude
、longitude
和 apkKey
(应该是 apiKey)。latitude
和 longitude
指定位置的坐标,apkKey 是进行身份验证所需的 API 密钥。
getForecastByCityName
:此方法通过城市名称获取预报数据。它需要两个参数:cityName 和 apiKey。cityName
是城市的名称,apiKey 是进行身份验证所需的 API 密钥。
getForecastByLocation
:此方法通过纬度和经度获取预报数据。它需要三个参数:latitude、longitude 和 apkKey
(应该是 apiKey)。latitude
和 longitude
指定位置的坐标,apkKey 是进行身份验证所需的 API 密钥。
每个方法都返回一个泛型类型为 WeatherResponse
或 ForecastResponse
的 Call 对象,具体取决于它是当前天气数据还是预报数据。这些 Call 对象代表可以入队的异步请求,可以执行网络请求并提供响应或潜在错误的回调。
3.2 在RetrofitClient里添加根据坐标获取天气的接口
object RetrofitClient {
private fun callGetCurrentWeather(call: Call<WeatherResponse>) {
call.enqueue(object : Callback<WeatherResponse> {
override fun onResponse(
call: Call<WeatherResponse>,
response: Response<WeatherResponse>
) {
if (response.isSuccessful) {
val weatherData = response.body()
handleWeatherData(weatherData)
} else {
handleWeatherFailure(response.message())
}
}
override fun onFailure(call: Call<WeatherResponse>, t: Throwable) {
handleWeatherFailure(t.message!!)
}
})
}
fun getWeatherByCityName(cityName: String) {
val call = weatherService.getWeatherByCityName(cityName, API_KEY)
callGetCurrentWeather(call)
}
fun getWeatherByLocation(location: Location) {
val call = weatherService.getWeatherByLocation(location.latitude, location.longitude, API_KEY)
Log.d(TAG, "lat:${location.latitude},lon:${location.longitude}")
callGetCurrentWeather(call)
}
private fun callGetForecast(call: Call<ForecastResponse>) {
call.enqueue(object : Callback<ForecastResponse> {
override fun onResponse(
call : Call<ForecastResponse>, response: Response<ForecastResponse>) {
if(response.isSuccessful) {
val forecastData = response.body()
handleForecastData(forecastData)
} else {
handleForecastFailure(response.message())
}
}
override fun onFailure(call: Call<ForecastResponse>, t: Throwable) {
handleForecastFailure(t.message!!)
}
})
}
fun getForecastByCityName(cityName: String) {
val call = weatherService.getForecastByCityName(cityName, API_KEY)
callGetForecast(call)
}
fun getForecastByLocation(location: Location) {
val call = weatherService.getForecastByLocation(location.latitude, location.longitude, API_KEY)
callGetForecast(call)
}
//...
}
其他都一样的,只是参数改成了Location
。
四、获取最近城市的信息
4.1 在CityDataAdapter获取最近的城市数据
class CityDataAdapter(private val originCityDataList: List<CityData>) :
RecyclerView.Adapter<CityDataAdapter.ViewHolder>() {
fun getClosestCityName(deviceLocation: Location) : CityData? {
var closestCityData : CityData ?= null
var minDistance = Double.MAX_VALUE
for(cityData in originCityDataList) {
val cityLocation = Location("")
cityLocation.latitude = cityData.coord.lat
cityLocation.longitude = cityData.coord.lon
val distance = deviceLocation.distanceTo(cityLocation)
if(distance < minDistance) {
minDistance = distance.toDouble()
closestCityData = cityData
}
}
Log.d("CityDataAdapter", "city name: ${closestCityData?.name}, " +
"${closestCityData?.coord?.lat},${closestCityData?.coord?.lon}, "+
"distance:${minDistance}")
return closestCityData
}
}
这个CityDataAdapter
类是一个为RecyclerView
设计的适配器,用于展示一个城市数据列表。它包含了一个方法getClosestCityName
,用于根据设备的位置找到最近的城市的功能。这里是这个类的详细描述:
originCityDataList: List<CityData>
: 这是一个构造函数参数,用于接收一个城市数据列表。
getClosestCityName(deviceLocation: Location) : CityData?
: 这是一个方法,用于找到与设备位置最近的城市的城市数据。它接受一个Location对象作为参数,表示设备的位置。
closestCityData: CityData? = null
: 这是一个局部变量,用于存储最近的城市的城市数据。
minDistance = Double.MAX_VALUE
: 这是一个局部变量,用于存储最近城市的最小距离。
方法中的for循环遍历originCityDataList
中的每个城市数据,并创建一个Location
对象来表示城市的位置。
使用distanceTo
方法计算设备位置与城市位置之间的距离。
如果计算出的距离小于minDistance
,则更新minDistance
和closestCityData
。
方法最后,通过日志输出最近城市的相关信息,并返回closestCityData
。
总的来说,这个适配器不仅用于展示城市数据列表,还提供了一个方便的方法来找到用户设备位置最近的城市的城市数据。
4.2 在MainActivity里补全:
class MainActivity : AppCompatActivity(), LocationManagerUtils.PermissionCallback {
override fun onLocationChanged(location: Location) {
val cityDataAdapter = binding.cityDataRecyclerView.adapter as CityDataAdapter
val closestCityData = cityDataAdapter.getClosestCityName(location)
val locationMessage = "latitude:${location.latitude}, longitude:${location.longitude}"
val cityName = "closet CityName:${closestCityData?.name}"
val message = "$locationMessage, $cityName"
if(closestCityData == null) {
RetrofitClient.getWeatherByLocation(location)
RetrofitClient.getForecastByLocation(location)
Toast.makeText(this, "update by location $locationMessage", Toast.LENGTH_SHORT).show()
} else {
val name = closestCityData.name
RetrofitClient.getWeatherByCityName(name)
RetrofitClient.getForecastByCityName(name)
Toast.makeText(this, "update by city name $cityName", Toast.LENGTH_SHORT).show()
}
Log.d(tag, message)
}
}
在MainActivity中,实现了LocationManagerUtils.PermissionCallback
接口的onLocationChanged
方法。这个方法会在设备的位置发生变化时被调用,用于根据新的位置信息更新天气数据。这里是这个方法的详细描述:
cityDataAdapter = binding.cityDataRecyclerView.adapter as CityDataAdapter
: 这行代码获取了与RecyclerView
绑定的适配器,并将其转换为CityDataAdapter类型。
closestCityData = cityDataAdapter.getClosestCityName(location)
: 这行代码调用了CityDataAdapter的getClosestCityName
方法,传入了当前设备的位置信息,以获取最近的城市的城市数据。
locationMessage = "latitude:${location.latitude}, longitude:${location.longitude}"
: 这行代码创建了一个包含当前位置信息的字符串。
cityName = "closet CityName:${closestCityData?.name}"
: 这行代码创建了一个包含最近城市的城市名称的字符串。
message = "$locationMessage, $cityName"
: 这行代码将位置信息和城市名称合并为一个消息字符串。
如果closestCityData
为null
,表示没有找到最近的城市的城市数据,那么将通过位置信息来更新天气数据:
RetrofitClient.getWeatherByLocation(location)
:
这行代码调用RetrofitClient
的getWeatherByLocation
方法,传入了当前设备的位置信息,以获取天气数据。RetrofitClient.getForecastByLocation(location)
:
这行代码调用RetrofitClient
的getForecastByLocation
方法,传入了当前设备的位置信息,以获取天气预报数据。- 显示一个提示,告诉用户天气数据正在通过位置信息更新。
如果closestCityData
不为null
,表示找到了最近的城市的城市数据,那么将通过城市名称来更新天气数据:
name = closestCityData.name
: 这行代码获取了最近城市的城市名称。RetrofitClient.getWeatherByCityName(name)
: 这行代码调用RetrofitClient
的getWeatherByCityName
方法,传入了最近城市的城市名称,以获取天气数据。RetrofitClient.getForecastByCityName(name)
: 这行代码调用RetrofitClient
的getForecastByCityName
方法,传入了最近城市的城市名称,以获取天气预报数据。- 显示一个提示,告诉用户天气数据正在通过城市名称更新。
Log.d(tag, message)
: 这行代码记录了一个日志,包含了位置信息和城市名称的消息。
总的来说,这个onLocationChanged
方法用于根据设备的位置信息来更新天气数据,并通过提示和日志来通知用户更新情况。