在 Android 10(API 级别 29)及更高版本中,使用位置信息服务的应用必须请求位置权限。为了保护用户隐私,这些应用需要获得用户的同意才能访问他们的位置信息。根据精确度要求,应用可以请求大致位置或者确切位置的权限。

根据不同的权限需求,可以将权限分为以下几个类别:

前台位置信息:建议开发者声明 location 的前台服务类型。在 Android 10(API 级别 29)及更高版本中,您必须声明此前台服务类型。示例代码如下:

```xml

android:name="MyNavigationService"

android:foregroundServiceType="location">

```

在上述代码中,我们声明了一个名为 "MyNavigationService" 的前台服务,其类型为 "location",用于获取前台位置信息。请注意,此服务仅适用于 Android 10(API 级别 29)及更高版本。

准确位置:当应用请求ACCESS_COARSE_LOCATION权限或ACCESS_FINE_LOCATION权限时,就是在声明需要获取准确位置信息。这两个权限分别表示应用可以访问大致位置和确切位置。示例代码如下:

```xml

```

后台位置信息:在某些情况下,应用可能需要在后台收集位置信息。为此,可以使用ACCESS_BACKGROUND_LOCATION权限。该权限允许应用在后台访问用户的大致位置信息。示例代码如下:

```xml

```

总结:在使用位置信息服务的应用中,需要根据实际需求选择合适的权限类别和精确度级别。对于前台位置信息,建议声明 location 的前台服务类型;对于准确位置,可以根据需要请求 ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 权限;而对于后台位置信息,可以使用ACCESS_BACKGROUND_LOCATION权限。

在 Android 10(API 级别 29)及更高版本中,您必须在应用的清单中声明ACCESS_BACKGROUND_LOCATION权限,以便请求在运行时于后台访问位置信息。在较低版本的 Android 系统中,当应用获得前台位置信息访问权限时,也会自动获得后台位置信息访问权限。

大致位置是指提供设备位置的估算值。如果此位置估算值来自LocationManagerService或FusedLocationProvider,则该估算值会精确到 3 平方公里(约 1.2 平方英里)以内。如果您的应用已经声明了ACCESS_COARSE_LOCATION权限,但未声明ACCESS_FINE_LOCATION权限,您的应用就可以收到该精确度级别的位置信息。

确切位置是指提供尽可能准确的设备位置估算值。如果位置估算值来自 LocationManagerService 或 FusedLocationProvider,则此估算值通常可以精确到 50 米(160 英尺)以内,有时甚至可以精确到几米(10 英尺)以内。如果您的应用已经声明了 ACCESS_FINE_LOCATION 权限,您的应用就可以收到该精确度级别的位置信息。

、定义工具类

为了实现上述功能,我们需要在Android应用程序中创建一个工具类。首先,确保已在AndroidManifest.xml文件中添加了必要的权限。这些权限包括允许程序打开网络套接字、获取网络状态、访问WiFi网络信息、访问CellID或WiFi热点来获取粗略的位置以及后台位置信息(适用于Android 10及以上版本)。

接下来,创建一个名为`PermissionUtils`的工具类,并在其中添加以下方法:

```java

import android.Manifest;

import android.content.Context;

import android.content.pm.PackageManager;

import androidx.core.app.ActivityCompat;

import androidx.core.content.ContextCompat;

public class PermissionUtils {

public static boolean hasInternetPermission(Context context) {

return ContextCompat.checkSelfPermission(context, Manifest.permission.INTERNET) == PackageManager.PERMISSION_GRANTED;

}

public static boolean hasAccessNetworkStatePermission(Context context) {

return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_NETWORK_STATE) == PackageManager.PERMISSION_GRANTED;

}

public static boolean hasAccessWifiStatePermission(Context context) {

return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_WIFI_STATE) == PackageManager.PERMISSION_GRANTED;

}

public static boolean hasAccessCoarseLocationPermission(Context context) {

return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;

}

public static boolean hasAccessFineLocationPermission(Context context) {

return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;

}

public static boolean hasAccessBackgroundLocationPermission(Context context) {

return ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_BACKGROUND_LOCATION) == PackageManager.PERMISSION_GRANTED;

}

public static void requestInternetPermission(Activity activity) {

ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.INTERNET}, 1);

}

public static void requestAccessNetworkStatePermission(Activity activity) {

ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_NETWORK_STATE}, 2);

}

public static void requestAccessWifiStatePermission(Activity activity) {

ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_WIFI_STATE}, 3);

}

public static void requestAccessCoarseLocationPermission(Activity activity) {

ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 4);

}

public static void requestAccessFineLocationPermission(Activity activity) {

ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 5);

}

public static void requestAccessBackgroundLocationPermission(Activity activity) {

ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_BACKGROUND_LOCATION}, 6);

}

}

```

在上述代码中,我们创建了一个名为`PermissionUtils`的工具类,并为每个权限方法提供了适当的实现。这些方法会检查应用程序是否已经获得了相应的权限,如果没有,则会请求用户授权。请注意,这些方法需要传递一个`Activity`类型的参数,因此在使用时请确保传入正确的上下文对象。

```java

public class LocationUtils { private volatile static LocationUtils uniqueInstance;

private LocationManager locationManager;

private Context mContext;

private ArrayList addressCallbacks;

private AddressCallback addressCallback;

private static Location location;

private boolean isInit = false; //是否加载过

public AddressCallback getAddressCallback() {

return addressCallback;

}

public void setAddressCallback(AddressCallback addressCallback) {

this.addressCallback = addressCallback;

if (isInit) {

showLocation();

} else {

isInit = true;

}

}

//其他方法保持不变

}

```

、开始定位

//注意6.0及以上版本需要在申请完权限后调用方法 LocationUtils.getInstance(this).setAddressCallback(new LocationUtils.AddressCallback() { @Override public void onGetAddress(Address address) { String countryName = address.getCountryName();//国家 String adminArea = address.getAdminArea();//省 String locality = address.getLocality();//市 String subLocality = address.getSubLocality();//区 String featureName = address.getFeatureName();//街道 LogUtils.eTag("定位地址",countryName,adminArea,locality,subLocality,featureName); } @Override public void onGetLocation(double lat, double lng) { LogUtils.eTag("定位经纬度",lat,lng); } });

注意:

某些版本的手机在使用Geocoder进行地址转换的时候会出现如下异常:

java.io.IOException: grpc failed

原因为:the service is not available 服务不可用 即设备不支持Geocoder

这时可以使用Google的API接口进行解析,API接口如下:

要使用Google Maps API进行定位并节省电量,您需要执行以下步骤:

1. 在GoogleMap开发平台上申请API密钥。

2. 使用setPriority()方法设置位置的精确度。您可以选择以下优先级之一:

- PRIORITY_HIGH_ACCURACY:提供最高精确度,但会消耗大量电池电量。

- PRIORITY_BALANCED_POWER_ACCURACY:提供准确的位置,同时进行了耗电优化。

- PRIORITY_LOW_POWER:主要依赖移动电话基站,以最小的电池电量消耗提供粗略的位置信息。

- PRIORITY_NO_POWER:被动地从其他应用接收位置。

3. 使用setInterval()和setFastestInterval()方法设置位置更新频率。对于后台位置收集,建议使用几秒钟的时间间隔。Android 8.0引入的后台位置更新限制将执行这些策略,但您的应用也应努力在Android 7.0或更低版本的设备上执行它们。

4. 使用setMaxWaitTime()方法设置延迟时间,传递的值通常比setInterval()方法中指定的时间间隔大几倍。这将有助于减少电池电量消耗。

以下是一个示例代码:

```java

import androidx.annotation.NonNull;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.Toast;

import com.google.android.gms.location.FusedLocationProviderClient;

import com.google.android.gms.location.LocationServices;

import com.google.android.gms.tasks.OnSuccessListener;

import com.google.android.gms.tasks.Task;

import com.google.maps.GeoApiContext;

import com.google.maps.Geocoder;

import com.google.maps.model.LatLng;

import com.google.maps.model.PlacemarkOptions;

public class MainActivity extends AppCompatActivity {

private FusedLocationProviderClient mFusedLocationClient;

private GeoApiContext mGeoApiContext;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);

mGeoApiContext = new GeoApiContext.Builder()

.apiKey("YOUR_API_KEY") // 请替换为您的API密钥

.build();

}

private void getLocationAndShowToast() {

mFusedLocationClient.getLastLocation().addOnSuccessListener(this, new OnSuccessListener() {

@Override

public void onSuccess(Location location) {

if (location != null) {

double latitude = location.getLatitude();

double longitude = location.getLongitude();

String address = getAddressFromLatLng(latitude, longitude);

Toast.makeText(MainActivity.this, "当前位置:" + address, Toast.LENGTH_SHORT).show();

} else {

Toast.makeText(MainActivity.this, "无法获取位置", Toast.LENGTH_SHORT).show();

}

}

});

}

private String getAddressFromLatLng(double latitude, double longitude) {

Geocoder geocoder = new Geocoder(this, Locale.getDefault());

Task> task = geocoder.getFromLocation(latitude, longitude, 1); // 只返回一个地址结果,可以根据需要调整此参数

try {

List

addresses = task.getResult();

if (addresses != null && addresses.size() > 0) {

Address address = addresses.get(0);

StringBuilder builder = new StringBuilder();

for (int i = 0; i < address.getMaxAddressLineIndex(); i++) {

builder.append(address.getAddressLine(i)).append("");

}

return builder.toString();

} else {

return "未找到地址";

}

} catch (Exception e) {

e.printStackTrace();

return "获取地址失败";

} finally {

task = null; // 注意释放资源,避免内存泄漏

}

}

}

```

如果您的应用无需即时位置更新,您应该将最大等待时间传递给setMaxWaitTime()方法,以牺牲一定的延迟时间来获取更多的数据和更高的电池效率。当使用地理围栏时,应用应向setNotificationResponsiveness()方法传递一个较大的值,以节省耗电量。建议设置五分钟或更大的值。

定位最佳实践包括:

1. 移除不必要的位置更新:造成不必要的电池电量消耗的一个常见原因是,当不再需要位置更新时,没有移除它们。例如,当某个Activity的onStart()或onResume()生命周期方法中包含requestlocationUpdates()调用,但在onPause()或onStop()生命周期方法中却没有相应的removeLocationUpdates()调用时,就会发生这种情况。

2. 设置合理的超时:为了防止电池电量消耗,应设置一个停止位置更新的合理超时。通过设置超时,可确保更新不会无限期地继续,并且在请求更新后未移除更新的情况下(例如由于代码错误),对应用起到保护作用。

3. 批量处理请求:对于所有非前台用例,将多个请求一起进行批处理。您可以使用setInterval()方法指定计算位置的时间间隔。然后使用setMaxWaitTime()方法设置位置传递给应用的时间间隔。传递给setMaxWaitTime()方法的值应是传递给setInterval()方法的值的倍数。例如,请考虑以下位置请求:

```kotlin

val request = LocationRequest()

request.setInterval(10 * 60 * 1000)

request.setMaxWaitTime(60 * 60 * 1000)

```

4. 使用被动位置更新:您的应用大约每15分钟计算一次位置。如果其他应用请求位置,则您的应用可在最多两分钟后获得这些信息。