近期一个项目用到了该功能,因此进行了比较详细的研究,在这里整理一下。
首先指的一提的是,Android Q版本之后,定位权限有一个改动就是,如果是后台服务想要获取地理位置信息的话,需要申请新的权限ACCESS_BACKGROUND_LOCATION ,如果不想申请该权限进行定位的话,需要将服务切换为前台服务,如何切换的代码在github地址上,附在本文最后。
接下来进行位置监听的代码分析:
想要获取后台位置监听的话,可以直接调用LocationManager的requestLocationUpdates方法,通常来说直接调用的是:
public void requestLocationUpdates(String provider, long minTime, float minDistance,
LocationListener listener, Looper looper) {
checkProvider(provider);
checkListener(listener);
LocationRequest request = LocationRequest.createFromDeprecatedProvider(
provider, minTime, minDistance, false);
requestLocationUpdates(request, listener, looper, null);
}
刚开始进行参数的非空校验:
private static void checkProvider(String provider) {
if (provider == null) {
throw new IllegalArgumentException("invalid provider: " + provider);
}
}
之后调用createFromDeprecatedProvider生成一个request,该部分代码很简单
public static LocationRequest createFromDeprecatedProvider(String provider, long minTime,
float minDistance, boolean singleShot) {
if (minTime < 0) minTime = 0;
if (minDistance < 0) minDistance = 0;
int quality;
if (LocationManager.PASSIVE_PROVIDER.equals(provider)) {
quality = POWER_NONE;
} else if (LocationManager.GPS_PROVIDER.equals(provider)) {
quality = ACCURACY_FINE;
} else {
quality = POWER_LOW;
}
LocationRequest request = new LocationRequest()
.setProvider(provider)
.setQuality(quality)
.setInterval(minTime)
.setFastestInterval(minTime)
.setSmallestDisplacement(minDistance);
if (singleShot) request.setNumUpdates(1);
return request;
}
创建好request调用requestLocationUpdates:
private void requestLocationUpdates(LocationRequest request, LocationListener listener,
Looper looper, PendingIntent intent) {
String packageName = mContext.getPackageName();
// wrap the listener class
ListenerTransport transport = wrapListener(listener, looper);
try {
mService.requestLocationUpdates(request, transport, intent, packageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
其中wrapListener的实现如下:
private ListenerTransport wrapListener(LocationListener listener, Looper looper) {
if (listener == null) return null;
synchronized (mListeners) {
ListenerTransport transport = mListeners.get(listener);
if (transport == null) {
transport = new ListenerTransport(listener, looper);
}
mListeners.put(listener, transport);
return transport;
}
}
mListeners是维护listener的一个hashmap:
// Map from LocationListeners to their associated ListenerTransport objects
private HashMap<LocationListener,ListenerTransport> mListeners =
new HashMap<LocationListener,ListenerTransport>();
如果该listener之前未注册过的话,会生成一个新的ListenerTransport:
ListenerTransport(LocationListener listener, Looper looper) {
mListener = listener;
if (looper == null) {
mListenerHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
_handleMessage(msg);
}
};
} else {
mListenerHandler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
_handleMessage(msg);
}
};
}
}
这里会进行loop的判断,一般来说,我们不传入loop时,会生成一个指向当前线程消息队列的handler,接下来看下_handleMessage的实现:
private void _handleMessage(Message msg) {
switch (msg.what) {
case TYPE_LOCATION_CHANGED:
Location location = new Location((Location) msg.obj);
mListener.onLocationChanged(location);
break;
case TYPE_STATUS_CHANGED:
Bundle b = (Bundle) msg.obj;
String provider = b.getString("provider");
int status = b.getInt("status");
Bundle extras = b.getBundle("extras");
mListener.onStatusChanged(provider, status, extras);
break;
case TYPE_PROVIDER_ENABLED:
mListener.onProviderEnabled((String) msg.obj);
break;
case TYPE_PROVIDER_DISABLED:
mListener.onProviderDisabled((String) msg.obj);
break;
}
try {
mService.locationCallbackFinished(this);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
因此实际上位置信息是通过向注册监听器的线程发送一个message来实现,如果我们想在子线程注册一个监听的话,需要维护这个looper:
Looper.prepare();
Looper.loop();
而如果想在子线程取消这个监听器的话,同样也可以使用这个looper来实现,只需要发送一个取消监听器的message就可以实现该功能了:
handler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
locationManager.removeUpdates(gpsListener);
break;
}
}
};
该ListenerTransport生成之后,通过aidl接口发送到LocatonManagerService:
mService.requestLocationUpdates(request, transport, intent, packageName);
@Override
public void requestLocationUpdates(LocationRequest request, ILocationListener listener,
PendingIntent intent, String packageName) {
if (request == null) request = DEFAULT_LOCATION_REQUEST;
checkPackageName(packageName);
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
request.getProvider());
WorkSource workSource = request.getWorkSource();
if (workSource != null && !workSource.isEmpty()) {
checkDeviceStatsAllowed();
}
boolean hideFromAppOps = request.getHideFromAppOps();
if (hideFromAppOps) {
checkUpdateAppOpsAllowed();
}
boolean callerHasLocationHardwarePermission =
mContext.checkCallingPermission(android.Manifest.permission.LOCATION_HARDWARE)
== PERMISSION_GRANTED;
LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel,
callerHasLocationHardwarePermission);
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
// providers may use public location API's, need to clear identity
long identity = Binder.clearCallingIdentity();
try {
// We don't check for MODE_IGNORED here; we will do that when we go to deliver
// a location.
checkLocationAccess(pid, uid, packageName, allowedResolutionLevel);
synchronized (mLock) {
Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid,
packageName, workSource, hideFromAppOps);
requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName);
}
} finally {
Binder.restoreCallingIdentity(identity);
}
}
可以看到这里做了很多校验,首先是包名的校验:
private void checkPackageName(String packageName) {
if (packageName == null) {
throw new SecurityException("invalid package name: " + packageName);
}
int uid = Binder.getCallingUid();
String[] packages = mPackageManager.getPackagesForUid(uid);
if (packages == null) {
throw new SecurityException("invalid UID " + uid);
}
for (String pkg : packages) {
if (packageName.equals(pkg)) return;
}
throw new SecurityException("invalid package name: " + packageName);
}
getCallerAllowedResolutionLevel来校验当前的权限获取情况:
/**
* Returns the resolution level allowed to the caller
*
* @return resolution level allowed to caller
*/
private int getCallerAllowedResolutionLevel() {
return getAllowedResolutionLevel(Binder.getCallingPid(), Binder.getCallingUid());
}
/**
* Returns the resolution level allowed to the given PID/UID pair.
*
* @param pid the PID
* @param uid the UID
* @return resolution level allowed to the pid/uid pair
*/
private int getAllowedResolutionLevel(int pid, int uid) {
if (mContext.checkPermission(android.Manifest.permission.ACCESS_FINE_LOCATION,
pid, uid) == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_FINE;
} else if (mContext.checkPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
pid, uid) == PERMISSION_GRANTED) {
return RESOLUTION_LEVEL_COARSE;
} else {
return RESOLUTION_LEVEL_NONE;
}
}
checkResolutionLevelIsSufficientForProviderUse来校验申请的监听器是否超出了当前所有权限的限制:
/**
* Throw SecurityException if specified resolution level is insufficient to use the named
* location provider.
*
* @param allowedResolutionLevel resolution level allowed to caller
* @param providerName the name of the location provider
*/
private void checkResolutionLevelIsSufficientForProviderUse(int allowedResolutionLevel,
String providerName) {
int requiredResolutionLevel = getMinimumResolutionLevelForProviderUse(providerName);
if (allowedResolutionLevel < requiredResolutionLevel) {
switch (requiredResolutionLevel) {
case RESOLUTION_LEVEL_FINE:
throw new SecurityException("\"" + providerName + "\" location provider " +
"requires ACCESS_FINE_LOCATION permission.");
case RESOLUTION_LEVEL_COARSE:
throw new SecurityException("\"" + providerName + "\" location provider " +
"requires ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission.");
default:
throw new SecurityException("Insufficient permission for \"" + providerName +
"\" location provider.");
}
}
}
接下来会获取当前的WorkSource和来校验如下两个权限:
/**
* Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages
* for battery).
*/
private void checkDeviceStatsAllowed() {
mContext.enforceCallingOrSelfPermission(
// 更新设备状态
android.Manifest.permission.UPDATE_DEVICE_STATS, null);
}
private void checkUpdateAppOpsAllowed() {
// 更新应用权限状态
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.UPDATE_APP_OPS_STATS, null);
}
经过多种权限检查之后,生产reciver:
private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent,
int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) {
if (intent == null && listener == null) {
throw new IllegalArgumentException("need either listener or intent");
} else if (intent != null && listener != null) {
throw new IllegalArgumentException("cannot register both listener and intent");
} else if (intent != null) {
checkPendingIntent(intent);
return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps);
} else {
return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps);
}
}
这里从listener不为null,intent为null的情况分析:
private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid,
String packageName, WorkSource workSource, boolean hideFromAppOps) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
receiver = new Receiver(listener, null, pid, uid, packageName, workSource,
hideFromAppOps);
try {
receiver.getListener().asBinder().linkToDeath(receiver, 0);
} catch (RemoteException e) {
Slog.e(TAG, "linkToDeath failed:", e);
return null;
}
mReceivers.put(binder, receiver);
}
return receiver;
}
Reciver是LocationManagerServiced的一个内部类,与listener是一一对应的,成员变量如下:
final Identity mIdentity;
final int mAllowedResolutionLevel; // resolution level allowed to receiver
final ILocationListener mListener;
final PendingIntent mPendingIntent;
final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
final Object mKey;
final HashMap<String, UpdateRecord> mUpdateRecords = new HashMap<>();
// True if app ops has started monitoring this receiver for locations.
boolean mOpMonitoring;
// True if app ops has started monitoring this receiver for high power (gps) locations.
boolean mOpHighPowerMonitoring;
int mPendingBroadcasts;
PowerManager.WakeLock mWakeLock;
接下来调用requestLocationUpdatesLocked:
private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
int pid, int uid, String packageName) {
// Figure out the provider. Either its explicitly request (legacy use cases), or
// use the fused provider
if (request == null) request = DEFAULT_LOCATION_REQUEST;
String name = request.getProvider();
if (name == null) {
throw new IllegalArgumentException("provider name must not be null");
}
LocationProviderInterface provider = mProvidersByName.get(name);
if (provider == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
UpdateRecord record = new UpdateRecord(name, request, receiver);
if (D) {
Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
+ " " + name + " " + request + " from " + packageName + "(" + uid + " "
+ (record.mIsForegroundUid ? "foreground" : "background")
+ (isThrottlingExemptLocked(receiver.mIdentity)
? " [whitelisted]" : "") + ")");
}
UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
if (oldRecord != null) {
oldRecord.disposeLocked(false);
}
boolean isProviderEnabled = isAllowedByUserSettingsLocked(name, uid, mCurrentUserId);
if (isProviderEnabled) {
applyRequirementsLocked(name);
} else {
// Notify the listener that updates are currently disabled
receiver.callProviderEnabledLocked(name, false);
}
// Update the monitoring here just in case multiple location requests were added to the
// same receiver (this request may be high power and the initial might not have been).
receiver.updateMonitoring(true);
}
requestLocationUpdatesLocked这个函数比较复杂,明早起来后进行详细分析。
先说下这个receiver最后是如何回调到client的:
当硬件检测到有位置更新之后,最初调用的是 GpsLocationProvider.cpp 中的 location_callback 函数
代码的实现在GpsLocationProvider.java中:
/**
* called from native code to update our position.
*/
private void reportLocation(int flags, double latitude, double longitude, double altitude,
float speed, float bearing, float accuracy, long timestamp) {
if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude +
" timestamp: " + timestamp);
synchronized (mLocation) {
mLocationFlags = flags;
if ((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
mLocation.setLatitude(latitude);
mLocation.setLongitude(longitude);
mLocation.setTime(timestamp);
// It would be nice to push the elapsed real-time timestamp
// further down the stack, but this is still useful
mLocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
if ((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) {
mLocation.setAltitude(altitude);
} else {
mLocation.removeAltitude();
}
if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) {
mLocation.setSpeed(speed);
} else {
mLocation.removeSpeed();
}
if ((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) {
mLocation.setBearing(bearing);
} else {
mLocation.removeBearing();
}
if ((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) {
mLocation.setAccuracy(accuracy);
} else {
mLocation.removeAccuracy();
}
mLocation.setExtras(mLocationExtras);
try {
// private final ILocationManager mILocationManager;
mILocationManager.reportLocation(mLocation, false);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling reportLocation");
}
}
mLastFixTime = System.currentTimeMillis();
// report time to first fix
if (mTimeToFirstFix == 0 && (flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) {
mTimeToFirstFix = (int)(mLastFixTime - mFixRequestTime);
if (DEBUG) Log.d(TAG, "TTFF: " + mTimeToFirstFix);
// notify status listeners
mListenerHelper.onFirstFix(mTimeToFirstFix);
}
if (mSingleShot) {
stopNavigating();
}
if (mStarted && mStatus != LocationProvider.AVAILABLE) {
// we want to time out if we do not receive a fix
// within the time out and we are requesting infrequent fixes
if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mFixInterval < NO_FIX_TIMEOUT) {
mAlarmManager.cancel(mTimeoutIntent);
}
// send an intent to notify that the GPS is receiving fixes.
Intent intent = new Intent(LocationManager.GPS_FIX_CHANGE_ACTION);
intent.putExtra(LocationManager.EXTRA_GPS_ENABLED, true);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
updateStatus(LocationProvider.AVAILABLE, mSvCount);
}
if (!hasCapability(GPS_CAPABILITY_SCHEDULING) && mStarted &&
mFixInterval > GPS_POLLING_THRESHOLD_INTERVAL) {
if (DEBUG) Log.d(TAG, "got fix, hibernating");
hibernate();
}
}
而LocationManagerService实现了ILocationManager的aidl接口:
public class LocationManagerService extends ILocationManager.Stub
其中reportLocation实现如下:
@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);
Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
m.arg1 = (passive ? 1 : 0);
mLocationHandler.sendMessageAtFrontOfQueue(m);
}
// prepare worker thread
mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper());
private class LocationWorkerHandler extends Handler {
public LocationWorkerHandler(Looper looper) {
super(looper, null, true);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOCATION_CHANGED:
handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
break;
}
}
}
可以看到其实LocationManagerService也是通过维护一个消息队列来获取到位置信息改变的通知的,接收到what为MSG_LOCATION_CHANGED的message后会调用handleLocationChanged:
private void handleLocationChanged(Location location, boolean passive) {
// create a working copy of the incoming Location so that the service can modify it without
// disturbing the caller's copy
Location myLocation = new Location(location);
String provider = myLocation.getProvider();
// set "isFromMockProvider" bit if location came from a mock provider. we do not clear this
// bit if location did not come from a mock provider because passive/fused providers can
// forward locations from mock providers, and should not grant them legitimacy in doing so.
if (!myLocation.isFromMockProvider() && isMockProvider(provider)) {
myLocation.setIsFromMockProvider(true);
}
synchronized (mLock) {
if (isAllowedByCurrentUserSettingsLocked(provider)) {
if (!passive) {
// notify passive provider of the new location
mPassiveProvider.updateLocation(myLocation);
}
handleLocationChangedLocked(myLocation, passive);
}
}
}
handleLocationChangedLocked的实现比较长,核心实现为:
if (!receiver.callLocationChangedLocked(notifyLocation)) {
Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
receiverDead = true;
}
callLocationChangedLocked实现在Receiver类中
public boolean callLocationChangedLocked(Location location) {
if (mListener != null) {
try {
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
mListener.onLocationChanged(new Location(location));
// call this after broadcasting so we do not increment
// if we throw an exeption.
incrementPendingBroadcastsLocked();
}
} catch (RemoteException e) {
return false;
}
} else {
Intent locationChanged = new Intent();
locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED,
new Location(location));
try {
synchronized (this) {
// synchronize to ensure incrementPendingBroadcastsLocked()
// is called before decrementPendingBroadcasts()
mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler,
getResolutionPermission(mAllowedResolutionLevel),
PendingIntentUtils.createDontSendToRestrictedAppsBundle(null));
// call this after broadcasting so we do not increment
// if we throw an exeption.
incrementPendingBroadcastsLocked();
}
} catch (PendingIntent.CanceledException e) {
return false;
}
}
return true;
}
而这个mListener就是生成receiver时传入的参数:
final ILocationListener mListener;
LocationManagerService与LocationManager的通信就是通过这个ILocationListener.aidl来完成的,最后回调到LocationManager的onLocationChanged函数,接下来就是我们之前分析过的消息队列的工作模式。
LocationManagerService的实现一共有3485行,做了很多的参数校验与状态检查,其中有很多可以借鉴学习的地方,我通过管中窥豹的形式简单分析了其中的一部分代码,应该还有很多错误与不足,欢迎指正。
最后附上github地址:
https://github.com/woyelaishishiba/LocationManagerDemo