标签:
转载请标明出处:一片枫叶的专栏
本文我们将讲解允许模拟位置在Android M下的坑。做地图类应用的同学应该都知道为了避免软件模拟位置影响正常流程的进行我们一般都会判断用户手机是否打开了模拟位置设置,若打开了则终止用户流程,提醒用户关闭模拟位置设置。在android系统的开发者选项中有一个模拟位置的选项,其作用是允许用户通过代码模拟设备的当前位置,比如地图类应用需要测试在外地的使用情况,通过开启此项选项可以通过代码模拟位置,具体可参考我的:Android中的开发者选项
允许模拟位置的设置选项在手机的开发者选项设置中:
产品实践:
在我们的产品下单用车中有一个取车的环节,通过手机控制开车门,而这个时候会判断当前手机是否打开的模拟位置的功能,若打开则,提示用户并关闭该模拟位置的功能。(若是允许用户打开模拟位置功能,则恶意用户可以通过第三方的模拟位置App修改手机的定位信息,进而影响我们App的定位信息,当需要用户在中关村还车时,在十里堡就可以通过模拟位置屏蔽这个操作了)
判断用户是否打开模拟位置的代码如下:
/**
* 判断是否打开了允许虚拟位置,如果打开了 则弹窗让他去关闭
*/
public static boolean isAllowMockLocation(final Activity context) {
/**
* 判断用户是否开启了模拟位置功能
*/
boolean isOpen = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0;
if (isOpen) {
Config.showTiplDialog(context, null, "定位失败,需要关闭【允许模拟位置】功能后才能使用友友用车查看附近的车辆。", "去设置", new View.OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS));
}
});
}
return isOpen;
}
在开车门页面中,点击开车们按钮,判断用户是否打开了模拟位置开关,若打开则提示用户关闭:
这时候点击去设置按钮,则会跳转到开发者选项中,并允许用户关闭开发者选项。
出现的问题:
但是在Android M的机型中判断逻辑出现了问题,部分三星手机打开车载模式的话,这时候再次点击开车门的话,上述代码会判断出用户开启了模拟位置功能,这时候就会阻塞用户的操作,并指引用户关闭模拟位置开关。但是Android M手机上已经没有了允许模拟位置的设置开关了,取而代之的是选择模拟位置信息应用设置按钮。
按道理来说,即便用户开启了车载模式这时候通过上述判断是否开启模拟位置的代码返回值应该是false(没有打开模拟位置),但是这时候用于弹出了定位失败,需要关闭模拟位置的弹窗,说明通过代码判断用户是否打开了模拟位置返回了true。
后来经过排查得知像这种允许模拟位置等信息都是保存在系统底层的一个数据库中,而我们的判断代码返回了true,则说明用户底层的允许模拟位置数据库值为true。
但是这时候Android M中由于已经不存在允许模拟位置取而代之的是选择模拟位置信息应用设置,相当于这是两个设置底层数据库变量的开关了,而我们的代码判断的是允许模拟位置的数据库值,在Android M中并没有更改允许模拟位置的开关,所以这样就没办法更改Android M下的允许模拟位置的值了。但是Android M上不是使用了选择模拟信息应用设置么?这又是什么鬼呢?
在Android M中已经没有了允许模拟位置的开发,取而代之的是:选择模拟位置信息应用:
在Android M下默认的应用是无法显示在选择模拟信息应用中的,需要经过如下的操作才可以:
这样经过设置之后我们的应用信息就可以显示在模拟位置中了,其中经过测试当为我们的应用设置了模拟位置信息之后,其只可以影响我们自己应用的定位信息,而无法影响其他应用的定位信息。这也算android系统解决的模拟位置信息的bug吧。
允许模拟位置的BUG:
在Android M之前如果我们为自己的应用选择了允许模拟位置,则可以通过一个应用的模拟位置操作影响其他应用的定位信息,而这种操作Google认为是不正确的。模拟位置信息的初衷是为了方便App的调试操作,而当这种操作影响其他应用时就可以做一些黑操作了。
比如通过模拟位置,在使用滴滴的时候模拟位置信息抢单等等。
所以为了解决这个问题,android M中升级了允许模拟位置设置,取而代之的是选择模拟位置信息应用设置,通过设置这个选项,只能影响当前应用,而不能影响其他应用的定位信息。
比如,这时候我们在通过一些App模拟当前手机的定位信息,这时候就不可以影响滴滴的定位信息了。
执行Android M下的模拟位置操作:
在debug-AndroidManifest中添加模拟位置的权限权限
在开发者选项,选择模拟位置信息应用中,选择自身App
通过代码模拟位置
public class RunnableMockLocation implements Runnable {
@Override
public void run() {
try {
// 模拟位置(addTestProvider成功的前提下)
String providerStr = LocationManager.GPS_PROVIDER;
Location mockLocation = new Location(providerStr);
mockLocation.setLatitude(22); // 维度(度)
mockLocation.setLongitude(113); // 经度(度)
mockLocation.setAltitude(30); // 高程(米)
mockLocation.setBearing(180); // 方向(度)
mockLocation.setSpeed(10); //速度(米/秒)
mockLocation.setAccuracy(0.1f); // 精度(米)
mockLocation.setTime(new Date().getTime()); // 本地时间
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
locationManager.setTestProviderLocation(providerStr, mockLocation);
} catch (Exception e) {
// 防止用户在软件运行过程中关闭模拟位置或选择其他应用
stopMockLocation();
}
}
}
//位置监听
private LocationListener locationListener=new LocationListener() {
/**
* 位置信息变化时触发
*/
public void onLocationChanged(Location location) {
double lat = location.getLatitude();
double lot = location.getLongitude();
String str= "Latitude"+lat+"\r\nLongitude:"+lot;
textView.setText(str);
Log.i(TAG, "时间:"+location.getTime());
Log.i(TAG, "经度:"+location.getLongitude());
Log.i(TAG, "纬度:"+location.getLatitude());
Log.i(TAG, "海拔:"+location.getAltitude());
}
/**
* GPS状态变化时触发
*/
public void onStatusChanged(String provider, int status, Bundle extras) {
switch (status) {
//GPS状态为可见时
case LocationProvider.AVAILABLE:
Log.i(TAG, "当前GPS状态为可见状态");
break;
//GPS状态为服务区外时
case LocationProvider.OUT_OF_SERVICE:
Log.i(TAG, "当前GPS状态为服务区外状态");
break;
//GPS状态为暂停服务时
case LocationProvider.TEMPORARILY_UNAVAILABLE:
Log.i(TAG, "当前GPS状态为暂停服务状态");
break;
}
}
/**
* GPS开启时触发
*/
public void onProviderEnabled(String provider) {
}
/**
* GPS禁用时触发
*/
public void onProviderDisabled(String provider) {
}
};
我们打开我们的其它应用发现其定位信息并未受到影响。也就是说android M中的选择模拟位置信息应用与Android M以下的手机中的允许模拟位置区别。
允许模拟位置与选择模拟位置信息应用的区别:
允许模拟位置,可以通过模拟位置影响其他应用的定位信息
选择模拟位置信息应用只能影响当前应用的定位信息
允许模拟位置与选择模拟位置信息应用最终在系统中保存在两个数据库表中,且相互不影响
而我们的手机中判断的是允许模拟位置开关,在android M中若判断打开了这个开关,但是系统已经关闭这个开关的设置操作,所以我们只需要屏蔽这个值即可。
最后的解决方案:
/**
* 判断是否打开了允许虚拟位置,如果打开了 则弹窗让他去关闭
*/
public static boolean isAllowMockLocation(final Activity context) {
boolean isOpen = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ALLOW_MOCK_LOCATION, 0) != 0;
/**
* 该判断API是androidM以下的API,由于Android M中已经没有了关闭允许模拟位置的入口,所以这里一旦检测到开启了模拟位置,并且是android M以上,则
* 默认设置为未有开启模拟位置
*/
if (isOpen && Build.VERSION.SDK_INT > 22) {
isOpen = false;
}
if (isOpen) {
Config.showTiplDialog(context, null, "定位失败,需要关闭【允许模拟位置】功能后才能使用友友用车查看附近的车辆。", "去设置", new View.OnClickListener() {
@Override
public void onClick(View view) {
context.startActivity(new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS));
}
});
}
return isOpen;
}
也就是说,当我们判断出当前设备打开允许模拟位置时,在判断一下手机系统的版本,若为Android M以及以上,就屏蔽不管。可能部分同学会问那么android M上的选择模拟位置信息应用有影响么?答案是否定的,由于我们的App没有添加允许模拟位置的权限,所以其根本不会出现在选择模拟位置应用列表,进而不会执行模拟位置的操作。
所以最终的解决方案就是,检测设备是否开启了模拟位置选项,若开启了,则判断当前设备是否为Android M即以上,若是,则屏蔽不管,否则阻塞用户操作,引导用户关闭模拟位置选项。
Android tips(十)-->允许模拟位置在Android M下的坑
标签:
原文地址:http://blog.csdn.net/qq_23547831/article/details/52033726