跨進(jìn)程廣播的工作原理(3)

通過廣播Intent來查找對(duì)應(yīng)廣播接收者的具體實(shí)現(xiàn)

上一篇我們粗略的走完了一遍廣播發(fā)送的主流程,但一些詳細(xì)的具體實(shí)現(xiàn)沒有仔細(xì)研讀露该,所以接下來我們要一點(diǎn)一點(diǎn)的給補(bǔ)回去底靠。這個(gè)篇章我們主要研究廣播意圖和廣播接收者的配對(duì)查找,看看谷歌究竟是怎么實(shí)現(xiàn)的!
上一篇文章我們說到廣播接收者查詢分為了靜態(tài)廣播查詢和動(dòng)態(tài)廣播查詢询筏,此次我們先分析靜態(tài)廣播接收者查詢。


image.png

切入入口就從廣播發(fā)送的主流程那邊開始帚稠,首先我們先分析collectReceiverComponents方法的內(nèi)部實(shí)現(xiàn):

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    ...
    private List<ResolveInfo> collectReceiverComponents(Intent intent, String resolvedType,
            int callingUid, int[] users) {
        // TODO: come back and remove this assumption to triage all broadcasts
        int pmFlags = STOCK_PM_FLAGS | MATCH_DEBUG_TRIAGED_MISSING;

        List<ResolveInfo> receivers = null;
        try {
            HashSet<ComponentName> singleUserReceivers = null;
            boolean scannedFirstReceivers = false;
            for (int user : users) {//循環(huán)獲取各用戶下的廣播接收者
                // Skip users that have Shell restrictions, with exception of always permitted
                //跳過具有Shell限制的用戶
                // Shell broadcasts
                if (callingUid == SHELL_UID
                        && mUserController.hasUserRestriction(
                                UserManager.DISALLOW_DEBUGGING_FEATURES, user)
                        && !isPermittedShellBroadcast(intent)) {
                    continue;
                }
                //查找匹配Intent 的廣播接收者
                List<ResolveInfo> newReceivers = AppGlobals.getPackageManager()
                        .queryIntentReceivers(intent, resolvedType, pmFlags, user).getList();
                if (user != UserHandle.USER_SYSTEM && newReceivers != null) {
                    // If this is not the system user, we need to check for
                    // any receivers that should be filtered out.
                    //如果不是系統(tǒng)用戶影兽,檢查過濾廣播接收者
                    for (int i=0; i<newReceivers.size(); i++) {
                        ResolveInfo ri = newReceivers.get(i);
                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_SYSTEM_USER_ONLY) != 0) {
                            newReceivers.remove(i);
                            i--;
                        }
                    }
                }
                if (newReceivers != null && newReceivers.size() == 0) {
                    newReceivers = null;
                }
                if (receivers == null) {
                    receivers = newReceivers;
                } else if (newReceivers != null) {
                    // We need to concatenate the additional receivers
                    // found with what we have do far.  This would be easy,
                    // but we also need to de-dup any receivers that are
                    // singleUser.
                    if (!scannedFirstReceivers) {
                        //掃描第一個(gè)用戶對(duì)應(yīng)的廣播接收者拇砰,并篩選出單個(gè)用戶廣播接收者
                        // Collect any single user receivers we had already retrieved.
                        scannedFirstReceivers = true;
                        for (int i=0; i<receivers.size(); i++) {
                            ResolveInfo ri = receivers.get(i);
                            if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
                                ComponentName cn = new ComponentName(
                                        ri.activityInfo.packageName, ri.activityInfo.name);
                                if (singleUserReceivers == null) {
                                    singleUserReceivers = new HashSet<ComponentName>();
                                }
                                singleUserReceivers.add(cn);
                            }
                        }
                    }
                    // Add the new results to the existing results, tracking
                    // and de-dupping single user receivers.
                    //循環(huán)遍歷新的接收者列表
                    for (int i=0; i<newReceivers.size(); i++) {
                        ResolveInfo ri = newReceivers.get(i);
                        if ((ri.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) {
                            //篩選出單個(gè)用戶廣播接收者甥材,避免多次發(fā)送
                            ComponentName cn = new ComponentName(
                                    ri.activityInfo.packageName, ri.activityInfo.name);
                            if (singleUserReceivers == null) {
                                singleUserReceivers = new HashSet<ComponentName>();
                            }
                            if (!singleUserReceivers.contains(cn)) {
                                singleUserReceivers.add(cn);
                                receivers.add(ri);
                            }
                        } else {
                            receivers.add(ri);
                        }
                    }
                }
            }
        } catch (RemoteException ex) {
            // pm is in same process, this will never happen.
        }
        return receivers;
    }
    ...
}

沿著AppGlobals.getPackageManager().queryIntentReceivers()走下去,最終你會(huì)發(fā)現(xiàn)靜態(tài)廣播接收者的查詢是在系統(tǒng)包管理服務(wù)PackageManagerService 里面實(shí)現(xiàn)的赫蛇。

public class PackageManagerService extends IPackageManager.Stub
        implements PackageSender {
    ...
    public @NonNull ParceledListSlice<ResolveInfo> queryIntentReceivers(Intent intent,
            String resolvedType, int flags, int userId) {
        return new ParceledListSlice<>(
                queryIntentReceiversInternal(intent, resolvedType, flags, userId));
    }

    private @NonNull List<ResolveInfo> queryIntentReceiversInternal(Intent intent,
            String resolvedType, int flags, int userId) {
        //用戶是否存在校驗(yàn)
        if (!sUserManager.exists(userId)) return Collections.emptyList();
        final int callingUid = Binder.getCallingUid();
        final String instantAppPkgName = getInstantAppPackageName(callingUid);
        flags = updateFlagsForResolve(flags, userId, intent, callingUid,
                false /*includeInstantApps*/);
        //獲取廣播接收者組件信息移迫,包括包名和類名
        ComponentName comp = intent.getComponent();
        if (comp == null) {
            if (intent.getSelector() != null) {
                intent = intent.getSelector();
                comp = intent.getComponent();
            }
        }
        if (comp != null) {//組件不為空,則說明不用去查找,直接可以組裝并返回ResolveInfo
            final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
            //獲取此次活動(dòng)的相關(guān)信息
            final ActivityInfo ai = getReceiverInfo(comp, flags, userId);
            if (ai != null) {
                // When specifying an explicit component, we prevent the activity from being
                // used when either 1) the calling package is normal and the activity is within
                // an instant application or 2) the calling package is ephemeral and the
                // activity is not visible to instant applications.
                //兩個(gè)明確的情況下叮姑,此次活動(dòng)將被阻止
                //1托享、調(diào)用包正常传惠,活動(dòng)在Instant APP中運(yùn)行
                //2浇坐、調(diào)用包是臨時(shí)的摇予,活動(dòng)對(duì)Instant APP不可見。
                final boolean matchInstantApp =
                        (flags & PackageManager.MATCH_INSTANT) != 0;
                final boolean matchVisibleToInstantAppOnly =
                        (flags & PackageManager.MATCH_VISIBLE_TO_INSTANT_APP_ONLY) != 0;
                final boolean matchExplicitlyVisibleOnly =
                        (flags & PackageManager.MATCH_EXPLICITLY_VISIBLE_ONLY) != 0;
                final boolean isCallerInstantApp =
                        instantAppPkgName != null;
                final boolean isTargetSameInstantApp =
                        comp.getPackageName().equals(instantAppPkgName);
                final boolean isTargetInstantApp =
                        (ai.applicationInfo.privateFlags
                                & ApplicationInfo.PRIVATE_FLAG_INSTANT) != 0;
                final boolean isTargetVisibleToInstantApp =
                        (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0;
                final boolean isTargetExplicitlyVisibleToInstantApp =
                        isTargetVisibleToInstantApp
                        && (ai.flags & ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP) == 0;
                final boolean isTargetHiddenFromInstantApp =
                        !isTargetVisibleToInstantApp
                        || (matchExplicitlyVisibleOnly && !isTargetExplicitlyVisibleToInstantApp);
                final boolean blockResolution =
                        !isTargetSameInstantApp
                        && ((!matchInstantApp && !isCallerInstantApp && isTargetInstantApp)
                                || (matchVisibleToInstantAppOnly && isCallerInstantApp
                                        && isTargetHiddenFromInstantApp));
                if (!blockResolution) {
                    ResolveInfo ri = new ResolveInfo();
                    ri.activityInfo = ai;
                    list.add(ri);
                }
            }
            return applyPostResolutionFilter(list, instantAppPkgName);//過濾臨時(shí)活動(dòng)
        }

        // reader
        synchronized (mPackages) {
            String pkgName = intent.getPackage();
            if (pkgName == null) {
                //通過mReceivers查詢獲取ResolveInfo列表
                final List<ResolveInfo> result =
                        mReceivers.queryIntent(intent, resolvedType, flags, userId);
                return applyPostResolutionFilter(result, instantAppPkgName);
            }
            final PackageParser.Package pkg = mPackages.get(pkgName);
            if (pkg != null) {
                //查詢mReceivers獲取ResolveInfo列表
                final List<ResolveInfo> result = mReceivers.queryIntentForPackage(
                        intent, resolvedType, flags, pkg.receivers, userId);
                return applyPostResolutionFilter(result, instantAppPkgName);
            }
            return Collections.emptyList();
        }
    }

    private List<ResolveInfo> applyPostResolutionFilter(List<ResolveInfo> resolveInfos,
            String ephemeralPkgName) {
        for (int i = resolveInfos.size() - 1; i >= 0; i--) {
            final ResolveInfo info = resolveInfos.get(i);
            final boolean isEphemeralApp = info.activityInfo.applicationInfo.isInstantApp();
            // TODO: When adding on-demand split support for non-instant apps, remove this check
            // and always apply post filtering
            // allow activities that are defined in the provided package
            if (isEphemeralApp) {
                if (info.activityInfo.splitName != null
                        && !ArrayUtils.contains(info.activityInfo.applicationInfo.splitNames,
                                info.activityInfo.splitName)) {
                    // requested activity is defined in a split that hasn't been installed yet.
                    // add the installer to the resolve list
                    if (DEBUG_EPHEMERAL) {
                        Slog.v(TAG, "Adding ephemeral installer to the ResolveInfo list");
                    }
                    final ResolveInfo installerInfo = new ResolveInfo(mInstantAppInstallerInfo);
                    installerInfo.auxiliaryInfo = new AuxiliaryResolveInfo(
                            info.activityInfo.packageName, info.activityInfo.splitName,
                            info.activityInfo.applicationInfo.versionCode, null /*failureIntent*/);
                    // make sure this resolver is the default
                    installerInfo.isDefault = true;
                    installerInfo.match = IntentFilter.MATCH_CATEGORY_SCHEME_SPECIFIC_PART
                            | IntentFilter.MATCH_ADJUSTMENT_NORMAL;
                    // add a non-generic filter
                    installerInfo.filter = new IntentFilter();
                    // load resources from the correct package
                    installerInfo.resolvePackageName = info.getComponentInfo().packageName;
                    resolveInfos.set(i, installerInfo);
                    continue;
                }
            }
            // caller is a full app, don't need to apply any other filtering
            if (ephemeralPkgName == null) {
                continue;
            } else if (ephemeralPkgName.equals(info.activityInfo.packageName)) {
                // caller is same app; don't need to apply any other filtering
                continue;
            }
            // allow activities that have been explicitly exposed to ephemeral apps
            if (!isEphemeralApp
                    && ((info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) != 0)) {
                continue;
            }
            resolveInfos.remove(i);
        }
        return resolveInfos;
    }
  ...
}

從上面的代碼邏輯來看吗跋,在發(fā)送廣播時(shí)把對(duì)應(yīng)的包名和類名帶上,廣播發(fā)送的效率會(huì)有很大的提高,因?yàn)槠渲腥鄙倭俗詈臅r(shí)的Intent查找匹配邏輯跌宛!而最終的匹配查找是在變量mReceivers中實(shí)現(xiàn)的酗宋,那么mReceivers究竟做了什么呢?

public class PackageManagerService extends IPackageManager.Stub
        implements PackageSender {
    ...
    // All available receivers, for your resolving pleasure.
    final ActivityIntentResolver mReceivers = new ActivityIntentResolver();

    final class ActivityIntentResolver
            extends IntentResolver<PackageParser.ActivityIntentInfo, ResolveInfo> {
         ....
        public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags,
                int userId) {
            if (!sUserManager.exists(userId)) return null;
            mFlags = flags;
            return super.queryIntent(intent, resolvedType,
                    (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0,
                    userId);
        }

        public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType,
                int flags, ArrayList<PackageParser.Activity> packageActivities, int userId) {
            if (!sUserManager.exists(userId)) return null;
            if (packageActivities == null) {
                return null;
            }
            mFlags = flags;
            final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0;
            final int N = packageActivities.size();
            ArrayList<PackageParser.ActivityIntentInfo[]> listCut =
                new ArrayList<PackageParser.ActivityIntentInfo[]>(N);

            ArrayList<PackageParser.ActivityIntentInfo> intentFilters;
            for (int i = 0; i < N; ++i) {
                intentFilters = packageActivities.get(i).intents;
                if (intentFilters != null && intentFilters.size() > 0) {
                    PackageParser.ActivityIntentInfo[] array =
                            new PackageParser.ActivityIntentInfo[intentFilters.size()];
                    intentFilters.toArray(array);
                    listCut.add(array);
                }
            }
            return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId);
        }
        ....
    }
    ...
}

沒錯(cuò)疆拘,事實(shí)上的查找邏輯是寫在父類IntentResolver里面的:

public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
    ....
    public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly,
            int userId) {
        String scheme = intent.getScheme();

        ArrayList<R> finalList = new ArrayList<R>();

        final boolean debug = localLOGV ||
                ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0);

        if (debug) Slog.v(
            TAG, "Resolving type=" + resolvedType + " scheme=" + scheme
            + " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent);

        F[] firstTypeCut = null;
        F[] secondTypeCut = null;
        F[] thirdTypeCut = null;
        F[] schemeCut = null;

        // If the intent includes a MIME type, then we want to collect all of
        // the filters that match that MIME type.
        //通過MIME type來匹配Intent
        if (resolvedType != null) {
            int slashpos = resolvedType.indexOf('/');
            if (slashpos > 0) {
                final String baseType = resolvedType.substring(0, slashpos);
                if (!baseType.equals("*")) {
                    if (resolvedType.length() != slashpos+2
                            || resolvedType.charAt(slashpos+1) != '*') {
                        // Not a wild card, so we can just look for all filters that
                        // completely match or wildcards whose base type matches.
                        firstTypeCut = mTypeToFilter.get(resolvedType);
                        if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut));
                        secondTypeCut = mWildTypeToFilter.get(baseType);
                        if (debug) Slog.v(TAG, "Second type cut: "
                                + Arrays.toString(secondTypeCut));
                    } else {
                        // We can match anything with our base type.
                        firstTypeCut = mBaseTypeToFilter.get(baseType);
                        if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut));
                        secondTypeCut = mWildTypeToFilter.get(baseType);
                        if (debug) Slog.v(TAG, "Second type cut: "
                                + Arrays.toString(secondTypeCut));
                    }
                    // Any */* types always apply, but we only need to do this
                    // if the intent type was not already */*.
                    thirdTypeCut = mWildTypeToFilter.get("*");
                    if (debug) Slog.v(TAG, "Third type cut: " + Arrays.toString(thirdTypeCut));
                } else if (intent.getAction() != null) {
                    // The intent specified any type ({@literal *}/*).  This
                    // can be a whole heck of a lot of things, so as a first
                    // cut let's use the action instead.
                    firstTypeCut = mTypedActionToFilter.get(intent.getAction());
                    if (debug) Slog.v(TAG, "Typed Action list: " + Arrays.toString(firstTypeCut));
                }
            }
        }

        // If the intent includes a data URI, then we want to collect all of
        // the filters that match its scheme (we will further refine matches
        // on the authority and path by directly matching each resulting filter).
        //通過scheme來匹配
        if (scheme != null) {
            schemeCut = mSchemeToFilter.get(scheme);
            if (debug) Slog.v(TAG, "Scheme list: " + Arrays.toString(schemeCut));
        }

        // If the intent does not specify any data -- either a MIME type or
        // a URI -- then we will only be looking for matches against empty
        // data.
        //通過action來匹配
        if (resolvedType == null && scheme == null && intent.getAction() != null) {
            firstTypeCut = mActionToFilter.get(intent.getAction());
            if (debug) Slog.v(TAG, "Action list: " + Arrays.toString(firstTypeCut));
        }
        //將查找出來的封裝成為目標(biāo)對(duì)象列表并返回
        FastImmutableArraySet<String> categories = getFastIntentCategories(intent);
        if (firstTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, firstTypeCut, finalList, userId);
        }
        if (secondTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, secondTypeCut, finalList, userId);
        }
        if (thirdTypeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, thirdTypeCut, finalList, userId);
        }
        if (schemeCut != null) {
            buildResolveList(intent, categories, debug, defaultOnly, resolvedType,
                    scheme, schemeCut, finalList, userId);
        }
        filterResults(finalList);
        sortResults(finalList);

        if (debug) {
            Slog.v(TAG, "Final result list:");
            for (int i=0; i<finalList.size(); i++) {
                Slog.v(TAG, "  " + finalList.get(i));
            }
        }
        return finalList;
    }
    ....

    /**
     * All filters that have been registered.
     */
    private final ArraySet<F> mFilters = new ArraySet<F>();

    /**
     * All of the MIME types that have been registered, such as "image/jpeg",
     * "image/*", or "{@literal *}/*".
     */
    private final ArrayMap<String, F[]> mTypeToFilter = new ArrayMap<String, F[]>();

    /**
     * The base names of all of all fully qualified MIME types that have been
     * registered, such as "image" or "*".  Wild card MIME types such as
     * "image/*" will not be here.
     */
    private final ArrayMap<String, F[]> mBaseTypeToFilter = new ArrayMap<String, F[]>();

    /**
     * The base names of all of the MIME types with a sub-type wildcard that
     * have been registered.  For example, a filter with "image/*" will be
     * included here as "image" but one with "image/jpeg" will not be
     * included here.  This also includes the "*" for the "{@literal *}/*"
     * MIME type.
     */
    private final ArrayMap<String, F[]> mWildTypeToFilter = new ArrayMap<String, F[]>();

    /**
     * All of the URI schemes (such as http) that have been registered.
     */
    private final ArrayMap<String, F[]> mSchemeToFilter = new ArrayMap<String, F[]>();

    /**
     * All of the actions that have been registered, but only those that did
     * not specify data.
     */
    private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>();

    /**
     * All of the actions that have been registered and specified a MIME type.
     */
    private final ArrayMap<String, F[]> mTypedActionToFilter = new ArrayMap<String, F[]>();

}

從上面的代碼可以看出蜕猫,查詢廣播的目標(biāo)接收者其實(shí)是通過Map鍵值對(duì)來查找的,根據(jù)不同的專屬特征(Action哎迄、Scheme回右、Mime Typed等)來獲取對(duì)應(yīng)接收者,邏輯其實(shí)并不復(fù)雜漱挚。代碼讀到此處翔烁,我們難免會(huì)有疑問:mActionToFilter、mSchemeToFilter等這些過濾器是什么時(shí)候初始化的旨涝?靜態(tài)注冊(cè)的廣播是如何加載到這些Map里面的蹬屹?別急,代碼還需繼續(xù)看下去:

public abstract class IntentResolver<F extends IntentFilter, R extends Object> {
      ....
    public void addFilter(F f) {
        if (localLOGV) {
            Slog.v(TAG, "Adding filter: " + f);
            f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), "      ");
            Slog.v(TAG, "    Building Lookup Maps:");
        }

        mFilters.add(f);
        int numS = register_intent_filter(f, f.schemesIterator(),
                mSchemeToFilter, "      Scheme: ");
        int numT = register_mime_types(f, "      Type: ");
        if (numS == 0 && numT == 0) {
            register_intent_filter(f, f.actionsIterator(),
                    mActionToFilter, "      Action: ");
        }
        if (numT != 0) {
            register_intent_filter(f, f.actionsIterator(),
                    mTypedActionToFilter, "      TypedAction: ");
        }
    }
    private final int register_intent_filter(F filter, Iterator<String> i,
            ArrayMap<String, F[]> dest, String prefix) {
        if (i == null) {
            return 0;
        }

        int num = 0;
        while (i.hasNext()) {
            String name = i.next();
            num++;
            if (localLOGV) Slog.v(TAG, prefix + name);
            addFilter(dest, name, filter);
        }
        return num;
    }
    private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) {
        F[] array = map.get(name);
        if (array == null) {
            array = newArray(2);
            map.put(name,  array);
            array[0] = filter;
        } else {
            final int N = array.length;
            int i = N;
            while (i > 0 && array[i-1] == null) {
                i--;
            }
            if (i < N) {
                array[i] = filter;
            } else {
                F[] newa = newArray((N*3)/2);
                System.arraycopy(array, 0, newa, 0, N);
                newa[N] = filter;
                map.put(name, newa);
            }
        }
    }
}

全局查看IntentResolver的代碼白华,你會(huì)發(fā)現(xiàn)IntentResolver對(duì)外開放的過濾器數(shù)據(jù)添加接口只有一個(gè)addFilter(F f)慨默,而mReceivers中將這個(gè)接口的調(diào)用封裝到了addActivity(PackageParser.Activity a, String type)方法中,沿著方法被調(diào)用的地方查找弧腥,你會(huì)發(fā)現(xiàn)PackageManagerService的構(gòu)造函數(shù)中經(jīng)過一層層的封裝厦取,最終有觸發(fā)IntentResolver.addActivity()的調(diào)用,此處應(yīng)是用作初始化管搪;同時(shí)在PackageHandler mHandler 中也有觸發(fā)調(diào)用虾攻,此處應(yīng)是用作更新!此處代碼較多就不一一摘錄了抛蚤。

總結(jié)

通過上面的源碼分析台谢,我們可以總結(jié)出一下結(jié)論:
1、靜態(tài)廣播接收者的查找實(shí)際上是在包管理服務(wù)PackageManagerService 中實(shí)現(xiàn)的岁经。
2朋沮、廣播Intent中指定目標(biāo)接收者的包名和類名會(huì)跳過查找的步驟,極大的提高了廣播發(fā)送的效率缀壤。
3樊拓、靜態(tài)廣播接收者會(huì)在包管理服務(wù)PackageManagerService初始化的時(shí)候以鍵值對(duì)的形式被加載到內(nèi)存當(dāng)中,廣播Intent在查找對(duì)應(yīng)接收者的時(shí)候通過其所帶的Action塘慕、Scheme筋夏、MIME TYPE等數(shù)據(jù)來獲取對(duì)應(yīng)的接收者。當(dāng)然在有新android包安裝图呢、升級(jí)或卸載的時(shí)候条篷,靜態(tài)廣播接收者的數(shù)據(jù)也會(huì)實(shí)時(shí)更新骗随。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赴叹,隨后出現(xiàn)的幾起案子鸿染,更是在濱河造成了極大的恐慌,老刑警劉巖乞巧,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涨椒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡绽媒,警方通過查閱死者的電腦和手機(jī)蚕冬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來是辕,“玉大人囤热,你說我怎么就攤上這事∶飧猓” “怎么了赢乓?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長石窑。 經(jīng)常有香客問我牌芋,道長,這世上最難降的妖魔是什么松逊? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任躺屁,我火速辦了婚禮,結(jié)果婚禮上经宏,老公的妹妹穿的比我還像新娘犀暑。我一直安慰自己,他們只是感情好烁兰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布耐亏。 她就那樣靜靜地躺著,像睡著了一般沪斟。 火紅的嫁衣襯著肌膚如雪广辰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天主之,我揣著相機(jī)與錄音择吊,去河邊找鬼。 笑死槽奕,一個(gè)胖子當(dāng)著我的面吹牛几睛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粤攒,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼所森,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼囱持!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起必峰,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤洪唐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后吼蚁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡问欠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年肝匆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片顺献。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旗国,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出注整,到底是詐尸還是另有隱情能曾,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布肿轨,位于F島的核電站寿冕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏椒袍。R本人自食惡果不足惜驼唱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望驹暑。 院中可真熱鬧玫恳,春花似錦、人聲如沸优俘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帆焕。三九已至惭婿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間视搏,已是汗流浹背审孽。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留浑娜,地道東北人佑力。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像筋遭,于是被迫代替她去往敵國和親打颤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子暴拄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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

  • 1、動(dòng)態(tài)注冊(cè)過程源碼分析: 在Activity中動(dòng)態(tài)注冊(cè)廣播室编饺,在注冊(cè)方法之前其實(shí)省略了Context乖篷,也就是實(shí)際...
    騎著豬的蝸牛閱讀 717評(píng)論 0 1
  • 2.1 Activity 2.1.1 Activity的生命周期全面分析 典型情況下的生命周期:在用戶參與的情況下...
    AndroidMaster閱讀 3,029評(píng)論 0 8
  • Android系統(tǒng)的廣播機(jī)制是一種基于消息發(fā)布和訂閱的事件驅(qū)動(dòng)模型,即廣播發(fā)送者負(fù)責(zé)發(fā)布消息透且,而接收者需要先訂閱消...
    泡面先生_Jack閱讀 1,152評(píng)論 0 2
  • 1.Android廣播機(jī)制概述 Android廣播分為兩個(gè)方面:廣播發(fā)送者和廣播接收者撕蔼,通常情況下,Broadca...
    IT小魔女的故事閱讀 549評(píng)論 0 2
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程秽誊,因...
    小菜c閱讀 6,365評(píng)論 0 17