系列文章目录
总篇:数据抓取:抓取手机设备各种数据
分篇(一):【数据抓取(一)】手机通讯录数据获取
分篇(二):数据抓取(二)&定位方案:地址信息的获取
分篇(二):数据抓取(三):免权限获取所有安装的应用程序信息(系统和非系统)
文章目录
前言
在安卓应用的使用中,定位是一个不可或缺的功能,当然成熟的公司一般都是使用高德SDK这类成熟,经得起考验的轮子来开发。那我们这些没钱 囊中羞涩,但是又想体验一把定位功能的友友们该怎么办,其实谷歌已经提供了一套成熟的api供我们使用。本文主要编写关于利用谷歌api获取经纬及详细地址的实现步骤,当然最后也会提供一份已经在使用的工具类给大家。
按照惯例,这里应该上展示图,但是怕被盒,就算了吧(狗头)
在总篇有展示demo,这里也可以下载下来看看(密码azdt):
一、实现步骤
我们要实现的不只是单纯的获取经纬度,而是在未开启服务时进入app后告知用户去开启服务,同时在用户同意定位权限获取后,即使获取定位信息加以保存。
所以可以分为三个步骤:
- 检查是否开启定位服务
- 动态获取定位权限
- 调用工具类获取定位,并在工具类及时保存定位信息
1.检查是否开启定位服务(通常会在启动Activity进行判断)
- 获取LocationManager服务
- 根据服务提供API
isProviderEnabled()
检查是否开启定位服务
// 判断是否开启定位服务
public boolean isLocationServiceEnabled1(Context context) {
LocationManager locationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE);
boolean isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
boolean isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
return !isGPSEnabled&&!isNetworkEnabled;
}
2.根据定位服务是否开启分支执行代码
已开启:检查是否获取定位权限(获取则初始化工具类,未获取则进行动态权限申请)
未开启:跳转定位服务页,开启定位服务
注意:本项目使用XXPermissions三方库简化动态权限申请流程,有兴趣可以了解下
// 检查是否开启GPS
private void checkGPS(){
if (!isLocationServiceEnabled(this)) {
if (checkPermission(this)){
LocationUtils.getInstance(this);
}else {
requestPermission(Permission.ACCESS_COARSE_LOCATION);
}
}else {
LocationUtils.getInstance(this);
}
}
// 检查是否申请权限
private boolean checkPermission(Context context){
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d("数据抓取:LocationUtils", "并未申请相关权限");
return false;
}
return true;
}
private void requestPermission(String...permission){
XXPermissions.with(this).permission(permission).request(new OnPermissionCallback() {
@Override
public void onGranted(@NonNull List<String> permissions, boolean allGranted) {
if (!allGranted) {
Toast.makeText(MainActivity.this,"获取权限成功", Toast.LENGTH_LONG).show();
LocationUtils.getInstance(this);
}
}
@Override
public void onDenied(@NonNull List<String> permissions, boolean doNotAskAgain) {
if (doNotAskAgain) {
Toast.makeText(MainActivity.this,"获取权限被禁止且不再询问",Toast.LENGTH_LONG).show();
// 如果是被永久拒绝就跳转到应用权限系统设置页面
XXPermissions.startPermissionActivity(MainActivity.this, permissions);
} else {
Toast.makeText(MainActivity.this,"获取权限失败",Toast.LENGTH_LONG).show();
}
}
});
}
3.工具类LocationUtils获取地址信息
在前面的步骤通过判断是否开启GPS定位服务以及是否申请定位动态权限两步骤之后, 我们使用LocationUtils.getInstance(this);
进行定位工具类初始化。
那么工具类LocationUtils又是如何获取地址信息?
- 先获取LocationManager服务
- 再通过服务的api
getProviders(true)
获取位置提供器 - 最后可以根据位置提供器获取Location
其中getLastKnownLocation()方法获取上一次定位服务获取的地址,如果一开始就开启GPS定位服务,此方法能够快速获取Location;
反之如果没有开启GPS定位服务,此处便设置requestLocationUpdates()监听器对定位进行监听。
//获取经纬度location
private void getLocation(Context context) {
//1.获取位置管理器
LocationManager locationManager = (LocationManager) mContext.getSystemService(LOCATION_SERVICE);
//2.获取位置提供器,GPS或是NetWork
List<String> providers = locationManager.getProviders(true);
Log.d("TAG", "打印位置提供器: ");
if (providers!=null&&providers.size()!=0){
for (String proviceder:providers){
if (locationManager.isProviderEnabled(proviceder)) {
Log.d("TAG", "longitude:gps_toOpen" );
// 需要检查权限,否则编译报错,想抽取成方法都不行,还是会报错。只能这样重复 code 了。
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d("数据抓取:LocationUtils", "并未申请相关权限");
return;
}
Location location = locationManager.getLastKnownLocation(proviceder);
Log.d("TAG", "getLocation: "+proviceder);
if (location!=null){
setLocation(location);
getAddress(context,location);
}
locationManager.requestLocationUpdates(proviceder, 5000, 3, new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
if (location!=null){
setLocation(location);
getAddress(context,location);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
locationManager.removeUpdates(this);
}
}
}
});
}
}
}
}
4.工具类LocationUtils通过SP存取经纬度
该工具类实现单例模式,在第一次初始化时就初始化了SharedPreferences.Editor和SharedPreferences对象。
@SuppressLint("StaticFieldLeak")
private volatile static LocationUtils uniqueInstance;
private Location location;
private final Context mContext;
private String tag = "";
private SharedPreferences preferences;
private SharedPreferences.Editor edit;
private LocationUtils(Context context) {
mContext = context;
preferences = mContext.getSharedPreferences("locaiton",MODE_PRIVATE);
edit = preferences.edit();
getLocation(context);
}
//实现单例
public static LocationUtils getInstance(Context context) {
if (uniqueInstance == null) {
synchronized (LocationUtils.class) {
if (uniqueInstance == null) {
uniqueInstance = new LocationUtils(context);
}
}
}
return uniqueInstance;
}
sp存方法已经在第三步骤调用,接下来贴上取方法
private void setLocation(Location location) {
this.location = location;
if(location!=null){
setData();
}
}
private void setData(){
edit.putString("longitude",String.valueOf(location.getLongitude()));
edit.putString("Latitude",String.valueOf(location.getLatitude()));
edit.apply();
}
public String getLongitude(){
Log.d("TAG", "splocation_get"+preferences.getString("longitude",""));
return preferences.getString("longitude","");
}
public String getLatitude(){
Log.d("TAG", "splocation_get"+preferences.getString("Latitude",""));
return preferences.getString("Latitude","");
}
二.地址信息类AddressInfo
在上述步骤我们便已经实行了位置获取,但是地址可用信息不止经纬度,我们数据抓取获取的信息也自然还有多个字段,下图展示:
字段名 | 描述 |
---|---|
gps_longitude | 经度 |
gps_latitude | 维度 |
gps_address_street | 街道 |
gps_address_province | 省份 |
gps_address_city | 城市 |
gps_address_country | 国家 |
gps_address_countryCode | 国家代码 |
AddressInfo代码如下:
public static class AddressInfo{
private String gps_longitude;// 经度
private String gps_latitude;// 维度
private String gps_address_street;// 街道
private String gps_address_province;// 省份
private String gps_address_city;// 城市
private String gps_address_country;// 国家
private String gps_address_countryCode;// 国家代码
public AddressInfo() {}
public String getGps_address_country() {
return gps_address_country;
}
public String getGps_address_countryCode() {
return gps_address_countryCode;
}
public String getGps_longitude() {
return gps_longitude;
}
public String getGps_latitude() {
return gps_latitude;
}
public String getGps_address_street() {
return gps_address_street;
}
public String getGps_address_province() {
return gps_address_province;
}
public String getGps_address_city() {
return gps_address_city;
}
}
那么我们又如何从Location对象中解析出如下数据呢?
通过Geocoder对象的getFromLocation()方法返回一个地址数组,通过对下标数据分析,装入地址信息类。
public String getAddress(Context context,Location location) {
List<Address> result;
try {
if (location != null) {
Geocoder gc = new Geocoder(context, Locale.getDefault());
// 返回一个地址数组,该数组试图描述给定纬度和经度周围的区域。
// 返回的地址应该根据提供给该类构造函数的区域设置进行本地化。
// 结果可以通过网络查找的方式获得,这个方法可能需要一些时间来返回,因此不应该在主线程上调用。
result = gc.getFromLocation(location.getLatitude(),
location.getLongitude(), 1);
for (int i = 0; i < result.size(); i++) {
address = result.get(i).toString();
}
addressInfo.gps_latitude = String.valueOf(result.get(0).getLatitude());
addressInfo.gps_longitude = String.valueOf(result.get(0).getLongitude());
addressInfo.gps_address_street = result.get(0).getAddressLine(2);
addressInfo.gps_address_province = result.get(0).getAdminArea();
addressInfo.gps_address_city = result.get(0).getLocality();
addressInfo.gps_address_country = result.get(0).getAddressLine(0);
addressInfo.gps_address_countryCode = result.get(0).getCountryCode();
}
} catch (Exception e) {
e.printStackTrace();
}
return address;
}
三、获取地址信息类(在确认定位服务和权限没问题后)
LocationUtils instance = LocationUtils.getInstance(this);
LocationUtils.AddressInfo addressInfo = instance.getAddressInfo();
最后附上完整工具类
package com.itaem.datacapture.Utils;// 2023/4/15
import static android.content.Context.LOCATION_SERVICE;
import static android.content.Context.MODE_PRIVATE;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import java.util.List;
import java.util.Locale;
/**
* 获取经纬度、位置工具类
*/
public class LocationUtils {
@SuppressLint("StaticFieldLeak")
private volatile static LocationUtils uniqueInstance;
private Location location;
private final Context mContext;
private String tag = "";
private SharedPreferences preferences;
private SharedPreferences.Editor edit;
private LocationUtils(Context context) {
mContext = context;
preferences = mContext.getSharedPreferences("locaiton",MODE_PRIVATE);
edit = preferences.edit();
getLocation(context);
}
//实现单例
public static LocationUtils getInstance(Context context) {
if (uniqueInstance == null) {
synchronized (LocationUtils.class) {
if (uniqueInstance == null) {
uniqueInstance = new LocationUtils(context);
}
}
}
return uniqueInstance;
}
//获取经纬度location
private void getLocation(Context context) {
//1.获取位置管理器
LocationManager locationManager = (LocationManager) mContext.getSystemService(LOCATION_SERVICE);
//2.获取位置提供器,GPS或是NetWork
List<String> providers = locationManager.getProviders(true);
Log.d("TAG", "打印位置提供器: ");
if (providers!=null&&providers.size()!=0){
for (String proviceder:providers){
if (locationManager.isProviderEnabled(proviceder)) {
Log.d("TAG", "longitude:gps_toOpen" );
// 需要检查权限,否则编译报错,想抽取成方法都不行,还是会报错。只能这样重复 code 了。
if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.d("数据抓取:LocationUtils", "并未申请相关权限");
return;
}
Location location = locationManager.getLastKnownLocation(proviceder);
Log.d("TAG", "getLocation: "+proviceder);
if (location!=null){
setLocation(location);
getAddress(context,location);
}
locationManager.requestLocationUpdates(proviceder, 5000, 3, new LocationListener() {
@Override
public void onLocationChanged(@NonNull Location location) {
if (location!=null){
setLocation(location);
getAddress(context,location);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
locationManager.removeUpdates(this);
}
}
}
});
}
}
}
}
private void setLocation(Location location) {
this.location = location;
if(location!=null){
setData();
}
}
private void setData(){
edit.putString("longitude",String.valueOf(location.getLongitude()));
edit.putString("Latitude",String.valueOf(location.getLatitude()));
edit.apply();
}
public String getLongitude(){
Log.d("TAG", "splocation_get"+preferences.getString("longitude",""));
return preferences.getString("longitude","");
}
public String getLatitude(){
Log.d("TAG", "splocation_get"+preferences.getString("Latitude",""));
return preferences.getString("Latitude","");
}
//获取经纬度
public Location showLocation() {
if (location!=null){
Log.d("TAG", "经纬度" + location.getLongitude());
Log.d("TAG", "经纬度" + location.getLatitude());
}
Log.d("TAG", "location_tag" + tag);
return location;
}
private AddressInfo addressInfo = new AddressInfo();
private String address = "";
public AddressInfo getAddressInfo() {
return addressInfo;
}
public String getAddress() {
return address;
}
//获取地址信息:城市、街道等信息
public String getAddress(Context context,Location location) {
List<Address> result;
try {
if (location != null) {
Geocoder gc = new Geocoder(context, Locale.getDefault());
// 返回一个地址数组,该数组试图描述给定纬度和经度周围的区域。
// 返回的地址应该根据提供给该类构造函数的区域设置进行本地化。
// 结果可以通过网络查找的方式获得,这个方法可能需要一些时间来返回,因此不应该在主线程上调用。
result = gc.getFromLocation(location.getLatitude(),
location.getLongitude(), 1);
for (int i = 0; i < result.size(); i++) {
address = result.get(i).toString();
}
addressInfo.gps_latitude = String.valueOf(result.get(0).getLatitude());
addressInfo.gps_longitude = String.valueOf(result.get(0).getLongitude());
addressInfo.gps_address_street = result.get(0).getAddressLine(2);
addressInfo.gps_address_province = result.get(0).getAdminArea();
addressInfo.gps_address_city = result.get(0).getLocality();
addressInfo.gps_address_country = result.get(0).getAddressLine(0);
addressInfo.gps_address_countryCode = result.get(0).getCountryCode();
}
} catch (Exception e) {
e.printStackTrace();
}
return address;
}
public static class AddressInfo{
private String gps_longitude;// 经度
private String gps_latitude;// 维度
private String gps_address_street;// 街道
private String gps_address_province;// 省份
private String gps_address_city;// 城市
private String gps_address_country;// 国家
private String gps_address_countryCode;// 国家代码
public AddressInfo() {}
public String getGps_address_country() {
return gps_address_country;
}
public String getGps_address_countryCode() {
return gps_address_countryCode;
}
public String getGps_longitude() {
return gps_longitude;
}
public String getGps_latitude() {
return gps_latitude;
}
public String getGps_address_street() {
return gps_address_street;
}
public String getGps_address_province() {
return gps_address_province;
}
public String getGps_address_city() {
return gps_address_city;
}
}
}
最后附上相关源码以及我手搓的开源库:
数据抓取:https://github.com/Android5730/DataCapture
如果有帮助到各位,可以给个star,给我一点信心去完善这个开源库
总结
本工具类已经步入正式使用了,基本上不会有问题,当然如果能够获取精细级别的定位权限,那么获取数据就更加完善,本项目只获取了大致位置权限,可能有部分机型无法获取全部的信息数据。
当然,如果有可以修改的建议和更好的方法,欢迎在评论区提出。