最近有一个需求,需要用GPS的模拟功能,研究了一下源码。
使用流程
- 在开发者模式中的“选择模拟位置信息应用”选项,选择我们要模拟的app
- 在我们的app中调用LocationManager.setTestProviderLocation()这个接口
开发者模式源码分析
看了开发者模式的源码,路径在/packages/apps/Settings/src/com/android/settings/development和/frameworks/base/packages/SettingsLib
搜索“选择模拟位置信息应用”,在SettingsLib中strings.xml的找到该声明
进一步搜索关键字“mock_location_app”,发现在MockLocationAppPreferenceController.java中使用了,所以直接看到这个类。发现勾选我们要模拟的app后主要是调用如下代码
private void writeMockLocation(String mockLocationAppName) {
removeAllMockLocations();
// Enable the app op of the new mock location app if such.
if (!TextUtils.isEmpty(mockLocationAppName)) {
try {
final ApplicationInfo ai = mPackageManager.getApplicationInfo(
mockLocationAppName, PackageManager.MATCH_DISABLED_COMPONENTS);
mAppsOpsManager.setMode(AppOpsManager.OP_MOCK_LOCATION, ai.uid,
mockLocationAppName, AppOpsManager.MODE_ALLOWED);
} catch (PackageManager.NameNotFoundException e) {
/* ignore */
}
}
}
GPS信号模拟源码分析
看到LocationManager.java的setTestProviderLocation方法
首先判断了Location是否是完整的,若不完整,则填充默认值。接着通过ILocationManager.aidl调用到LocationManagerService。
在最后面调用了MockProvider的setLocation方法,看到MockProvider.java
很简单,直接调用了LocationManagerService的reportLocation方法,就这样把GPS信息回调上去了。
后续调用
mLocationHandler.sendMessageAtFrontOfQueue(m);
handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
handleLocationChangedLocked(myLocation, passive);
receiver.callLocationChangedLocked(notifyLocation);
就这样又回到了LocationManager,调用mListener.onLocationChanged(new Location(location))回调到app
APP例子
public class MainActivity extends Activity implements View.OnClickListener {
private static final String TAG = "GPS_Test";
private LocationManager locationManager;
private Button btn_set;
private AppOpsManager mAppsOpsManager;
private PackageManagerWrapper mPackageManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_set = findViewById(R.id.btn_set);
btn_set.setOnClickListener(this);
// 调用以下方法后,就不需要在开发者模式中勾选我们的模拟位置信息的app
mAppsOpsManager = (AppOpsManager) this.getSystemService(Context.APP_OPS_SERVICE);
mPackageManager = new PackageManagerWrapper(this.getPackageManager());
writeMockLocation();
try {
Thread.sleep(1000); //延时1秒,确保开发者模式的“选择模拟位置信息应用”选项选择了我们的app
} catch (InterruptedException e) {
e.printStackTrace();
}
locationManager = (LocationManager) this.getSystemService(LOCATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// Android M Permission check
List<String> permissionLists = new ArrayList<>();
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
permissionLists.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
permissionLists.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.MANAGE_APP_OPS_MODES) != PackageManager.PERMISSION_GRANTED) {
permissionLists.add(Manifest.permission.MANAGE_APP_OPS_MODES);
}
if (!permissionLists.isEmpty()) {//说明肯定有拒绝的权限
ActivityCompat.requestPermissions(this, permissionLists.toArray(new String[permissionLists.size()]), 1);
} else {
Log.d(TAG, "ALL have permissions");
}
}
// 添加并启动GpsProvider
locationManager.addTestProvider(LocationManager.GPS_PROVIDER, false, false,
false, false, true,
false, false, 0, 5);
// 开启测试Provider
locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true);
// 设置监听
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
}
private void writeMockLocation() {
try {
String mockLocationAppName = "android.com.gpstest";
final ApplicationInfo ai = mPackageManager.getApplicationInfo(
mockLocationAppName, PackageManager.MATCH_DISABLED_COMPONENTS);
mAppsOpsManager.setMode(AppOpsManager.OP_MOCK_LOCATION, ai.uid,
mockLocationAppName, AppOpsManager.MODE_ALLOWED);
} catch (PackageManager.NameNotFoundException e) {
/* ignore */
}
}
protected final LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.d(TAG, " onLocationChanged " + location.toString());
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d(TAG, "onStatusChanged " + provider);
}
@Override
public void onProviderEnabled(String provider) {
Log.d(TAG, " onProviderEnabled " + provider);
}
@Override
public void onProviderDisabled(String provider) {
Log.d(TAG, " onProviderDisabled " + provider);
}
};
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_set:
// 创建新的Location对象,并设定必要的属性值
Location mockLocation = new Location(LocationManager.GPS_PROVIDER);
mockLocation.setLatitude(39.820036);
mockLocation.setLongitude(116.813751);
mockLocation.setAccuracy(501);
mockLocation.setTime(System.currentTimeMillis());
mockLocation.setBearing(1.2f);
mockLocation.setSpeed(10.8f);
mockLocation.setVerticalAccuracyMeters(1.5f);
mockLocation.setBearingAccuracyDegrees(3.3f);
// 这里一定要设置nonasecond单位的值,否则是没法持续收到监听的
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
mockLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
// 设置最新位置,一定要在requestLocationUpdate完成后进行,才能收到监听
locationManager.setTestProviderLocation(LocationManager.GPS_PROVIDER, mockLocation);
break;
}
}
}
<!--AndroidManifest.xml-->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.com.gpstest">
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.MANAGE_APP_OPS_MODES" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>