HI,欢迎来到《每周一博》。今天是九月第二周,我给大家分析一下Android网络定位源码。由于文章长度有限制,分为上下两篇,阅读上篇请跳转到Android网络定位源码分析[上]。
六. 一条定位请求之旅
那么接下来继续分析App发起一次定位的过程:
这里先对LocationManagerService2个内部类做一个说明,一个是UpdateRecord,它封装了LocationRequest,Receiver,和是否在前台,在构造函数里会往mRecordsByProvider里存一条记录;
1834 private class UpdateRecord {
1835 final String mProvider;
1836 final LocationRequest mRealRequest; // original request from client
1837 LocationRequest mRequest; // possibly throttled version of the request
1838 final Receiver mReceiver;
1839 boolean mIsForegroundUid;
1840 Location mLastFixBroadcast;
1841 long mLastStatusBroadcast;
1842
1843 /**
1844 * Note: must be constructed with lock held.
1845 */
1846 UpdateRecord(String provider, LocationRequest request, Receiver receiver) {
1847 mProvider = provider;
1848 mRealRequest = request;
1849 mRequest = request;
1850 mReceiver = receiver;
1851 mIsForegroundUid = isImportanceForeground(
1852 mActivityManager.getPackageImportance(mReceiver.mIdentity.mPackageName));
1853
1854 ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
1855 if (records == null) {
1856 records = new ArrayList<>();
1857 mRecordsByProvider.put(provider, records);
1858 }
1859 if (!records.contains(this)) {
1860 records.add(this);
1861 }
1862
1863 // Update statistics for historical location requests by package/provider
1864 mRequestStatistics.startRequesting(
1865 mReceiver.mIdentity.mPackageName, provider, request.getInterval());
1866 }
另一个是Receiver,它内部封装了客户端经包装后的listener或广播,还有worksource。它还有四个方法,分别对应listener中的4个回调方法:位置变化,provider状态变化,enable和disable方法。
775 /**
776 * A wrapper class holding either an ILocationListener or a PendingIntent to receive
777 * location updates.
778 */
779 private final class Receiver implements IBinder.DeathRecipient, PendingIntent.OnFinished {
780 final Identity mIdentity;
781 final int mAllowedResolutionLevel; // resolution level allowed to receiver
782
783 final ILocationListener mListener;
784 final PendingIntent mPendingIntent;
785 final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller.
786 final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver.
787 final Object mKey;
788
789 final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<>();
790
791 // True if app ops has started monitoring this receiver for locations.
792 boolean mOpMonitoring;
793 // True if app ops has started monitoring this receiver for high power (gps) locations.
794 boolean mOpHighPowerMonitoring;
795 int mPendingBroadcasts;
796 PowerManager.WakeLock mWakeLock;
797
798 Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid,
799 String packageName, WorkSource workSource, boolean hideFromAppOps) {
800 mListener = listener;
801 mPendingIntent = intent;
802 if (listener != null) {
803 mKey = listener.asBinder();
804 } else {
805 mKey = intent;
806 }
807 mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid);
808 mIdentity = new Identity(uid, pid, packageName);
809 if (workSource != null && workSource.size() <= 0) {
810 workSource = null;
811 }
812 mWorkSource = workSource;
813 mHideFromAppOps = hideFromAppOps;
814
815 updateMonitoring(true);
816
817 // construct/configure wakelock
818 mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY);
819 if (workSource == null) {
820 workSource = new WorkSource(mIdentity.mUid, mIdentity.mPackageName);
821 }
822 mWakeLock.setWorkSource(workSource);
823 }
Receiver实现了Binder的死亡代理。跨进程通讯时,service可能会为bind过来的client分配一些资源,当client调用release或者unbind的时候则会释放资源,但是如果service不知道,则为其分配的资源则不会被释放。Android提供了一种叫做死亡通知的机制,就是用在这个场景下的。当client被杀后,会回调binderDied方法,然后通过removeUpdatesLocked来释放资源。
1056 @Override
1057 public void binderDied() {
1058 if (D) Log.d(TAG, "Location listener died");
1059
1060 synchronized (mLock) {
1061 removeUpdatesLocked(this);
1062 }
1063 synchronized (this) {
1064 clearPendingBroadcastsLocked();
1065 }
1066 }
当客户端调用LocationManager的requestLocationUpdates方法时,会把参数拼成LocationRequest这个类,传给LocationManagerService。服务端会调用requestLocationUpdatesLocked方法,这些加Locked的方法是系统封装的带锁的方法。
2037 private void requestLocationUpdatesLocked(LocationRequest request, Receiver receiver,
2038 int pid, int uid, String packageName) {
2039 // Figure out the provider. Either its explicitly request (legacy use cases), or
2040 // use the fused provider
2041 if (request == null) request = DEFAULT_LOCATION_REQUEST;
2042 String name = request.getProvider();
2043 if (name == null) {
2044 throw new IllegalArgumentException("provider name must not be null");
2045 }
2046
2047 LocationProviderInterface provider = mProvidersByName.get(name);
2048 if (provider == null) {
2049 throw new IllegalArgumentException("provider doesn't exist: " + name);
2050 }
2051
2052 UpdateRecord record = new UpdateRecord(name, request, receiver);
2053 if (D) Log.d(TAG, "request " + Integer.toHexString(System.identityHashCode(receiver))
2054 + " " + name + " " + request + " from " + packageName + "(" + uid + " "
2055 + (record.mIsForegroundUid ? "foreground" : "background")
2056 + (isThrottlingExemptLocked(receiver.mIdentity)
2057 ? " [whitelisted]" : "") + ")");
2058
2059 UpdateRecord oldRecord = receiver.mUpdateRecords.put(name, record);
2060 if (oldRecord != null) {
2061 oldRecord.disposeLocked(false);
2062 }
2063
2064 boolean isProviderEnabled = isAllowedByUserSettingsLocked(name, uid);
2065 if (isProviderEnabled) {
2066 applyRequirementsLocked(name);
2067 } else {
2068 // Notify the listener that updates are currently disabled
2069 receiver.callProviderEnabledLocked(name, false);
2070 }
2071 // Update the monitoring here just in case multiple location requests were added to the
2072 // same receiver (this request may be high power and the initial might not have been).
2073 receiver.updateMonitoring(true);
2074 }
在这里每个发起定位请求的客户端都会插入一条记录UpdateRecord,现在终于知道为什么取消定位的方法叫removeUpdate而不是removeRequest了吧,其实请求定位和取消定位就是插入删除一条记录,如果之前有这条记录,那么就把它移除,相当于App调用了2次定位,那么后面的请求会把前面的覆盖,这种情况一般是发生在持续定位的过程,就像三星测试机,每调用一次requestLocationUpdate方法就会走一次onSetRequest,后面覆盖前面,而vivo测试机如果调用了requestLocationUpdate,就必须removeUpdate才能再次触发requestLocationUpdate,否则调用不生效,可能就是修改了这块的代码。
如果provider是enable状态,就会走applyRequirementsLocked方法,这里先对ProviderRequest类做一个介绍,这个类是对LocationRequest集合的一个封装,可以理解为是现在所有发起的定位的请求集合,比如5个应用向系统要位置,那么ProviderRequest里就有5个LocationRequest。它有2个变量,最小时间间隔默认值是9223372036854775807L,是否需要上报位置默认值是flase。
public final class ProviderRequest implements Parcelable {
/** Location reporting is requested (true) */
public boolean reportLocation = false;
/** The smallest requested interval */
public long interval = Long.MAX_VALUE;
/**
* A more detailed set of requests.
* <p>Location Providers can optionally use this to
* fine tune location updates, for example when there
* is a high power slow interval request and a
* low power fast interval request.
*/
public List<LocationRequest> locationRequests = new ArrayList<LocationRequest>();
在它的toString方法里可以看到ON和OFF的身影,NLP提供商会接受到它的包装类ProviderRequestUnbundle对象,可以得到ON或OFF的值。
80 @Override
81 public String toString() {
82 StringBuilder s = new StringBuilder();
83 s.append("ProviderRequest[");
84 if (reportLocation) {
85 s.append("ON");
86 s.append(" interval=");
87 TimeUtils.formatDuration(interval, s);
88 } else {
89 s.append("OFF");
90 }
91 s.append(']');
92 return s.toString();
93 }
我们继续分析applyRequirementsLocked这个方法,先取出了设置里最小的时间间隔DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000,是30分,不过这个变量厂商一般会修改,vivo测试机上目前是10分。全局有一个变量mRecordsByProvider来记录所有的UpdateRecord,所以这里对UpdateRecord集合做了一个遍历,筛选出一个最小的时间作为更新频率。先是判断该应用是否在后台,是的话就选择App传递值和设置值的最大值作为它的更新时间。然后判断当前LocationRequest的频率和ProviderRequest的频率,小于标记为需要上报位置,并把ProviderRequest的频率调低。
这么说可能会没有感知,我举个测试的例子你就懂了,有2个应用在请求位置,频率分别是1min,5min,那么ProviderRequest的频率就是1min,这会生成2条UpdateRecord,这时再来一个10s的定位请求,会先判断如果在后台就设置为30分,然后用第3条请求的频率和ProviderRequest的频率作比较,选择最小的10s作为ProviderRequest的频率,同时标记需要上报位置。
1708 private void applyRequirementsLocked(String provider) {
1709 LocationProviderInterface p = mProvidersByName.get(provider);
1710 if (p == null) return;
1711
1712 ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider);
1713 WorkSource worksource = new WorkSource();
1714 ProviderRequest providerRequest = new ProviderRequest();
1715
1716 ContentResolver resolver = mContext.getContentResolver();
1717 long backgroundThrottleInterval = Settings.Global.getLong(
1718 resolver,
1719 Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS,
1720 DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS);
1721
1722 if (records != null) {
1723 for (UpdateRecord record : records) {
1724 if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
1725 if (checkLocationAccess(
1726 record.mReceiver.mIdentity.mPid,
1727 record.mReceiver.mIdentity.mUid,
1728 record.mReceiver.mIdentity.mPackageName,
1729 record.mReceiver.mAllowedResolutionLevel)) {
1730 LocationRequest locationRequest = record.mRealRequest;
1731 long interval = locationRequest.getInterval();
1732
1733 if (!isThrottlingExemptLocked(record.mReceiver.mIdentity)) {
1734 if (!record.mIsForegroundUid) {
1735 interval = Math.max(interval, backgroundThrottleInterval);
1736 }
1737 if (interval != locationRequest.getInterval()) {
1738 locationRequest = new LocationRequest(locationRequest);
1739 locationRequest.setInterval(interval);
1740 }
1741 }
1742
1743 record.mRequest = locationRequest;
1744 providerRequest.locationRequests.add(locationRequest);
1745 if (interval < providerRequest.interval) {
1746 providerRequest.reportLocation = true;
1747 providerRequest.interval = interval;
1748 }
1749 }
1750 }
1751 }
这里注意下ProviderRequest是一个局部变量,每次都会new出来的,它的时间频率默认值是一个大数,所以每次遍历只要有定位请求,频率就会改变,直到找出最小的频率,并且标记为需要上报位置。过去我们理解的是请求定位传ON,移除定位传OFF,这是错误的,OFF的意义应该是当前所有的定位请求全部取消了,也就是最后一个需要定位的请求也取消了,而不是单个App请求请求。
经过测试发现确实如此,如果只有一个APP请求定位,那么调用removeUpdate会收到OFF,如果是多个APP请求位置,那么只有最后一个请求调用了removeUpdate才会接受到OFF事件,当然修改过系统代码的可能不会接受到OFF,比如三星手机,它始终返回ON,但是interval设置的比较大,是12小时,如果三星没有修改过代码的话,那么可以理解为系统默认会要求请求,每半天定位一次,永远不会取消。
这里也看到了8.0系统的新特性,有2个方法isThrottlingExemptLocked和record.mIsForegroundUid做判断,如果是系统和白名单的应用那么不会受限,其他应用如果进入后台定位频率将会被调大到30分,我测试了一下确实是这样,Activity不在前台即执行了onPause之后频率就会降低,还有灭屏,即便开启了service在service里定位也没用,甚至是应用内跨进程的service单独定位也有这个限制,只要唤醒它的Activity进入后台定位频率就变大。
1816 private boolean isThrottlingExemptLocked(Identity identity) {
1817 if (identity.mUid == Process.SYSTEM_UID) {
1818 return true;
1819 }
1820
1821 if (mBackgroundThrottlePackageWhitelist.contains(identity.mPackageName)) {
1822 return true;
1823 }
1824
1825 for (LocationProviderProxy provider : mProxyProviders) {
1826 if (identity.mPackageName.equals(provider.getConnectedPackageName())) {
1827 return true;
1828 }
1829 }
1830
1831 return false;
1832 }
遍历完之后判断如果需要上报位置,就把worksource记录下,以便于追查耗电的元凶,worksource包含2个参数,一个是uid,一个是包名。最后会回调setRequest方法,把ProviderRequest和WorkSource参数传递过去,所以App每调一次requestLocationUpdate方法,NLP提供商就会回调onSetRequest方法。
1753 if (providerRequest.reportLocation) {
1754 // calculate who to blame for power
1755 // This is somewhat arbitrary. We pick a threshold interval
1756 // that is slightly higher that the minimum interval, and
1757 // spread the blame across all applications with a request
1758 // under that threshold.
1759 long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
1760 for (UpdateRecord record : records) {
1761 if (isCurrentProfile(UserHandle.getUserId(record.mReceiver.mIdentity.mUid))) {
1762 LocationRequest locationRequest = record.mRequest;
1763
1764 // Don't assign battery blame for update records whose
1765 // client has no permission to receive location data.
1766 if (!providerRequest.locationRequests.contains(locationRequest)) {
1767 continue;
1768 }
1769
1770 if (locationRequest.getInterval() <= thresholdInterval) {
1771 if (record.mReceiver.mWorkSource != null
1772 && record.mReceiver.mWorkSource.size() > 0
1773 && record.mReceiver.mWorkSource.getName(0) != null) {
1774 // Assign blame to another work source.
1775 // Can only assign blame if the WorkSource contains names.
1776 worksource.add(record.mReceiver.mWorkSource);
1777 } else {
1778 // Assign blame to caller.
1779 worksource.add(
1780 record.mReceiver.mIdentity.mUid,
1781 record.mReceiver.mIdentity.mPackageName);
1782 }
1783 }
1784 }
1785 }
1786 }
1787 }
1788
1789 if (D) Log.d(TAG, "provider request: " + provider + " " + providerRequest);
1790 p.setRequest(providerRequest, worksource);
1791 }
1792
接着我们向服务器请求定位,得到结果后调用LocationProviderBase的reportLocation方法来把位置上报。这里又需要注意了,reportLocation并不是ILocationProvider里的接口方法,而是LocationProviderBase里的一个自定义的final方法,它调用的是ILocationManager里定义的reportLocation方法,而前面已经说过LocationManagerService才是ILocationManager真正实现类,所以要去LocationManagerService去找reportLocation究竟做了什么,定位结果是怎么返给APP的。
public abstract class LocationProviderBase {
private final String TAG;
protected final ILocationManager mLocationManager;
private final ProviderProperties mProperties;
private final IBinder mBinder;
127 /**
128 * Used by the location provider to report new locations.
129 *
130 * @param location new Location to report
131 *
132 * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission.
133 */
134 public final void reportLocation(Location location) {
135 try {
136 mLocationManager.reportLocation(location, false);
137 } catch (RemoteException e) {
138 Log.e(TAG, "RemoteException", e);
139 } catch (Exception e) {
140 // never crash provider, might be running in a system process
141 Log.e(TAG, "Exception", e);
142 }
143 }
LocationManagerService的reportLocation就是用handler发送了一个MSG_LOCATION_CHANGED的消息,还是很符合谷歌的风格的;
2515 @Override
2516 public void reportLocation(Location location, boolean passive) {
2517 checkCallerIsProvider();
2518
2519 if (!location.isComplete()) {
2520 Log.w(TAG, "Dropping incomplete location: " + location);
2521 return;
2522 }
2523
2524 mLocationHandler.removeMessages(MSG_LOCATION_CHANGED, location);
2525 Message m = Message.obtain(mLocationHandler, MSG_LOCATION_CHANGED, location);
2526 m.arg1 = (passive ? 1 : 0);
2527 mLocationHandler.sendMessageAtFrontOfQueue(m);
2528 }
那么进入case分支,查看它到底做了什么,原来是调用了handleLocationChanged方法,而它又调用了handleLocationChangedLocked方法,我们来继续跟进;
2725 private class LocationWorkerHandler extends Handler {
2726 public LocationWorkerHandler(Looper looper) {
2727 super(looper, null, true);
2728 }
2729
2730 @Override
2731 public void handleMessage(Message msg) {
2732 switch (msg.what) {
2733 case MSG_LOCATION_CHANGED:
2734 handleLocationChanged((Location) msg.obj, msg.arg1 == 1);
2735 break;
2736 }
2737 }
2738 }
在handleLocationChangedLocked方法里先对mLastLocation做了更新,然后遍历所有UpdateRecord,根据LocationRequest请求的精度来确定返回粗略位置还是精确位置。
2659 Location notifyLocation;
2660 if (receiver.mAllowedResolutionLevel < RESOLUTION_LEVEL_FINE) {
2661 notifyLocation = coarseLocation; // use coarse location
2662 } else {
2663 notifyLocation = lastLocation; // use fine location
2664 }
2665 if (notifyLocation != null) {
2666 Location lastLoc = r.mLastFixBroadcast;
2667 if ((lastLoc == null) || shouldBroadcastSafe(notifyLocation, lastLoc, r, now)) {
2668 if (lastLoc == null) {
2669 lastLoc = new Location(notifyLocation);
2670 r.mLastFixBroadcast = lastLoc;
2671 } else {
2672 lastLoc.set(notifyLocation);
2673 }
2674 if (!receiver.callLocationChangedLocked(notifyLocation)) {
2675 Slog.w(TAG, "RemoteException calling onLocationChanged on " + receiver);
2676 receiverDead = true;
2677 }
2678 r.mRealRequest.decrementNumUpdates();
2679 }
2680 }
这里会走一个shouldBroadcastSafe的判断,看看返回的时间间隔和位置差值是否符合用户的传入,这里就把客户端传来的参数用到了,比如App5min要一次位置,位置差在50米以外才回调,这个方法就是判断这个的。
2531 private static boolean shouldBroadcastSafe(
2532 Location loc, Location lastLoc, UpdateRecord record, long now) {
2533 // Always broadcast the first update
2534 if (lastLoc == null) {
2535 return true;
2536 }
2537
2538 // Check whether sufficient time has passed
2539 long minTime = record.mRealRequest.getFastestInterval();
2540 long delta = (loc.getElapsedRealtimeNanos() - lastLoc.getElapsedRealtimeNanos())
2541 / NANOS_PER_MILLI;
2542 if (delta < minTime - MAX_PROVIDER_SCHEDULING_JITTER_MS) {
2543 return false;
2544 }
2545
2546 // Check whether sufficient distance has been traveled
2547 double minDistance = record.mRealRequest.getSmallestDisplacement();
2548 if (minDistance > 0.0) {
2549 if (loc.distanceTo(lastLoc) <= minDistance) {
2550 return false;
2551 }
2552 }
2553
2554 // Check whether sufficient number of udpates is left
2555 if (record.mRealRequest.getNumUpdates() <= 0) {
2556 return false;
2557 }
2558
2559 // Check whether the expiry date has passed
2560 return record.mRealRequest.getExpireAt() >= now;
2561 }
如果符合条件,就会回调客户端的listener,这个listener是封装在Receiver里的,注意这次回调是跨进程的,会抛出RemoteException,如果抛出该异常,就认为客户端已经被杀,那么这个receiver将被标记为死亡状态并加入一个列表中。在callLocationChangedLocaked方法里就会回调客户端Listener的onLocationChanged方法,并把Location回传回去,如果客户端不是用的Listener而是用广播的形式来接受数据,那么就会发送广播。回调完之后会走一个方法 r.mRealRequest.decrementNumUpdates(),把LocationRequest里的计数器减1。
980 public boolean callLocationChangedLocked(Location location) {
981 if (mListener != null) {
982 try {
983 synchronized (this) {
984 // synchronize to ensure incrementPendingBroadcastsLocked()
985 // is called before decrementPendingBroadcasts()
986 mListener.onLocationChanged(new Location(location));
987 // call this after broadcasting so we do not increment
988 // if we throw an exeption.
989 incrementPendingBroadcastsLocked();
990 }
991 } catch (RemoteException e) {
992 return false;
993 }
994 } else {
995 Intent locationChanged = new Intent();
996 locationChanged.putExtra(LocationManager.KEY_LOCATION_CHANGED, new Location(location));
997 try {
998 synchronized (this) {
999 // synchronize to ensure incrementPendingBroadcastsLocked()
1000 // is called before decrementPendingBroadcasts()
1001 mPendingIntent.send(mContext, 0, locationChanged, this, mLocationHandler,
1002 getResolutionPermission(mAllowedResolutionLevel));
1003 // call this after broadcasting so we do not increment
1004 // if we throw an exeption.
1005 incrementPendingBroadcastsLocked();
1006 }
1007 } catch (PendingIntent.CanceledException e) {
1008 return false;
1009 }
1010 }
1011 return true;
1012 }
然后看是否需要回调callStatusChangedLocked方法,它内部也是回调Listener或者发送广播。
945 public boolean callStatusChangedLocked(String provider, int status, Bundle extras) {
946 if (mListener != null) {
947 try {
948 synchronized (this) {
949 // synchronize to ensure incrementPendingBroadcastsLocked()
950 // is called before decrementPendingBroadcasts()
951 mListener.onStatusChanged(provider, status, extras);
952 // call this after broadcasting so we do not increment
953 // if we throw an exeption.
954 incrementPendingBroadcastsLocked();
955 }
956 } catch (RemoteException e) {
957 return false;
958 }
959 } else {
960 Intent statusChanged = new Intent();
961 statusChanged.putExtras(new Bundle(extras));
962 statusChanged.putExtra(LocationManager.KEY_STATUS_CHANGED, status);
963 try {
964 synchronized (this) {
965 // synchronize to ensure incrementPendingBroadcastsLocked()
966 // is called before decrementPendingBroadcasts()
967 mPendingIntent.send(mContext, 0, statusChanged, this, mLocationHandler,
968 getResolutionPermission(mAllowedResolutionLevel));
969 // call this after broadcasting so we do not increment
970 // if we throw an exeption.
971 incrementPendingBroadcastsLocked();
972 }
973 } catch (PendingIntent.CanceledException e) {
974 return false;
975 }
976 }
977 return true;
978 }
接着会判断LocationRequest的numUpdates的个数,这个在一开始分析LocationMananger的时候说到过,调用requestSingleUpdate会把这个变量设置为1,而它减1是在回调listener那里,所以单次请求到这里就会结束,它被加入到一个deadUpdateRecords的列表中。最后对回调失败的客户端也就是被杀死的客户端进行清理,通过调用removeUpdatesLocked释放资源。然后对加入到deadUpdateRecords里的请求做一个释放,再执行一次applyRequirementsLocked方法。
需要注意的是这里有2个列表,一个是客户端已经被杀死的列表,针对这个列表执行的方法是removeUpdatesLocked,另一个是deadUpdateRecords的列表,对它可以简单理解成单次定位而非持续定位的请求,也就是通过requestSingleUpdate调用产生的请求,这些请求执行完上报位置后要做释放,调用的是Receiver的disposeLocked方法,同时再执行applyRequirementsLocked方法,而这个方法又会回调setRequest方法。
一开始我怎么也看不懂这是啥意思,后来用demo反复测试后终于搞明白了,requestSingleUpdate方法会触发2次onSetRequest方法,这在log里有显示,第一次是App发起的,传入的interval是0(vivo手机经过修改是2ms),上报位置后又回调了一次onSetRequest,这次传的是OFF,并且没有worksource,也就是终止定位了,这正好和源码里一次单次定位回调2次onSetRequest是吻合的。当然这是在只有一个App定位的情形下测试的,如果2个App一起定位,另一个是持续定位,时间间隔是20s,那么requestSingleUpdate的第二次onSetRequest回调传回来的参数就是20,并且传来的ON,不是OFF。
2693 // track expired records
2694 if (r.mRealRequest.getNumUpdates() <= 0 || r.mRealRequest.getExpireAt() < now) {
2695 if (deadUpdateRecords == null) {
2696 deadUpdateRecords = new ArrayList<>();
2697 }
2698 deadUpdateRecords.add(r);
2699 }
2700 // track dead receivers
2701 if (receiverDead) {
2702 if (deadReceivers == null) {
2703 deadReceivers = new ArrayList<>();
2704 }
2705 if (!deadReceivers.contains(receiver)) {
2706 deadReceivers.add(receiver);
2707 }
2708 }
2709 }
2710
2711 // remove dead records and receivers outside the loop
2712 if (deadReceivers != null) {
2713 for (Receiver receiver : deadReceivers) {
2714 removeUpdatesLocked(receiver);
2715 }
2716 }
2717 if (deadUpdateRecords != null) {
2718 for (UpdateRecord r : deadUpdateRecords) {
2719 r.disposeLocked(true);
2720 }
2721 applyRequirementsLocked(provider);
2722 }
接着我们继续看下removeUpdatesLocked方法里做了什么,如果客户端传入的是listener,那么让它死亡,调用receiver.getListener().asBinder().unlinkToDeath(receiver, 0)方法,然后取出UpdateRecord,执行它的disposeLocked(false)方法,这个方法传入false就是从全局的记录UpdateRecord列表对象mRecordsByProvider中移除该记录。这是对死亡的客户端做一次回调。如果是单次定位的请求则调用disposeLocked(true)方法来销毁,boolean值表示是否移除Receiver。
2100 private void removeUpdatesLocked(Receiver receiver) {
2101 if (D) Log.i(TAG, "remove " + Integer.toHexString(System.identityHashCode(receiver)));
2102
2103 if (mReceivers.remove(receiver.mKey) != null && receiver.isListener()) {
2104 receiver.getListener().asBinder().unlinkToDeath(receiver, 0);
2105 synchronized (receiver) {
2106 receiver.clearPendingBroadcastsLocked();
2107 }
2108 }
2109
2110 receiver.updateMonitoring(false);
2111
2112 // Record which providers were associated with this listener
2113 HashSet<String> providers = new HashSet<>();
2114 HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords;
2115 if (oldRecords != null) {
2116 // Call dispose() on the obsolete update records.
2117 for (UpdateRecord record : oldRecords.values()) {
2118 // Update statistics for historical location requests by package/provider
2119 record.disposeLocked(false);
2120 }
2121 // Accumulate providers
2122 providers.addAll(oldRecords.keySet());
2123 }
2124
2125 // update provider
2126 for (String provider : providers) {
2127 // If provider is already disabled, don't need to do anything
2128 if (!isAllowedByCurrentUserSettingsLocked(provider)) {
2129 continue;
2130 }
2131
2132 applyRequirementsLocked(provider);
2133 }
2134 }
1868 /**
1869 * Method to be called when a record will no longer be used.
1870 */
1871 void disposeLocked(boolean removeReceiver) {
1872 mRequestStatistics.stopRequesting(mReceiver.mIdentity.mPackageName, mProvider);
1873
1874 // remove from mRecordsByProvider
1875 ArrayList<UpdateRecord> globalRecords = mRecordsByProvider.get(this.mProvider);
1876 if (globalRecords != null) {
1877 globalRecords.remove(this);
1878 }
1879
1880 if (!removeReceiver) return; // the caller will handle the rest
1881
1882 // remove from Receiver#mUpdateRecords
1883 HashMap<String, UpdateRecord> receiverRecords = mReceiver.mUpdateRecords;
1884 if (receiverRecords != null) {
1885 receiverRecords.remove(this.mProvider);
1886
1887 // and also remove the Receiver if it has no more update records
1888 if (receiverRecords.size() == 0) {
1889 removeUpdatesLocked(mReceiver);
1890 }
1891 }
1892 }
至此一次定位的流程就算是走完了,可以看出一次定位可能会多次回调onSetRequest方法,主要是requestSingleUpdate,它会把numUpdates变量置为1,而默认值则是一个大数Integer.MAX_VALUE = 0x7fffffff,伴随着每次reportLocation计数器减1,单次定位只会执行一次,并且归零后再回调一次onSetRequest方法,根据情况传入OFF。持续定位则不会回调2次,因为这个大数要递减N多次才会为0,我们之前理解的一次定位只回调一次onSetRequest是错误的。
移除定位请求其实上面也说到了,它调用removeUpdates,根据传入的listener来找到对应的Receiver,然后调用removeUpdatesLocked方法,让监听死亡,并调用record.disposeLocked(false)来销毁记录,然后再走一次applyRequirementsLocked方法。也就是说removeUpdates也会回调一次onSetRequest方法,这和log显示的是一致的,之前一直不明白为什么移除定位的时候也要发一次请求并传入OFF,这回看源码就懂了。但这是针对单个App的情形,requestUpdate之后调用removeUpdates会回调onSetRequest并传入OFF,但多个App一起请求下就会有变化,onSetRequest仍然会回调,但是是否传入OFF要看这个请求是否是最后一个请求,否则传入的是ON,频率是ProviderRequest的最小频率。所以OFF是针对所有请求都关闭的时候才会回传。
我们看到其实applyRequirementsLocked这是个关键方法,不论是初始化,还是requestUpdate,removeUpdates,reportLocation都会调用它,它就是一个应用参数标准,每当有变动就去检查,变更一下定位频率,并回调onSetRequest方法。所以onSetRequest方法就是参数(主要就是定位频率)发生了变更后的回调,而不是之前我们所理解的定一次位就回调一次,每当定位频率发生改变这个方法就会回调。
在实际测试过程中,我发现三星测试机不回传OFF,并且如果先调用持续定位,再调用单次定位后,持续定位会被抵消,参数传入了ON并且把时间设置成了12h,而vivo修改了这块逻辑,在持续定位时插入一条单次定位,不会回传OFF,而是传入ON,并且频率是当前最小的频率,这个还是比较合理的。
七. 总结分享
在上面走读源码的过程中,也分析到了很多和第三方NLP相关的点,这里再简单概括一下:
1.NLP提供商怎么配置,配置的这些参数是如何生效的,系统在哪里调用到了;
2.NLP提供商要用一个Service去绑定系统的Action,并间接实现ILocationProvider的方法,也就是继成LocationProviderBase类;
3.这个Service要配置service_version才能被正常解析;
4.NLP提供商要在onSetRequest方法里接收参数并实现定位功能,把结果上报回去,考虑到既有单次定位又有持续定位,可以开启一个定时器队列来实现,另外传入OFF的时候是不需要上报位置的;
5.onEnable可以理解为是一个初始化方法,onDisable可以理解为是销毁方法,可以在这2个方法里做初始化和释放资源的事情;
6.当Provider状态发生变更时通过onGetStatus和onGetStatusUpdateTime方法来改变状态和时间;
我把看源码过程中的其他收获也来分享一下;
1.WorkSource:就是定位来源,它由uid和包名共同组成,如果有多个,他们会以", "进行分割,一个逗号加一个空格,这个在做零秒定位解析worksource时产生过异常,因为那个空格没看出来,源码里确实是有的;
2.ProviderProperties:做重构的时候看到这个变量不知道是干嘛的,传了一堆boolean值,现在知道他其实是和App层的Criteria类似的。
private static ProviderPropertiesUnbundled PROPERTIES = ProviderPropertiesUnbundled.create(true, false, true, false, false, false, false, 1, 1);
Criteria是一组标准,比如是否支持海拔,支持方向,需要付费,高精度等等,App层可以指定一系列条件来获取到适合的Provider(也有可能没有满足条件的),那这些参数传递到NLP后就变成了ProviderProperties这个对象,这个变量是LocationProviderBase的构造函数需要的。
97 public ProviderProperties(boolean mRequiresNetwork,
98 boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost,
99 boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing,
100 int mPowerRequirement, int mAccuracy) {
101 this.mRequiresNetwork = mRequiresNetwork;
102 this.mRequiresSatellite = mRequiresSatellite;
103 this.mRequiresCell = mRequiresCell;
104 this.mHasMonetaryCost = mHasMonetaryCost;
105 this.mSupportsAltitude = mSupportsAltitude;
106 this.mSupportsSpeed = mSupportsSpeed;
107 this.mSupportsBearing = mSupportsBearing;
108 this.mPowerRequirement = mPowerRequirement;
109 this.mAccuracy = mAccuracy;
110 }
3.onGetStatus:这是NLP提供商要实现的一个方法,它就是获取Provider的状态的,一共有3种,在onEnable和onDisable后要更新状态,这样App才会在Listenter中的onStatusChanged方法里接收到正确的状态回调。
LocationProvider.OUT_OF_SERVICE = 0:无服务
LocationProvider.AVAILABLE = 2:provider可用
LocationProvider.TEMPORARILY_UNAVAILABLE = 1:provider不可用
4.onGetStatusUpdateTime:这个方法和上面是一样的,它是状态发生更新的时间,但要注意同样的状态不要更新2次,所以在上一个方法里要判断本次状态和之前是否相同,不同再去记录一下时间,谷歌让我们使用SystemClock.elapsedRealtime方法来进行设置。
5.GeocoderProxy的实现方式和LocationProviderProxy类似,也都是通过ServiceWatcher去读取配置,绑定对应的Service,IGeocodeProvider定义的AIDL接口GeocodeProvider已经实现了,我们只需要继承它就可以了,这个类更简单一共2个方法,一个是onGetFromLocationName,一个是onGetFromLocation。同理地理围栏GeofenceProvider也是一样的代理实现。
最后介绍下分析一个类遵循的主要步骤:
1.明确类的主要作用:顾名思义,LocationManagerService主要是用来提供定位服务的;
2.分析类的主要字段:LocationManagerService的很多字段都是围绕provider来展开的,所以它主要是用来管理provider的;
(1) mEnabledProviders:可用的Provider集合;
(2) mRecordsByProvider:定位请求Update的集合;
(3) mLastLocation:最近一次的定位信息,以 Location Provider 的名称为键的映射
3.理解类的初始化过程:LocationManagerService的初始化主要就是加载各种Provider,其中NetworkLocationPrivider和GeocoderProvider是通过ServiceWatcher去配置中读取绑定的;
4.理解类的主要业务逻辑方法:
(1) requestLocationUpdatesLocked:请求定位的方法,插入一条记录;
(2) removeUpdatesLocked:移除定位请求的方法,移除一条记录;
(3) applyRequirementsLocked:各种配置变更都会走该方法,它会回调setRequest方法;
(4) handleLocationChanged:上报位置时会触发该方法,这里会把结果回调给App;
5.分析类中的其他成员方法和内部类:
上面的4个方法其实主要是从大方向上去分析用到的,但是真正到了一些具体实现或者细节方面的东西还得看一些辅助的方法和内部类,比如UpdateRecord,Receiver这些关键类的关键方法,真正把位置回调给App的就是在Receiver的callLocationChangedLocaked方法中;
6.分析与这个类紧密相关的其他类:在第五部分介绍过了,主要有十几个相关的类吧;
八. 收尾
在分析源码的过程中,我疏通了很多过去搞不懂的盲点,而且对于Android当中的Binder机制有了更深的理解,一开始真没看出来是双重CS模型,看懂CS之后就很明朗了,另外对于Binder的死亡机制也有了更多理解,还有就是对定位这种架构的设计有了一些体会,其中不乏诸多设计模式的使用,Java的高级编程知识,同步锁的控制,面向抽象接口编程,封装参数等等,收获确实不少。
当然走读源码的过程并不是很顺畅,中间有一段时间卡住怎么也过不去,看不懂,遇到这种情况怎么办?别怕,多读几遍就懂了。