您的位置:首页 > 移动开发 > Android开发

Android系统中模拟GPS位置

2016-08-23 18:34 459 查看
Android系统中提供了模拟GPS坐标的功能,可以很方便的帮助我们测试不同地理位置下应用中各个功能效果。

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 gps