Android网络定位源码分析[下]

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的高级编程知识,同步锁的控制,面向抽象接口编程,封装参数等等,收获确实不少。

当然走读源码的过程并不是很顺畅,中间有一段时间卡住怎么也过不去,看不懂,遇到这种情况怎么办?别怕,多读几遍就懂了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,470评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,393评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,577评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,176评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,189评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,155评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,041评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,903评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,319评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,539评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,703评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,417评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,013评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,664评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,818评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,711评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,601评论 2 353

推荐阅读更多精彩内容