Android網(wǎng)絡(luò)定位源碼分析[下]

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í)間卡住怎么也過不去,看不懂斜筐,遇到這種情況怎么辦龙致?別怕,多讀幾遍就懂了顷链。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末净当,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蕴潦,更是在濱河造成了極大的恐慌,老刑警劉巖俘闯,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潭苞,死亡現(xiàn)場離奇詭異,居然都是意外死亡真朗,警方通過查閱死者的電腦和手機(jī)此疹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蝗碎,你說我怎么就攤上這事湖笨。” “怎么了蹦骑?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵慈省,是天一觀的道長。 經(jīng)常有香客問我眠菇,道長边败,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任捎废,我火速辦了婚禮笑窜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘登疗。我一直安慰自己排截,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布辐益。 她就那樣靜靜地躺著断傲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荷腊。 梳的紋絲不亂的頭發(fā)上艳悔,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音女仰,去河邊找鬼猜年。 笑死,一個(gè)胖子當(dāng)著我的面吹牛疾忍,可吹牛的內(nèi)容都是我干的乔外。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼一罩,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼杨幼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起聂渊,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤差购,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后汉嗽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體欲逃,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年饼暑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稳析。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洗做。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖彰居,靈堂內(nèi)的尸體忽然破棺而出诚纸,到底是詐尸還是另有隱情,我是刑警寧澤陈惰,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布畦徘,位于F島的核電站,受9級(jí)特大地震影響奴潘,放射性物質(zhì)發(fā)生泄漏旧烧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一画髓、第九天 我趴在偏房一處隱蔽的房頂上張望掘剪。 院中可真熱鬧,春花似錦奈虾、人聲如沸夺谁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匾鸥。三九已至,卻和暖如春碉纳,著一層夾襖步出監(jiān)牢的瞬間勿负,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工劳曹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奴愉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓铁孵,卻偏偏與公主長得像锭硼,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜕劝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容