HI盐须,歡迎來到《每周一博》。今天是九月第二周凤薛,我給大家分析一下Android網(wǎng)絡(luò)定位源碼座泳。由于文章長度有限制,分為上下兩篇洒沦,閱讀上篇請(qǐng)?zhí)D(zhuǎn)到Android網(wǎng)絡(luò)定位源碼分析[上]豹绪。
六. 一條定位請(qǐng)求之旅
那么接下來繼續(xù)分析App發(fā)起一次定位的過程:
這里先對(duì)LocationManagerService2個(gè)內(nèi)部類做一個(gè)說明,一個(gè)是UpdateRecord申眼,它封裝了LocationRequest瞒津,Receiver,和是否在前臺(tái)括尸,在構(gòu)造函數(shù)里會(huì)往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 }
另一個(gè)是Receiver,它內(nèi)部封裝了客戶端經(jīng)包裝后的listener或廣播姻氨,還有worksource钓辆。它還有四個(gè)方法,分別對(duì)應(yīng)listener中的4個(gè)回調(diào)方法:位置變化肴焊,provider狀態(tài)變化前联,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實(shí)現(xiàn)了Binder的死亡代理娶眷∷凄停跨進(jìn)程通訊時(shí),service可能會(huì)為bind過來的client分配一些資源届宠,當(dāng)client調(diào)用release或者unbind的時(shí)候則會(huì)釋放資源烁落,但是如果service不知道,則為其分配的資源則不會(huì)被釋放豌注。Android提供了一種叫做死亡通知的機(jī)制伤塌,就是用在這個(gè)場景下的。當(dāng)client被殺后轧铁,會(huì)回調(diào)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 }
當(dāng)客戶端調(diào)用LocationManager的requestLocationUpdates方法時(shí)齿风,會(huì)把參數(shù)拼成LocationRequest這個(gè)類药薯,傳給LocationManagerService。服務(wù)端會(huì)調(diào)用requestLocationUpdatesLocked方法救斑,這些加Locked的方法是系統(tǒng)封裝的帶鎖的方法童本。
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 }
在這里每個(gè)發(fā)起定位請(qǐng)求的客戶端都會(huì)插入一條記錄UpdateRecord,現(xiàn)在終于知道為什么取消定位的方法叫removeUpdate而不是removeRequest了吧脸候,其實(shí)請(qǐng)求定位和取消定位就是插入刪除一條記錄穷娱,如果之前有這條記錄绑蔫,那么就把它移除,相當(dāng)于App調(diào)用了2次定位鄙煤,那么后面的請(qǐng)求會(huì)把前面的覆蓋晾匠,這種情況一般是發(fā)生在持續(xù)定位的過程茶袒,就像三星測試機(jī)梯刚,每調(diào)用一次requestLocationUpdate方法就會(huì)走一次onSetRequest,后面覆蓋前面薪寓,而vivo測試機(jī)如果調(diào)用了requestLocationUpdate亡资,就必須removeUpdate才能再次觸發(fā)requestLocationUpdate,否則調(diào)用不生效向叉,可能就是修改了這塊的代碼锥腻。
如果provider是enable狀態(tài),就會(huì)走applyRequirementsLocked方法母谎,這里先對(duì)ProviderRequest類做一個(gè)介紹瘦黑,這個(gè)類是對(duì)LocationRequest集合的一個(gè)封裝,可以理解為是現(xiàn)在所有發(fā)起的定位的請(qǐng)求集合奇唤,比如5個(gè)應(yīng)用向系統(tǒng)要位置幸斥,那么ProviderRequest里就有5個(gè)LocationRequest。它有2個(gè)變量咬扇,最小時(shí)間間隔默認(rèn)值是9223372036854775807L甲葬,是否需要上報(bào)位置默認(rèn)值是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提供商會(huì)接受到它的包裝類ProviderRequestUnbundle對(duì)象经窖,可以得到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 }
我們繼續(xù)分析applyRequirementsLocked這個(gè)方法梭灿,先取出了設(shè)置里最小的時(shí)間間隔DEFAULT_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000画侣,是30分,不過這個(gè)變量廠商一般會(huì)修改堡妒,vivo測試機(jī)上目前是10分配乱。全局有一個(gè)變量mRecordsByProvider來記錄所有的UpdateRecord,所以這里對(duì)UpdateRecord集合做了一個(gè)遍歷涕蚤,篩選出一個(gè)最小的時(shí)間作為更新頻率宪卿。先是判斷該應(yīng)用是否在后臺(tái),是的話就選擇App傳遞值和設(shè)置值的最大值作為它的更新時(shí)間万栅。然后判斷當(dāng)前LocationRequest的頻率和ProviderRequest的頻率佑钾,小于標(biāo)記為需要上報(bào)位置,并把ProviderRequest的頻率調(diào)低烦粒。
這么說可能會(huì)沒有感知休溶,我舉個(gè)測試的例子你就懂了代赁,有2個(gè)應(yīng)用在請(qǐng)求位置,頻率分別是1min兽掰,5min芭碍,那么ProviderRequest的頻率就是1min,這會(huì)生成2條UpdateRecord孽尽,這時(shí)再來一個(gè)10s的定位請(qǐng)求窖壕,會(huì)先判斷如果在后臺(tái)就設(shè)置為30分,然后用第3條請(qǐng)求的頻率和ProviderRequest的頻率作比較杉女,選擇最小的10s作為ProviderRequest的頻率瞻讽,同時(shí)標(biāo)記需要上報(bào)位置。
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是一個(gè)局部變量熏挎,每次都會(huì)new出來的速勇,它的時(shí)間頻率默認(rèn)值是一個(gè)大數(shù),所以每次遍歷只要有定位請(qǐng)求坎拐,頻率就會(huì)改變烦磁,直到找出最小的頻率,并且標(biāo)記為需要上報(bào)位置哼勇。過去我們理解的是請(qǐng)求定位傳ON都伪,移除定位傳OFF,這是錯(cuò)誤的猴蹂,OFF的意義應(yīng)該是當(dāng)前所有的定位請(qǐng)求全部取消了院溺,也就是最后一個(gè)需要定位的請(qǐng)求也取消了,而不是單個(gè)App請(qǐng)求請(qǐng)求磅轻。
經(jīng)過測試發(fā)現(xiàn)確實(shí)如此珍逸,如果只有一個(gè)APP請(qǐng)求定位,那么調(diào)用removeUpdate會(huì)收到OFF聋溜,如果是多個(gè)APP請(qǐng)求位置谆膳,那么只有最后一個(gè)請(qǐng)求調(diào)用了removeUpdate才會(huì)接受到OFF事件,當(dāng)然修改過系統(tǒng)代碼的可能不會(huì)接受到OFF撮躁,比如三星手機(jī)漱病,它始終返回ON,但是interval設(shè)置的比較大把曼,是12小時(shí)杨帽,如果三星沒有修改過代碼的話,那么可以理解為系統(tǒng)默認(rèn)會(huì)要求請(qǐng)求嗤军,每半天定位一次注盈,永遠(yuǎn)不會(huì)取消。
這里也看到了8.0系統(tǒng)的新特性叙赚,有2個(gè)方法isThrottlingExemptLocked和record.mIsForegroundUid做判斷老客,如果是系統(tǒng)和白名單的應(yīng)用那么不會(huì)受限僚饭,其他應(yīng)用如果進(jìn)入后臺(tái)定位頻率將會(huì)被調(diào)大到30分,我測試了一下確實(shí)是這樣胧砰,Activity不在前臺(tái)即執(zhí)行了onPause之后頻率就會(huì)降低鳍鸵,還有滅屏,即便開啟了service在service里定位也沒用尉间,甚至是應(yīng)用內(nèi)跨進(jìn)程的service單獨(dú)定位也有這個(gè)限制偿乖,只要喚醒它的Activity進(jìn)入后臺(tái)定位頻率就變大。
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 }
遍歷完之后判斷如果需要上報(bào)位置乌妒,就把worksource記錄下汹想,以便于追查耗電的元兇,worksource包含2個(gè)參數(shù)撤蚊,一個(gè)是uid,一個(gè)是包名损话。最后會(huì)回調(diào)setRequest方法侦啸,把ProviderRequest和WorkSource參數(shù)傳遞過去,所以App每調(diào)一次requestLocationUpdate方法丧枪,NLP提供商就會(huì)回調(diào)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
接著我們向服務(wù)器請(qǐng)求定位,得到結(jié)果后調(diào)用LocationProviderBase的reportLocation方法來把位置上報(bào)拧烦。這里又需要注意了忘闻,reportLocation并不是ILocationProvider里的接口方法,而是LocationProviderBase里的一個(gè)自定義的final方法恋博,它調(diào)用的是ILocationManager里定義的reportLocation方法齐佳,而前面已經(jīng)說過LocationManagerService才是ILocationManager真正實(shí)現(xiàn)類,所以要去LocationManagerService去找reportLocation究竟做了什么债沮,定位結(jié)果是怎么返給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發(fā)送了一個(gè)MSG_LOCATION_CHANGED的消息,還是很符合谷歌的風(fēng)格的疫衩;
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 }
那么進(jìn)入case分支硅蹦,查看它到底做了什么,原來是調(diào)用了handleLocationChanged方法闷煤,而它又調(diào)用了handleLocationChangedLocked方法童芹,我們來繼續(xù)跟進(jìn);
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方法里先對(duì)mLastLocation做了更新鲤拿,然后遍歷所有UpdateRecord假褪,根據(jù)LocationRequest請(qǐng)求的精度來確定返回粗略位置還是精確位置。
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 }
這里會(huì)走一個(gè)shouldBroadcastSafe的判斷皆愉,看看返回的時(shí)間間隔和位置差值是否符合用戶的傳入嗜价,這里就把客戶端傳來的參數(shù)用到了艇抠,比如App5min要一次位置,位置差在50米以外才回調(diào)久锥,這個(gè)方法就是判斷這個(gè)的家淤。
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 }
如果符合條件,就會(huì)回調(diào)客戶端的listener瑟由,這個(gè)listener是封裝在Receiver里的絮重,注意這次回調(diào)是跨進(jìn)程的,會(huì)拋出RemoteException歹苦,如果拋出該異常青伤,就認(rèn)為客戶端已經(jīng)被殺,那么這個(gè)receiver將被標(biāo)記為死亡狀態(tài)并加入一個(gè)列表中殴瘦。在callLocationChangedLocaked方法里就會(huì)回調(diào)客戶端Listener的onLocationChanged方法狠角,并把Location回傳回去,如果客戶端不是用的Listener而是用廣播的形式來接受數(shù)據(jù)蚪腋,那么就會(huì)發(fā)送廣播丰歌。回調(diào)完之后會(huì)走一個(gè)方法 r.mRealRequest.decrementNumUpdates()屉凯,把LocationRequest里的計(jì)數(shù)器減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 }
然后看是否需要回調(diào)callStatusChangedLocked方法,它內(nèi)部也是回調(diào)Listener或者發(fā)送廣播悠砚。
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 }
接著會(huì)判斷LocationRequest的numUpdates的個(gè)數(shù)晓勇,這個(gè)在一開始分析LocationMananger的時(shí)候說到過,調(diào)用requestSingleUpdate會(huì)把這個(gè)變量設(shè)置為1灌旧,而它減1是在回調(diào)listener那里绑咱,所以單次請(qǐng)求到這里就會(huì)結(jié)束,它被加入到一個(gè)deadUpdateRecords的列表中节榜。最后對(duì)回調(diào)失敗的客戶端也就是被殺死的客戶端進(jìn)行清理羡玛,通過調(diào)用removeUpdatesLocked釋放資源。然后對(duì)加入到deadUpdateRecords里的請(qǐng)求做一個(gè)釋放宗苍,再執(zhí)行一次applyRequirementsLocked方法稼稿。
需要注意的是這里有2個(gè)列表,一個(gè)是客戶端已經(jīng)被殺死的列表讳窟,針對(duì)這個(gè)列表執(zhí)行的方法是removeUpdatesLocked让歼,另一個(gè)是deadUpdateRecords的列表,對(duì)它可以簡單理解成單次定位而非持續(xù)定位的請(qǐng)求丽啡,也就是通過requestSingleUpdate調(diào)用產(chǎn)生的請(qǐng)求谋右,這些請(qǐng)求執(zhí)行完上報(bào)位置后要做釋放,調(diào)用的是Receiver的disposeLocked方法补箍,同時(shí)再執(zhí)行applyRequirementsLocked方法改执,而這個(gè)方法又會(huì)回調(diào)setRequest方法啸蜜。
一開始我怎么也看不懂這是啥意思,后來用demo反復(fù)測試后終于搞明白了辈挂,requestSingleUpdate方法會(huì)觸發(fā)2次onSetRequest方法衬横,這在log里有顯示,第一次是App發(fā)起的终蒂,傳入的interval是0(vivo手機(jī)經(jīng)過修改是2ms)蜂林,上報(bào)位置后又回調(diào)了一次onSetRequest,這次傳的是OFF拇泣,并且沒有worksource噪叙,也就是終止定位了,這正好和源碼里一次單次定位回調(diào)2次onSetRequest是吻合的霉翔。當(dāng)然這是在只有一個(gè)App定位的情形下測試的睁蕾,如果2個(gè)App一起定位,另一個(gè)是持續(xù)定位早龟,時(shí)間間隔是20s惫霸,那么requestSingleUpdate的第二次onSetRequest回調(diào)傳回來的參數(shù)就是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 }
接著我們繼續(xù)看下removeUpdatesLocked方法里做了什么猜丹,如果客戶端傳入的是listener芝加,那么讓它死亡,調(diào)用receiver.getListener().asBinder().unlinkToDeath(receiver, 0)方法射窒,然后取出UpdateRecord藏杖,執(zhí)行它的disposeLocked(false)方法,這個(gè)方法傳入false就是從全局的記錄UpdateRecord列表對(duì)象mRecordsByProvider中移除該記錄脉顿。這是對(duì)死亡的客戶端做一次回調(diào)蝌麸。如果是單次定位的請(qǐng)求則調(diào)用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 }
至此一次定位的流程就算是走完了来吩,可以看出一次定位可能會(huì)多次回調(diào)onSetRequest方法,主要是requestSingleUpdate蔽莱,它會(huì)把numUpdates變量置為1弟疆,而默認(rèn)值則是一個(gè)大數(shù)Integer.MAX_VALUE = 0x7fffffff,伴隨著每次reportLocation計(jì)數(shù)器減1盗冷,單次定位只會(huì)執(zhí)行一次怠苔,并且歸零后再回調(diào)一次onSetRequest方法,根據(jù)情況傳入OFF仪糖。持續(xù)定位則不會(huì)回調(diào)2次柑司,因?yàn)檫@個(gè)大數(shù)要遞減N多次才會(huì)為0迫肖,我們之前理解的一次定位只回調(diào)一次onSetRequest是錯(cuò)誤的。
移除定位請(qǐng)求其實(shí)上面也說到了攒驰,它調(diào)用removeUpdates蟆湖,根據(jù)傳入的listener來找到對(duì)應(yīng)的Receiver,然后調(diào)用removeUpdatesLocked方法讼育,讓監(jiān)聽死亡帐姻,并調(diào)用record.disposeLocked(false)來銷毀記錄,然后再走一次applyRequirementsLocked方法奶段。也就是說removeUpdates也會(huì)回調(diào)一次onSetRequest方法饥瓷,這和log顯示的是一致的,之前一直不明白為什么移除定位的時(shí)候也要發(fā)一次請(qǐng)求并傳入OFF痹籍,這回看源碼就懂了呢铆。但這是針對(duì)單個(gè)App的情形,requestUpdate之后調(diào)用removeUpdates會(huì)回調(diào)onSetRequest并傳入OFF蹲缠,但多個(gè)App一起請(qǐng)求下就會(huì)有變化棺克,onSetRequest仍然會(huì)回調(diào),但是是否傳入OFF要看這個(gè)請(qǐng)求是否是最后一個(gè)請(qǐng)求线定,否則傳入的是ON娜谊,頻率是ProviderRequest的最小頻率。所以O(shè)FF是針對(duì)所有請(qǐng)求都關(guān)閉的時(shí)候才會(huì)回傳斤讥。
我們看到其實(shí)applyRequirementsLocked這是個(gè)關(guān)鍵方法纱皆,不論是初始化,還是requestUpdate芭商,removeUpdates派草,reportLocation都會(huì)調(diào)用它,它就是一個(gè)應(yīng)用參數(shù)標(biāo)準(zhǔn)铛楣,每當(dāng)有變動(dòng)就去檢查近迁,變更一下定位頻率,并回調(diào)onSetRequest方法簸州。所以onSetRequest方法就是參數(shù)(主要就是定位頻率)發(fā)生了變更后的回調(diào)鉴竭,而不是之前我們所理解的定一次位就回調(diào)一次,每當(dāng)定位頻率發(fā)生改變這個(gè)方法就會(huì)回調(diào)勿侯。
在實(shí)際測試過程中拓瞪,我發(fā)現(xiàn)三星測試機(jī)不回傳OFF,并且如果先調(diào)用持續(xù)定位助琐,再調(diào)用單次定位后祭埂,持續(xù)定位會(huì)被抵消,參數(shù)傳入了ON并且把時(shí)間設(shè)置成了12h,而vivo修改了這塊邏輯蛆橡,在持續(xù)定位時(shí)插入一條單次定位舌界,不會(huì)回傳OFF,而是傳入ON泰演,并且頻率是當(dāng)前最小的頻率呻拌,這個(gè)還是比較合理的。
七. 總結(jié)分享
在上面走讀源碼的過程中睦焕,也分析到了很多和第三方NLP相關(guān)的點(diǎn)藐握,這里再簡單概括一下:
1.NLP提供商怎么配置,配置的這些參數(shù)是如何生效的垃喊,系統(tǒng)在哪里調(diào)用到了猾普;
2.NLP提供商要用一個(gè)Service去綁定系統(tǒng)的Action,并間接實(shí)現(xiàn)ILocationProvider的方法本谜,也就是繼成LocationProviderBase類初家;
3.這個(gè)Service要配置service_version才能被正常解析;
4.NLP提供商要在onSetRequest方法里接收參數(shù)并實(shí)現(xiàn)定位功能乌助,把結(jié)果上報(bào)回去溜在,考慮到既有單次定位又有持續(xù)定位,可以開啟一個(gè)定時(shí)器隊(duì)列來實(shí)現(xiàn)他托,另外傳入OFF的時(shí)候是不需要上報(bào)位置的掖肋;
5.onEnable可以理解為是一個(gè)初始化方法,onDisable可以理解為是銷毀方法赏参,可以在這2個(gè)方法里做初始化和釋放資源的事情培遵;
6.當(dāng)Provider狀態(tài)發(fā)生變更時(shí)通過onGetStatus和onGetStatusUpdateTime方法來改變狀態(tài)和時(shí)間;
我把看源碼過程中的其他收獲也來分享一下登刺;
1.WorkSource:就是定位來源,它由uid和包名共同組成嗡呼,如果有多個(gè)纸俭,他們會(huì)以", "進(jìn)行分割,一個(gè)逗號(hào)加一個(gè)空格南窗,這個(gè)在做零秒定位解析worksource時(shí)產(chǎn)生過異常揍很,因?yàn)槟莻€(gè)空格沒看出來,源碼里確實(shí)是有的万伤;
2.ProviderProperties:做重構(gòu)的時(shí)候看到這個(gè)變量不知道是干嘛的窒悔,傳了一堆boolean值,現(xiàn)在知道他其實(shí)是和App層的Criteria類似的敌买。
private static ProviderPropertiesUnbundled PROPERTIES = ProviderPropertiesUnbundled.create(true, false, true, false, false, false, false, 1, 1);
Criteria是一組標(biāo)準(zhǔn)简珠,比如是否支持海拔,支持方向,需要付費(fèi)聋庵,高精度等等膘融,App層可以指定一系列條件來獲取到適合的Provider(也有可能沒有滿足條件的),那這些參數(shù)傳遞到NLP后就變成了ProviderProperties這個(gè)對(duì)象祭玉,這個(gè)變量是LocationProviderBase的構(gòu)造函數(shù)需要的氧映。
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提供商要實(shí)現(xiàn)的一個(gè)方法,它就是獲取Provider的狀態(tài)的脱货,一共有3種岛都,在onEnable和onDisable后要更新狀態(tài),這樣App才會(huì)在Listenter中的onStatusChanged方法里接收到正確的狀態(tài)回調(diào)振峻。
LocationProvider.OUT_OF_SERVICE = 0:無服務(wù)
LocationProvider.AVAILABLE = 2:provider可用
LocationProvider.TEMPORARILY_UNAVAILABLE = 1:provider不可用
4.onGetStatusUpdateTime:這個(gè)方法和上面是一樣的臼疫,它是狀態(tài)發(fā)生更新的時(shí)間,但要注意同樣的狀態(tài)不要更新2次铺韧,所以在上一個(gè)方法里要判斷本次狀態(tài)和之前是否相同多矮,不同再去記錄一下時(shí)間,谷歌讓我們使用SystemClock.elapsedRealtime方法來進(jìn)行設(shè)置哈打。
5.GeocoderProxy的實(shí)現(xiàn)方式和LocationProviderProxy類似塔逃,也都是通過ServiceWatcher去讀取配置,綁定對(duì)應(yīng)的Service料仗,IGeocodeProvider定義的AIDL接口GeocodeProvider已經(jīng)實(shí)現(xiàn)了湾盗,我們只需要繼承它就可以了,這個(gè)類更簡單一共2個(gè)方法立轧,一個(gè)是onGetFromLocationName格粪,一個(gè)是onGetFromLocation。同理地理圍欄GeofenceProvider也是一樣的代理實(shí)現(xiàn)氛改。
最后介紹下分析一個(gè)類遵循的主要步驟:
1.明確類的主要作用:顧名思義帐萎,LocationManagerService主要是用來提供定位服務(wù)的;
2.分析類的主要字段:LocationManagerService的很多字段都是圍繞provider來展開的胜卤,所以它主要是用來管理provider的疆导;
(1) mEnabledProviders:可用的Provider集合;
(2) mRecordsByProvider:定位請(qǐng)求Update的集合葛躏;
(3) mLastLocation:最近一次的定位信息澈段,以 Location Provider 的名稱為鍵的映射
3.理解類的初始化過程:LocationManagerService的初始化主要就是加載各種Provider,其中NetworkLocationPrivider和GeocoderProvider是通過ServiceWatcher去配置中讀取綁定的舰攒;
4.理解類的主要業(yè)務(wù)邏輯方法:
(1) requestLocationUpdatesLocked:請(qǐng)求定位的方法败富,插入一條記錄;
(2) removeUpdatesLocked:移除定位請(qǐng)求的方法摩窃,移除一條記錄兽叮;
(3) applyRequirementsLocked:各種配置變更都會(huì)走該方法,它會(huì)回調(diào)setRequest方法;
(4) handleLocationChanged:上報(bào)位置時(shí)會(huì)觸發(fā)該方法充择,這里會(huì)把結(jié)果回調(diào)給App德玫;
5.分析類中的其他成員方法和內(nèi)部類:
上面的4個(gè)方法其實(shí)主要是從大方向上去分析用到的,但是真正到了一些具體實(shí)現(xiàn)或者細(xì)節(jié)方面的東西還得看一些輔助的方法和內(nèi)部類椎麦,比如UpdateRecord宰僧,Receiver這些關(guān)鍵類的關(guān)鍵方法,真正把位置回調(diào)給App的就是在Receiver的callLocationChangedLocaked方法中观挎;
6.分析與這個(gè)類緊密相關(guān)的其他類:在第五部分介紹過了琴儿,主要有十幾個(gè)相關(guān)的類吧;
八. 收尾
在分析源碼的過程中嘁捷,我疏通了很多過去搞不懂的盲點(diǎn)造成,而且對(duì)于Android當(dāng)中的Binder機(jī)制有了更深的理解,一開始真沒看出來是雙重CS模型雄嚣,看懂CS之后就很明朗了晒屎,另外對(duì)于Binder的死亡機(jī)制也有了更多理解,還有就是對(duì)定位這種架構(gòu)的設(shè)計(jì)有了一些體會(huì)缓升,其中不乏諸多設(shè)計(jì)模式的使用鼓鲁,Java的高級(jí)編程知識(shí),同步鎖的控制港谊,面向抽象接口編程骇吭,封裝參數(shù)等等,收獲確實(shí)不少歧寺。
當(dāng)然走讀源碼的過程并不是很順暢燥狰,中間有一段時(shí)間卡住怎么也過不去,看不懂斜筐,遇到這種情況怎么辦龙致?別怕,多讀幾遍就懂了顷链。