Android系统中模拟GPS位置
2016-08-23 18:34
459 查看
Android系统中提供了模拟GPS坐标的功能,可以很方便的帮助我们测试不同地理位置下应用中各个功能效果。
2017-03-04更新: onLocationChanged不回调的原因及解决方法
首先通过adb命令的修改方法:
注意如果链接了多个设备,adb命令需要添加
同样原理,可以使用Android SDK中的DDMS工具来修改GPS坐标;打开DDMS,可以看到Emulator面板中的GPS设置选项,
当打开模拟器运行时,这里就会变成可操作状态。
对于第三方模拟器,Genymotion也提供了这样的功能。不仅可以通过GenyMotion虚拟机的控制面板调节GPS参数(截图略),还可以通过Genymotion提供的Java API修改坐标点:
首先,需要到系统设置中“开发者选项”处,将其中的“允许模拟位置”打开。
然后我们需要编写一个简单的APP,来通过系统API修改GPS坐标值。
这涉及了一个权限:
在Eclipse环境下,直接修改AndroidManifest即可;
在Android Studio环境下,需要建立
然后来编写代码:
在LocationManager中,提供了一个接口
参数比较多,但接口的用途还是很清晰的——添加一个用于模拟位置的Provider。然后我们可以向这个Provider中设定模拟的位置:
不过这种方式设置坐标后,并没有触发GPS的onLocationChanged回调函数,目前还不清楚后面的原理。但可以使用
首先来看一下
因此在我们传入GPS_PROVIDER后,后续请求GPS更新,就会使用我们给定的位置了。
再来看向测试用的PROVIDER设定位置点坐标,其真正逻辑同样位于
其中mockProvider是从
继续追踪到
还是要回到LocationManagerService中:
最终是在这里发送了MSG_LOCATION_CHANGED消息,但为何没有触发onLocationChanged?还需要研究一下……
为何会有
回调函数的注册是在设置模拟位置点之后——即先调用
而调用
因此时间的单位一定要正确,否则做换算后会和设定的minTime查几个数量级,一直返回false。
另外可以看到,在这里首次调用时,
2017-03-04更新: onLocationChanged不回调的原因及解决方法
模拟器中模拟位置方法
模拟器与真机中的模拟手段有所差异,在模拟器中,可以通过Android的调试工具,或是命令行来修改GPS坐标点。首先通过adb命令的修改方法:
adb geo fix 116.813752 39.820015
注意如果链接了多个设备,adb命令需要添加
-s参数指定模拟器设备。
同样原理,可以使用Android SDK中的DDMS工具来修改GPS坐标;打开DDMS,可以看到Emulator面板中的GPS设置选项,
当打开模拟器运行时,这里就会变成可操作状态。
对于第三方模拟器,Genymotion也提供了这样的功能。不仅可以通过GenyMotion虚拟机的控制面板调节GPS参数(截图略),还可以通过Genymotion提供的Java API修改坐标点:
Context mContext = getActivity(); GenymotionManager genymotion = GenymotionManager.getGenymotionManager(mContext); genymotion.getGps() .setLatitude(39.820015) .setLongitude(116.813752);
真机中模拟位置方法
真机中模拟GPS位置的方法稍复杂一点。首先,需要到系统设置中“开发者选项”处,将其中的“允许模拟位置”打开。
然后我们需要编写一个简单的APP,来通过系统API修改GPS坐标值。
这涉及了一个权限:
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
在Eclipse环境下,直接修改AndroidManifest即可;
在Android Studio环境下,需要建立
src/debug这个文件夹,然后在里面创建一个debug版本的AndroidManifest.xml文件,才能添加这个权限。
然后来编写代码:
在LocationManager中,提供了一个接口
public void addTestProvider(String name, boolean requiresNetwork, boolean requiresSatellite, boolean hasMonetaryCost, boolean supportsAltitude, boolean supportsBearing, int accuracy)
参数比较多,但接口的用途还是很清晰的——添加一个用于模拟位置的Provider。然后我们可以向这个Provider中设定模拟的位置:
private LocationManager mLocManager; mLocManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); mLocManager.addTestProvider(LocationManager.GPS_PROVIDER, "requiresNetwork" == "", "requiresSatellite" == "", "requiresCell" == "", "hasMonetaryCost" == "", "supportsAltitude" == "", "supportsSpeed" == "", "supportsBearing" == "", Criteria.NO_REQUIREMENT, Criteria.ACCURACY_COARSE); // 创建新的Location对象,并设定必要的属性值 Location newLocation = new Location(LocationManager.GPS_PROVIDER); newLocation.setLatitude(39.820015); newLocation.setLongitude(116.813752); newLocation.setAccuracy(500); newLocation.setTime(System.currentTimeMillis()); // 这里一定要设置nonasecond单位的值,否则是没法持续收到监听的,原因见下文 newLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); // 开启测试Provider mLocManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true); mLocManager.setTestProviderStatus(LocationManager.GPS_PROVIDER, LocationProvider.AVAILABLE, null, System.currentTimeMillis()); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 3000, 10.0f, new MyLocationListener()); //需要注册监听完成后再进行模拟定位点的设置,否则接受不到回调 //可以用handler postDelay方法 // 设置最新位置,一定要在requestLocationUpdate完成后进行,才能收到监听 mLocManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, newLocation);
不过这种方式设置坐标后,并没有触发GPS的onLocationChanged回调函数,目前还不清楚后面的原理。但可以使用
getLastKnownLocation接口获取:
Location tmp = mLocManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); Log.i(TAG, tmp.getLatitude() + ", " + tmp.getLongitude());
真机模拟位置原理
在真机模拟位置点的过程中,我们调用了很多TestProvider有关的API。
首先来看一下
addTestProvider函数,其最终调用到的逻辑位于
com.android.server.LocationManagerService中
@Override public void addTestProvider(String name, ProviderProperties properties, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } // 由此可以看到无法传入PASSIVE_PROVIDER作为测试使用 if (LocationManager.PASSIVE_PROVIDER.equals(name)) { throw new IllegalArgumentException("Cannot mock the passive location provider"); } long identity = Binder.clearCallingIdentity(); synchronized (mLock) { // remove the real provider if we are replacing GPS or network provider // 在这里比较传入的Provider名称,如果和GPS_PROVIDER相同,就将真正的GPS_PROVIDER移除掉 if (LocationManager.GPS_PROVIDER.equals(name) || LocationManager.NETWORK_PROVIDER.equals(name) || LocationManager.FUSED_PROVIDER.equals(name)) { LocationProviderInterface p = mProvidersByName.get(name); if (p != null) { removeProviderLocked(p); } } // 会向mMockProviders中添加这一测试用的provider,以便后续使用 addTestProviderLocked(name, properties); updateProvidersLocked(); } Binder.restoreCallingIdentity(identity); }
因此在我们传入GPS_PROVIDER后,后续请求GPS更新,就会使用我们给定的位置了。
再来看向测试用的PROVIDER设定位置点坐标,其真正逻辑同样位于
com.android.server.LocationManagerService中:
@Override public void setTestProviderLocation(String provider, Location loc, String opPackageName) { if (!canCallerAccessMockLocation(opPackageName)) { return; } synchronized (mLock) { MockProvider mockProvider = mMockProviders.get(provider); if (mockProvider == null) { throw new IllegalArgumentException("Provider \"" + provider + "\" unknown"); } // clear calling identity so INSTALL_LOCATION_PROVIDER permission is not required long identity = Binder.clearCallingIdentity(); // 向mockProvider传入位置点 mockProvider.setLocation(loc); Binder.restoreCallingIdentity(identity); } }
其中mockProvider是从
mMockProviders中得到的,在
addTestProvider时,
addTestProviderLocked函数会向其添加我们设定的provider。
继续追踪到
setLocation函数,位于
com.android.server.location.MockProvider中:
public void setLocation(Location l) { mLocation.set(l); mHasLocation = true; if (mEnabled) { try { // 调用LocationManagerService中的函数,而非应用层可获取到的LocationManager mLocationManager.reportLocation(mLocation, false); } catch (RemoteException e) { Log.e(TAG, "RemoteException calling reportLocation"); } } }
还是要回到LocationManagerService中:
@Override public void reportLocation(Location location, boolean passive) { checkCallerIsProvider(); if (!location.isComplete()) { Log.w(TAG, "Dropping incomplete location: " + location); return; } mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location); // 向Handler发送MSG_LOCATION_CHANGED消息 Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location); m.arg1 = (passive ? 1 : 0); mLocationHandler.sendMessageAtFrontOfQueue(m); }
最终是在这里发送了MSG_LOCATION_CHANGED消息,但为何没有触发onLocationChanged?还需要研究一下……
onLocationChanged
没有调用的原因
为何会有onLocationChanged没有回调的情况?原因有如下两点:
回调函数的注册是在设置模拟位置点之后——即先调用
setTestProviderLocation方法,再进行
requestLocationUpdate操作,这时就收不到结果了。将两者顺序调换即可,延迟一些调用
setTestProviderLocation以确保监听注册成功后进行位置更新。
Location的
setElapsedRealtimeNanos方法如其字面意义,一定要设定nanosecond单位的时间值。这是因为系统判断时操作为(
com.android.server.LocationManagerService中):
private static boolean shouldBroadcastSafe( Location loc, Location lastLoc, UpdateRecord record, long now) { // Always broadcast the first update if (lastLoc == null) { return true; } // Check whether sufficient time has passed long minTime = record.mRequest.getFastestInterval(); long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos()) / NANOS_PER_MILLI; // delta是通过nanosecond计算再换算成millsecond的 if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) { return false; } ...... }
而调用
onLocationChanged前,判断的逻辑为:
if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now))
因此时间的单位一定要正确,否则做换算后会和设定的minTime查几个数量级,一直返回false。
另外可以看到,在这里首次调用时,
lastLoc为空。因此如果你的时间设定不对,但是位置监听的注册顺序正确的话,会只收到一次
onLocationChanged调用。
相关文章推荐
- Android JBox2d,Box2d,opengl es+算法 粒子系统水流的模拟体会
- Android发送信息模拟系统
- Android 系统下模拟触点击的功能与权限
- android开发中模拟系统内存不足 应用释放的情况
- Android模拟、实现、触发系统按键事件的方法
- PopupWindow进阶用法——android上实现类似UCweb的自定义menu,完全模拟系统事件
- Android用代码模拟系统物理按键长按操作(类似按键精灵)
- Android 模拟系统事件(二)
- Android 模拟系统事件(一)
- Android实现模拟登陆正方系统查成绩
- Android中用Java代码模拟鼠标指针(可显示于整个系统界面之上)
- Android 模拟系统事件(三)
- Android 模拟系统事件(二)
- Android 模拟系统事件(三)
- 安卓Android系统超级终端(终端模拟…
- Android JBox2d,Box2d,opengl es+算法 粒子系统水流的模拟体会
- Android 模拟系统事件(一)
- android系统消息之模拟
- 近期小结--Android系统时间获取及AVD模拟GPS相关调试
- Android系统模拟位置的使用方法