AMS分析:BroadcastReceiver管理

Android系統的廣播機制是一種基于消息發布和訂閱的事件驅動模型,即廣播發送者負責發布消息,而接收者需要先訂閱消息,才能接收到消息,Android系統中的廣播機制是BroadcastReceiver組件。

廣播接收者需要首先將自己注冊,最終他們是將自己注冊到了AMS服務中,當廣播發送者發送一個廣播的時候,首先發送到AMS服務中,然后由AMS服務發送給對應的接收者。

BroadCastReceiver注冊分析

首先我們來分析廣播接收者的注冊過程。我們知道,在Activity或者Service中注冊一個廣播接收者調用registerReceiver方法來接收,該方法最終調用ContextImpl方法來實現。

1. ContextImpl.registerReceiver

public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
            String broadcastPermission, Handler scheduler) {
        return registerReceiverInternal(receiver, getUserId(),
                filter, broadcastPermission, scheduler, getOuterContext());
    }
    
 private Intent registerReceiverInternal(BroadcastReceiver receiver, int userId,
            IntentFilter filter, String broadcastPermission,
            Handler scheduler, Context context) {
        IIntentReceiver rd = null;
        if (receiver != null) {
            if (mPackageInfo != null && context != null) {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = mPackageInfo.getReceiverDispatcher(
                    receiver, context, scheduler,
                    mMainThread.getInstrumentation(), true);
            } else {
                if (scheduler == null) {
                    scheduler = mMainThread.getHandler();
                }
                rd = new LoadedApk.ReceiverDispatcher(
                        receiver, context, scheduler, null, true).getIIntentReceiver();
            }
        }
        try {
            return ActivityManagerNative.getDefault().registerReceiver(
                    mMainThread.getApplicationThread(), mBasePackageName,
                    rd, filter, broadcastPermission, userId);
        }
    }

注冊的時候registerReceiver方法直接調用了registerReceiverInternal方法來實現具體的邏輯,這個方法比較簡單,mPackageInfo是一個LoadedApk類型的對象,如果mPackageInfo不為空,則根據receiver到LoadedApk中查找這個Receiver關聯的Binder對象,如果mPackageInfo為空著直接創建一個Binder對象和receiver關聯起來。最后通過進程間請求,調用AMS服務的registerReceiver方法來注冊該廣播

我們知道LoadedApk這個類代表了一個加載到內存中的應用程序,它里邊保存了一個應用程序的基本資源信息,包括ActivityThread,ApplicationInfo,包名,資源路徑,Lib路徑等信息,它同時還保存了所有注冊的廣播的信息。

private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
        = new ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>();

這個mReceivers中以Context為鍵值保存了一個Map類型的信息ArrayMap<BroadcastReceiver, ReceiverDispatcher>,一個Context代表了一個Activity或者一個Service,因為Activity和Service最終都實現了Context。以Context為鍵值查找的時候可以直接找到某個Activity或者Service中注冊的所有廣播接收者。

接著看ArrayMap<BroadcastReceiver, ReceiverDispatcher>,這個是以BroadCastReceiver為鍵值保存了一個ReceiverDispatcher的值,主要是為了將BroadcastReceiver和一個ReceiverDispatcher關聯起來。

首先我們來看下LoadedApk.ReceiverDispatcher這個類具體是什么作用。

static final class ReceiverDispatcher {
 
        final static class InnerReceiver extends IIntentReceiver.Stub {
            final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
            final LoadedApk.ReceiverDispatcher mStrongRef;
 
            InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
                mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
                mStrongRef = strong ? rd : null;
            }
            public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
                ……
            }
        }
        //一個Binder類型的對象,實現了Binder的服務端
        final IIntentReceiver.Stub mIIntentReceiver;
        //指向一個BoradCastReceiver
        final BroadcastReceiver mReceiver;
        //指向發送者的Context上下文信息
        final Context mContext;
        //指向發送者進程主線程的Hander對象,通過它可以向主線程發送消息
        final Handler mActivityThread;
        final Instrumentation mInstrumentation;
        
        ……

Receiver這個類中保存了一些變量,分別指向了發送者的一些信息,其中InnerReceiver類型的變量最重要,它實現了IIntentReceiver.Stub,是一個Binder類型的本地對象,屬于Binder服務端。

這個類的主要作用就是,把Binder對象通過進程間傳遞出去,其他進程要和這邊的Receiver通信的時候,直接調用該Binder的代理端,這邊的InnerReceiver就可以接收到消息,然后在通過主線程的Handler對象發送個主線程來處理這個消息。

接著看LoadedAPk.getReceiverDispatcher方法的實現。

public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
            Context context, Handler handler,
            Instrumentation instrumentation, boolean registered) {
        synchronized (mReceivers) {
            LoadedApk.ReceiverDispatcher rd = null;
            ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
            if (registered) {
                //根據當前的Context,查找關于這個Activity或者Service注冊的所有廣播接收者信息
                map = mReceivers.get(context);
                if (map != null) {
                    //找到這個廣播接收者關聯的一個Binder服務端對象
                    rd = map.get(r);
                }
            }
            if (rd == null) {
                //如果LoadedApk中沒有保存的話,就先創建一個,然后保存的LoadedAPk的mReceivers集合中
                rd = new ReceiverDispatcher(r, context, handler,
                        instrumentation, registered);
                if (registered) {
                    if (map == null) {
                        map = new ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
                        mReceivers.put(context, map);
                    }
                    map.put(r, rd);
                }
            } else {
                rd.validate(context, handler);
            }
            rd.mForgotten = false;
            return rd.getIIntentReceiver();
        }
    }

根據前面方法的分析看出,這個方法的作用是用來在LoadedApk中查找這個廣播接收者關聯的Binder對象的。如果有則直接返回,如果找不到則new一個ReceiverDispatcher對象,保存到mReceivers集合中,然后再返回Receiver關聯的Binder對象。

2. AMS.registerReceiver

public Intent registerReceiver(IApplicationThread caller, String callerPackage,
            IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
        ArrayList<Intent> stickyIntents = null;
        ProcessRecord callerApp = null;
        int callingUid;
        int callingPid;
        synchronized(this) {
            if (caller != null) {
                 //獲取調用注冊程序的進程信息和Uid,Pid
                callerApp = getRecordForAppLocked(caller);
               ……
                callingUid = callerApp.info.uid;
                callingPid = callerApp.pid;
            } 
 
            userId = handleIncomingUser(callingPid, callingUid, userId,
                    true, ALLOW_FULL_ONLY, "registerReceiver", callerPackage);
 
            Iterator<String> actions = filter.actionsIterator();
            ……
            int[] userIds = { UserHandle.USER_ALL, UserHandle.getUserId(callingUid) };
            //查詢所有的Action相關的粘性廣播,并保存到stickyIntents中
            while (actions.hasNext()) {
                String action = actions.next();
                for (int id : userIds) {
                    ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(id);
                    if (stickies != null) {
                        ArrayList<Intent> intents = stickies.get(action);
                        if (intents != null) {
                            if (stickyIntents == null) {
                                stickyIntents = new ArrayList<Intent>();
                            }
                            stickyIntents.addAll(intents);
                        }
                    }
                }
            }
        }
……
        synchronized (this) {
            //根據注冊進程傳入的binder對象從mRegisteredReceivers查找對應的ReceiverList
            //mRegisteredReceivers列表,以注冊進程傳入的receiver.asBinder為key,保存了ReceiverList
            ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder());
            //如果第一次調用則new一個ReceiverList,并添加到mRegisteredReceivers列表中
            if (rl == null) {
                rl = new ReceiverList(this, callerApp, callingPid, callingUid,
                        userId, receiver);
                if (rl.app != null) {
                    rl.app.receivers.add(rl);
                } else {
                    try {
                        receiver.asBinder().linkToDeath(rl, 0);
                    } catch (RemoteException e) {
                        return sticky;
                    }
                    rl.linkedToDeath = true;
                }
                mRegisteredReceivers.put(receiver.asBinder(), rl);
            }
            ……
            //創建一個BroadCastReceiver對象并保存到ReceiverList中,BroadcastFilter表示廣播的過濾條件,一個廣播接收者可以有多個過濾條件
            BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
                    permission, callingUid, userId);
            rl.add(bf);
            //同時也保存到mReceiverResolve列表中,這個列表保存了系統中所有動態注冊的廣播接收者所對應的BroadCastFilter
            mReceiverResolver.addFilter(bf);
 
            // Enqueue broadcasts for all existing stickies that match
            // this filter.
            if (allSticky != null) {
               ……
 
                final int stickyCount = allSticky.size();
                for (int i = 0; i < stickyCount; i++) {
                    //遍歷所有符合條件的stick廣播Intent,然后把它封裝成一個BroadcastRecord并添加到BroadcastQueue的ParallelBroadcastLocked列表
                    Intent intent = allSticky.get(i);
                    BroadcastQueue queue = broadcastQueueForIntent(intent);
                    BroadcastRecord r = new BroadcastRecord(queue, intent, null,
                            null, -1, -1, null, null, AppOpsManager.OP_NONE, null, receivers,
                            null, 0, null, null, false, true, true, -1);
                    queue.enqueueParallelBroadcastLocked(r);
                    queue.scheduleBroadcastsLocked();
                }
            }
 
            return sticky;
        }
    }

該方法的主要工作:

  1. 獲取調用程序的進程信息ProcessRecord及PID和Uid
  2. 從AMS的mStickyBroadcasts列表中查詢所有和action匹配的sticky廣播,并保存到allSticky列表中,該Action就是即將要注冊的廣播的Action。
  3. 我們知道參數receiver是一個IIntentReceiver類型的變量,是binder代理端,指向了應用進程要注冊的廣播Receiver,registerdReceivers是一個map類型的變量,它以receiver.asBInder為key保存了ReceiverList。這樣應用進程的一個Receiver就和AMS服務中的一個ReceiverList關聯起來了。

ReceiverList又是什么呢? 它繼承自ArrayList<BroadCastFilter>,同時它內部有一個IntentReceiver類型的變量指向一個Receiver,這樣一個Receiver就和多個BroadcastFilter關聯起來了。因為一個廣播可以有多個過濾條件。

第三步的主要工作就是從mRegisterReceivers中查找receiver對應的ReceiverList信息,如果為空,就表示該廣播沒有注冊,需要新創建一個ReceiverList并添加到mRegisterReceivers列表中。

  1. 創建一個BroadcastFilter,添加到上面的ReceiverList中,同時也添加到mReceiverResolver集合中,mReceiverResolver集合存儲了所有動態注冊的廣播的過濾條件BroadcastFilter,便于廣播發送的時候查找Receiver。
  2. 將第二步找到的和此接收者Action相關的sticky廣播遍歷,封裝成一個BroadcastRecord,加入隊列準備發送。

3. BroadCastQueue. scheduleBroadcastsLocked

public void scheduleBroadcastsLocked() {
        //發送Handler消息個AMS服務,通知AMS服務發送廣播
        mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));
        mBroadcastsScheduled = true;
    }

最終調用scheduleBroadcastsLocked方法,發送一個Hander消息給AMS服務,由AMS服務來發送廣播。

同時也了解了sticky廣播的實現原理,發送完的sticky廣播會保存到mStickyBraodcasts集合中,在注冊的時候根據action查找對應的sticky廣播,如有有相關的sticky廣播的話,注冊完成后,立刻發送出去,這樣剛注冊的Receiver就可以立刻受到sticky的廣播了

這樣廣播的動態注冊就完成了。經過分析可以看出,AMS服務是廣播的注冊中心,廣播接收者要想接收廣播消息需要將自己注冊到AMS服務中去,指定要接收的廣播類型,發送廣播也是首先發送給AMS服務,由AMS服務來找到對應的廣播接收者,然后在調用對應的廣播接收者來處理。

Broadcast的發送過程分析

廣播的發送方式分為有序廣播和無序廣播,在注冊廣播的時候可以指定廣播的優先級,這樣廣播就會優先發送給優先級比較高的廣播,然后才會發送給優先級比較低的廣播。而無序廣播則不管廣播接收者的優先級。

同樣要分析廣播的發送過程需要從ContextImpl類開始分析,因為它最終實現了sendBroadcast的接口和方法,service和activity發送廣播的過程都是調用ContextImpl的方法來實現的。

1. ContextImpl.sendBroadcast

public void sendBroadcast(Intent intent) {
        ……
        try {
            intent.prepareToLeaveProcess();
            ActivityManagerNative.getDefault().broadcastIntent(
                    mMainThread.getApplicationThread(), intent, resolvedType, null,
                    Activity.RESULT_OK, null, null, null, AppOpsManager.OP_NONE, null, false, false,
                    getUserId());
        } 
    }

ContextImpl的發送廣播的方法最終通過進程間通信請求,調用AMS服務的broadcastIntent方法。同時在參數中將發送者的ApplicationThread和廣播的intent傳遞給了AMS服務。

這個方法比較簡單,進程間通信方式不再解釋,我們直接看AMS服務的broadcastIntent方法

2. AMS.broascastIntent

 public final int broadcastIntent(IApplicationThread caller,
            Intent intent, String resolvedType, IIntentReceiver resultTo,
            int resultCode, String resultData, Bundle resultExtras,
            String[] requiredPermissions, int appOp, Bundle options,
            boolean serialized, boolean sticky, int userId) {
        ……
        synchronized(this) {
            //驗證Intent信息
            intent = verifyBroadcastLocked(intent);
            //獲取調用進程的信息ProcessRecord
            final ProcessRecord callerApp = getRecordForAppLocked(caller);
            //獲取調用應用的uid和pid
            final int callingPid = Binder.getCallingPid();
            final int callingUid = Binder.getCallingUid();
            //通過broadcastIntentLocked方法來繼續處理發送廣播的邏輯
            int res = broadcastIntentLocked(callerApp,
                    callerApp != null ? callerApp.info.packageName : null,
                    intent, resolvedType, resultTo, resultCode, resultData, resultExtras,
                    requiredPermissions, appOp, null, serialized, sticky,
                    callingPid, callingUid, userId);
            Binder.restoreCallingIdentity(origId);
            return res;
        }
    }

這個方式實現也是比較簡單,獲取調用進程的ProcessRecord信息和調用程序的UID和PID,然后直接調用broadcastIntentLocked方法來繼續處理發送廣播的邏輯

3. AMS.broadcastIntentLocked

這個方法比較長,我們一部分一部分的來看

intent = new Intent(intent);
 
        //確保要接收的廣播的用戶仍然存在,否則的話直接返回錯誤信息
        if (userId != UserHandle.USER_ALL && !isUserRunningLocked(userId, false)) {
            if ((callingUid != Process.SYSTEM_UID
                    || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                    && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
                Slog.w(TAG, "Skipping broadcast of " + intent
                        + ": user " + userId + " is stopped");
                return ActivityManager.BROADCAST_FAILED_USER_STOPPED;
            }
        }
        
        //如果當前的廣播類型是sticky廣播,則把廣播添加到List列表中去
        if (sticky) {
            //首先檢查相關的權限信息
            if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
                    callingPid, callingUid)
                    != PackageManager.PERMISSION_GRANTED) {
                String msg = "Permission Denial: broadcastIntent() requesting a sticky broadcast from pid="
                        + callingPid + ", uid=" + callingUid
                        + " requires " + android.Manifest.permission.BROADCAST_STICKY;
            ……
            
            if (userId != UserHandle.USER_ALL) {
                //如果這個廣播不是發送給所有用戶的,則檢查發送給所有用戶的廣播中是否包含這個廣播,如果包含則和發送給所有用戶的廣播有沖突,拋出異常信息
                ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(
                        UserHandle.USER_ALL);
                if (stickies != null) {
                    ArrayList<Intent> list = stickies.get(intent.getAction());
                    if (list != null) {
                        int N = list.size();
                        int i;
                        for (i=0; i<N; i++) {
                            if (intent.filterEquals(list.get(i))) {
                                throw new IllegalArgumentException(
                                        "Sticky broadcast " + intent + " for user "
                                        + userId + " conflicts with existing global broadcast");
                            }
                        }
                    }
                }
            }
            //找到和這個用戶相關的所有的sticky廣播的信息
            ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId);
            if (stickies == null) {
                stickies = new ArrayMap<>();
                mStickyBroadcasts.put(userId, stickies);
            }
            //從得到的廣播信息中,找到和當前要發送廣播action相同的sticky廣播
            ArrayList<Intent> list = stickies.get(intent.getAction());
            if (list == null) {
                list = new ArrayList<>();
                stickies.put(intent.getAction(), list);
            }
            //遍歷拿到的stick廣播信息,如果已經存在,則更新替換掉他
            //如果不存在,則添加進去
            final int stickiesCount = list.size();
            int i;
            for (i = 0; i < stickiesCount; i++) {
                if (intent.filterEquals(list.get(i))) {
                    // This sticky already exists, replace it.
                    list.set(i, new Intent(intent));
                    break;
                }
            }
            if (i >= stickiesCount) {
                list.add(new Intent(intent));
            }
        }

參數sticky用來描述要發送的廣播是否是一個sticky類型的廣播,如果是,就需要對sticky廣播進行一些處理,在前面我們分析過,在注冊廣播的時候,如果AMS中保存了對應action類型的sticky廣播,則直接將保存的sticky廣播發送出去。那些sticky廣播是如何保存到AMS服務中去的呢?

代碼邏輯就是在這邊處理的,在注冊一個廣播的時候,如果它是sticky類型的廣播,就直接將它保存在AMS服務的mStickyBroadcasts集合中。

首先從AMS系統服務中查詢Action相關的sticky廣播,然后遍歷,如果該sticky廣播已經在AMS中注冊保存了,則用新的Sticky廣播替換掉它,否則直接添加到列表中去。

  List receivers = null;
        List<BroadcastFilter> registeredReceivers = null;
        //如果沒有FLAG_RECEIVER_REGISTERED_ONLY標簽,說明不是發送給動態注冊的廣播的,那么調用collectReceiverComponents,查找所有和該Intent相關的廣播接收者
        //保存到receivers列表中
        if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY)
                 == 0) {
            receivers = collectReceiverComponents(intent, resolvedType, callingUid, users);
        }
        //如果沒有指定特定的廣播接收者,則查找所用的動態注冊的廣播接收者的信息
        if (intent.getComponent() == null) {
            ……
                registeredReceivers = mReceiverResolver.queryIntent(intent,
                        resolvedType, false, userId);
            }
        }

第二部分的主要處理邏輯就是查找和intent相關的廣播接收者。我們知道廣播注冊的時候有兩種注冊方法,一種是靜態注冊方法,在Manifest文件中注冊,另一種是動態注冊方法,在代碼中調用registerBroadcast方法來注冊廣播。

這個地方進行了兩次查找,判斷發送廣播的intent的標簽,如果不是僅僅發送給動態注冊的廣播的時候,就調用collectReceiverComponents方法查詢所有Intent感興趣的廣播接收者,這里僅僅去PMS服務中查詢了感興趣的靜態注冊的廣播接收者,保存在receivers列表中。

然后判斷這個廣播是否指定了值發送給特定的廣播接收者,如果沒有指定的話,默認發送給所有感興趣的廣播接收者,這時候再去查詢一次,這次查詢的是所有對這個Intent感興趣的動態注冊的廣播接收者,保存在registeredReceivers列表中。

  int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
        if (!ordered && NR > 0) {
            //如果當前不是有序廣播,且動態注冊的感興趣的廣播不為0,將廣播intent封裝為一個BroadcastRecord
            final BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, resolvedType, requiredPermissions,
                    appOp, brOptions, registeredReceivers, resultTo, resultCode, resultData,
                    resultExtras, ordered, sticky, false, userId);
           ……
            //發送該廣播給動態注冊的廣播接收者
                queue.enqueueParallelBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();
            }
            //發送完成后,將registeredReceivers 置為 null
            registeredReceivers = null;
            NR = 0;
        }

第三部分的處理工作:

如果當前發送的廣播不是有序廣播,且動態注冊的廣播接收者>0,此時則需要把這個廣播優先發送給動態注冊的廣播接收者,為什么不和靜態注冊的廣播一起發送,卻要分開發送呢?這樣做是因為,動態注冊的廣播接收者一般情況下都已經運行起來了,而靜態注冊的廣播接收者只是在Manifest文件中注冊,要想接收廣播必須要等待要接收的組件啟動起來才可,所以這邊處理選擇分開發送,優先發送動態注冊的廣播接收者。

發送的廣播的時候,無序廣播都添加到了mParallelBroadcats隊列中,具體發送的處理邏輯后邊在詳細分析。

下一步處理發送給靜態注冊的廣播接收者的邏輯。

  int ir = 0;
        //感興趣的靜態注冊廣播接收者不為空
        if (receivers != null) {
            //首先對于特殊的ACTION_PACKAGE_ADDED的廣播需要特殊處理
            //這樣是為了防止一些應用則安裝完成之后監聽到package_add的廣播就立刻運行
            //所以要把它已標記起來,以后從廣播接收者信息中移除
            String skipPackages[] = null;
            if (Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())
                    || Intent.ACTION_PACKAGE_RESTARTED.equals(intent.getAction())
                    || Intent.ACTION_PACKAGE_DATA_CLEARED.equals(intent.getAction())) {
                Uri data = intent.getData();
                if (data != null) {
                    String pkgName = data.getSchemeSpecificPart();
                    if (pkgName != null) {
                        skipPackages = new String[] { pkgName };
                    }
                }
            //同樣,如果Intenty有ACTION_EXTERNAL_APPLICATIONS_AVAILABLE標簽,則同樣移除
            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(intent.getAction())) {
                skipPackages = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
            }
            if (skipPackages != null && (skipPackages.length > 0)) {
                for (String skipPackage : skipPackages) {
                    if (skipPackage != null) {
                        int NT = receivers.size();
                        for (int it=0; it<NT; it++) {
                            ResolveInfo curt = (ResolveInfo)receivers.get(it);
                            if (curt.activityInfo.packageName.equals(skipPackage)) {
                                receivers.remove(it);
                                it--;
                                NT--;
                            }
                        }
                    }
                }

處理靜態注冊的廣播接收者邏輯的時候,首先判斷該廣播事件是否是Package_add的廣播事件,如果是的話,需要從廣播接收者列表中移除,這樣為了防止一些毒瘤應用在安裝完成后接收到package_add廣播就立刻運行。

    int NT = receivers != null ? receivers.size() : 0;
            int it = 0;
            ResolveInfo curt = null;
            BroadcastFilter curr = null;
            //上面的無序廣播處理后,把mRegisterReceivers列表置為null,并且把NR設置成了0
            //而此時要想滿足此處條件,NR不為0,說明此處是有序廣播的處理邏輯
            //遍歷動態注冊的廣播和靜態注冊的廣播,安priority值排序合并放到receivers列表中
            while (it < NT && ir < NR) {
                if (curt == null) {
                    curt = (ResolveInfo)receivers.get(it);
                }
                if (curr == null) {
                    curr = registeredReceivers.get(ir);
                }
                if (curr.getPriority() >= curt.priority) {
                    // Insert this broadcast record into the final list.
                    receivers.add(it, curr);
                    ir++;
                    curr = null;
                    it++;
                    NT++;
                } else {
                    // Skip to the next ResolveInfo in the final list.
                    it++;
                    curt = null;
                }
            }
        ……
        //有序廣播接收者不為空,發送有序廣播
        if ((receivers != null && receivers.size() > 0)
                || resultTo != null) {
            BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, resolvedType,
                    requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
                    resultData, resultExtras, ordered, sticky, false, userId);
 
            if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r
                    + ": prev had " + queue.mOrderedBroadcasts.size());
            if (DEBUG_BROADCAST) Slog.i(TAG_BROADCAST,
                    "Enqueueing broadcast " + r.intent.getAction());
 
            boolean replaced = replacePending && queue.replaceOrderedBroadcastLocked(r);
            if (!replaced) {
                queue.enqueueOrderedBroadcastLocked(r);
                queue.scheduleBroadcastsLocked();
            }
        }

此處處理邏輯主要為有序廣播的處理邏輯。

在前面的分析中知道,如果是無序廣播的時候,動態注冊的廣播接收者就直接處理了,然后把mRegisteredReceivers列表置為了null,所以在有序廣播的時候就不會有動態注冊的廣播接收者合并進來。此時就會把靜態廣播放到有序廣播類別mOrderedBroadcasts隊列中發送。為什么會把靜態注冊的廣播接收和按照有廣播的邏輯來發送呢?有序廣播發送的處理邏輯是發送完成一個在發送另一個,而無序廣播是直接循環發送出去,靜態注冊的廣播接收者的進程不一定已經運行起來了,在發送的過程中可能需要先啟動進程,這樣很消耗資源,所以一個一個發送會好一點。

如果發送的是有序廣播,動態注冊的廣播在前面邏輯中則不會處理,此處就會把動態注冊的廣播和靜態注冊的廣播按照priority值排序,然后合并到receivers列表中。

最終把receiver列表中的廣播接收者封裝成一個BroadcastRecord對象放到mOrderedBroadcasts隊列中進行發送。

此處邏輯可以總結出來,如果是無序廣播的話。動態注冊的廣播接收者會比靜態注冊的廣播者優先收到廣播
下面我們來分析具體的廣播發送處理邏輯:

  BroadcastQueue queue = broadcastQueueForIntent(intent);
            BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp,
                    callerPackage, callingPid, callingUid, resolvedType,
                    requiredPermissions, appOp, brOptions, receivers, resultTo, resultCode,
                    resultData, resultExtras, ordered, sticky, false, userId);
 
            queue.enqueueOrderedBroadcastLocked(r);
            queue.scheduleBroadcastsLocked();

對于BroadCast的發送的處理邏輯大體上分為以上幾個步驟

  1. 根據廣播的Intent找到對應的廣播隊列
  2. 將Intent和廣播接收者封裝成一個BroadcastRecord對象
  3. 將BroadcastRecord對象添加拿到queue隊列中
  4. 調用queue的scheduleBroadcastsLocked方法發送廣播

4. AMS. broadcastQueueForIntent

  BroadcastQueue broadcastQueueForIntent(Intent intent) {
        final boolean isFg = (intent.getFlags() & Intent.FLAG_RECEIVER_FOREGROUND) != 0;
        return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
    }

這個方法很簡單,就是根據Intent中是否有Flag_Receiver_foreground標簽,來決定添加到那個隊列中去。

這個標簽是什么作用呢?看下SDK中的描述:

FLAG_RECEIVER_FOREGROUND 接受器以前臺優先級運行

If set, when sending a broadcast the recipient is allowed to run at foreground priority, with a shorter timeout interval. During normal broadcasts the receivers are not automatically hoisted out of the background priority class.

廣播默認都是后臺優先級,如果廣播中添加了這個標簽就會以前臺優先級運行,發送到接收到廣播的時間間隔會更短。

也就是說,默認廣播都是天劍到mBgBroadcastQueue隊列中去的,只有廣播添加了這個標簽后,才會被添加到mFgBroadcastQueue隊列中。這兩個隊列中處理邏輯的區別我們后面再詳細分析。

第二步,將Intent和廣播接收者隊列封裝成了一個BroadcastRecord對象,也就是說每一個廣播在AMS都是對應一個BroadcastRecord對象的。

5. BroadcastQueue. Enqueue……BroadcastLocked

BroadcastQueue中有兩個添加隊列的方法:

 public void enqueueParallelBroadcastLocked(BroadcastRecord r) {
        mParallelBroadcasts.add(r);
        r.enqueueClockTime = System.currentTimeMillis();
    }
 
    public void enqueueOrderedBroadcastLocked(BroadcastRecord r) {
        mOrderedBroadcasts.add(r);
        r.enqueueClockTime = System.currentTimeMillis();
    }

該消息發送了了一個Handler消息,有Hander的handleMessage來處理,最終調用了processNextBroadcast方法來處理。

7. BroadcastQueue. processNextBroadcast

動態注冊廣播接收者的處理邏輯

這個方法有點復雜,主要是兩種廣播的處理邏輯不一樣,那我們就先分析動態注冊的廣播接收者處理邏輯,然后在分析靜態注冊或者有序廣播的處理邏輯。

我們在前面講過,動態注冊的廣播接收者處理的時候是一次性的循環發送完國有廣播,而靜態注冊或者有序廣播則是一個發送完成在發送另一個。

 final void processNextBroadcast(boolean fromMsg) {
        synchronized(mService) {
            BroadcastRecord r;
 
           ……
            while (mParallelBroadcasts.size() > 0) {
                r = mParallelBroadcasts.remove(0);
                r.dispatchTime = SystemClock.uptimeMillis();
                r.dispatchClockTime = System.currentTimeMillis();
                final int N = r.receivers.size();
                
                for (int i=0; i<N; i++) {
                    Object target = r.receivers.get(i);
                    deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false);
                }
                addBroadcastToHistoryLocked(r);
            }

動態廣播的處理邏輯是在processNextBroadcast方法的開始處理的,變量mParallelBroadcasts列表,從列表開始位置一次拿到保存的BroadcastRecord對象,每個BroadcastRecord中可能對應多個廣播接收者,依次遍歷廣播接收者,然后調用deliverToRegisteredReceiverLocked方法發送廣播

 private void deliverToRegisteredReceiverLocked(BroadcastRecord r,
            BroadcastFilter filter, boolean ordered) {
        boolean skip = false;
        //檢查是否具有相關權限,沒有權限則skip = true 表示跳過這個廣播接收者
        ……
        //檢查接收廣播的進程是否存在,如果接收廣播的進程不存在則跳過這個廣播接收者,不向它發送該廣播
        if (filter.receiverList.app == null || filter.receiverList.app.crashing) {
            Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r
                    + " to " + filter.receiverList + ": process crashing");
            skip = true;
        }
 
        if (!skip) {
            
            if (ordered) {
                //有序廣播處理邏輯
            }
            try {
               
                performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver,
                        new Intent(r.intent), r.resultCode, r.resultData,
                        r.resultExtras, r.ordered, r.initialSticky, r.userId);
                ……
            } 
        }
    }

我們只關心和我們處理邏輯相關的關鍵代碼。

檢查相關的權限,如果沒有權限則skip設置為true,
檢查廣播接收者對應的進程是否存在,如果不存在skip設置為true
我們目前是無序廣播,所以ordered為false,最終調用performReceiveLocked方法來處理。

我么回憶下ReceiverList是什么,ReceiverList是我們在注冊廣播接收者的時候,一個廣播接收者的代表信息,它繼承者ArrayList<BroadcastFilter>,這樣可以實現一對多的數據結構,因為一個廣播接收者可能對應多個BroadcastFilter。所以receiverList.app代表了廣播接收者所在的進程,receiverList.receiver則是一個Binder對象,指向了App進程中的一個InnerReceiver的對象。

繼續看performReceiveLocked方法如何處理。

private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,
            Intent intent, int resultCode, String data, Bundle extras,
            boolean ordered, boolean sticky, int sendingUser) throws RemoteException {
        if (app != null) {
            if (app.thread != null) {
                app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,
                        data, extras, ordered, sticky, sendingUser, app.repProcState);
            }
        } else {
            receiver.performReceive(intent, resultCode, data, extras, ordered,
                    sticky, sendingUser);
        }
    }

參數receiver是一個IIntentReceiver接口的Binder代理對象,它的服務端是應用進程的InnerReceiver,參數app則是接收廣播的應用進程的進程對象ProcessRecord,在注冊廣播接收者的時候,我們知道在創建廣播接收者對應的ReceiverList對象的時候會初始化app的值。

ReceiverList rl = new ReceiverList(this, callerApp, callingPid, callingUid, userId, receiver);

一般情況下receiver的變量app都是不為null的,所以大部分情況下廣播都是通過應用進程的ApplicationThread對象的Binder代理對象,通過進程間調用,將這個廣播發送到應用進程主線程來處理,應用進程的主線程再調用對應的receiver來處理。而不是直接通過Receiver的Binder代理端來進行進程間的調用。

接著看ApplicationThread的scheduleRegisteredReceiver方法。

 public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
                int resultCode, String dataStr, Bundle extras, boolean ordered,
                boolean sticky, int sendingUser, int processState) throws RemoteException {
            updateProcessState(processState, false);
            receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
                    sticky, sendingUser);
        }

此時直接調用receiver的performReceive方法,receiver是一個binder類型的代理對象,這個方法調用最終調用到InnerReceiver的performReceiver方法中。

public void performReceive(Intent intent, int resultCode, String data,
                    Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
                //得到當前的dispatcher
                LoadedApk.ReceiverDispatcher rd = mDispatcher.get();
                if (rd != null) {
                    rd.performReceive(intent, resultCode, data, extras,
                            ordered, sticky, sendingUser);
                }
                ……
            }

這個方法很簡單,首先得到對應的dispatcher,然后調用dispatcher的方法來處理。

public void performReceive(Intent intent, int resultCode, String data,
                Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
            Args args = new Args(intent, resultCode, data, extras, ordered,
                    sticky, sendingUser);
            if (!mActivityThread.post(args)) {
                if (mRegistered && ordered) {
                    IActivityManager mgr = ActivityManagerNative.getDefault();
                    args.sendFinished(mgr);
                }
            }
        }

mActivityThread是一個Handler對象,該對象指向ActivityThread的Hander對象,Handler的post最終調用了Args的run方法。

final class Args extends BroadcastReceiver.PendingResult implements Runnable {
           ……
 
            public void run() {
                final BroadcastReceiver receiver = mReceiver;
               ……
                try {
                    ClassLoader cl =  mReceiver.getClass().getClassLoader();
                    intent.setExtrasClassLoader(cl);
                    setExtrasClassLoader(cl);
                    receiver.setPendingResult(this);
                    receiver.onReceive(mContext, intent);
                } 
                ……
            }
        }

變量receiver指向了一個廣播接收者BroadcastReceiver,然后調用receiver.onReceive()方法。到此動態注冊的廣播接收者接收廣播的邏輯就處理完成了。

下一步看靜態注冊或者有序廣播的發送邏輯。

靜態注冊或者有序廣播接收者的處理邏輯

我們再回到processNextBroadcast方法中,接著往下看

            //判斷當前是否正在等待靜態注冊的廣播接收者控件啟動并接收任務
            if (mPendingBroadcast != null) {
 
                boolean isDead;
                synchronized (mService.mPidsSelfLocked) {
                    ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);
                    isDead = proc == null || proc.crashing;
                }
                if (!isDead) {
                    //如果有個任務正在啟動則直接返回繼續等待
                    return;
                }
            }
 
            boolean looped = false;
 
            do {
                //從mOrderedBroadcasts列表中獲取下一個要發送的廣播
                r = mOrderedBroadcasts.get(0);
                boolean forceReceive = false;
 
                //判斷有序廣播是否發送超時,如果發送超時則直接強制停止發送這個廣播
                int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;
                if (mService.mProcessesReady && r.dispatchTime > 0) {
                    long now = SystemClock.uptimeMillis();
                    if ((numReceivers > 0) &&
                            (now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {
                        broadcastTimeoutLocked(false); //強制停止這個廣播
                        forceReceive = true;
                        r.state = BroadcastRecord.IDLE;
                    }
                }
 
                ……
 
                if (r.receivers == null || r.nextReceiver >= numReceivers
                        || r.resultAbort || forceReceive) {
                    //如果這個廣播接收者receivers已經處理完了,或者這個廣播已經強制停止了
                    if (r.resultTo != null) {
                        ……
                    }
 
                    //從Handler延時消息中移除timeout的消息
                    cancelBroadcastTimeoutLocked();
 
                    //添加到history中
                    addBroadcastToHistoryLocked(r);
                    //從mOrderedBroadcasts列表中移除
                    mOrderedBroadcasts.remove(0);
                    //置為null
                    r = null;
                    looped = true; 
                    continue;
                }
            } while (r == null);

首先判斷mPendingBroadcast是不是為空,這個mPendingBroadcast的作用是什么呢?

我們知道,mOrderedBroadcast中保存的廣播接收者都是需要一個發送完成另一個才可以發送的。其中靜態注冊的廣播接收者有可能對應的進程還未啟動,者就需要首先來啟動該進程。如果是這種情況的話mPendingBroadcast就是描述了一個正在等待靜態注冊的對應進程啟動的廣播。

如果他不為空,說明當前有任務正在處理,在對這個正在啟動的進程檢查之后就直接退出了。

如果mPendingBroadcast不為空,AMS就會繼續等待,否則的話就會準備處理mOrderedBroadcast的下一個廣播。

接下來是一個while循化,主要工作是從mOrderedBroadcasts列表中找到下一個要處理的廣播,退出條件是 r != null,即找到下一個合適的廣播。

1.首先從列表的開始拿出第一個BroadcastRecord,第一個有可能是正在處理的廣播,判斷他是否已經開始分發廣播,如果開始但是已經超時,則直接強制停止這個廣播。
2.然后,判斷這個廣播是否已經處理完成,或者是否強制停止,然后直接停止該廣播。
3.將發送給AMS的關于超時的延時消息移除
4.將第一個廣播移除掉,并把r設置為null,表示沒有找到合適的廣播,然后繼續循環查找,知道找到下一個合適的廣播,退出循環。

   //獲取下一個要執行的Receiver信息
            int recIdx = r.nextReceiver++;
 
            r.receiverTime = SystemClock.uptimeMillis();
            //如果recIdx = 0,說明這個廣播的才剛開始發送廣播任務,則記錄下當前的時間為分發廣播的時間
            if (recIdx == 0) {
                r.dispatchTime = r.receiverTime;
                r.dispatchClockTime = System.currentTimeMillis();
            }
            if (! mPendingBroadcastTimeoutMessage) {
                //向AMS的handler發送一個延時的超時消息,到超時的時間后自動發送
                long timeoutTime = r.receiverTime + mTimeoutPeriod;
                setBroadcastTimeoutLocked(timeoutTime);
            }
……
            final Object nextReceiver = r.receivers.get(recIdx);
 
            if (nextReceiver instanceof BroadcastFilter) {
                BroadcastFilter filter = (BroadcastFilter)nextReceiver;
                deliverToRegisteredReceiverLocked(r, filter, r.ordered);
                return;
            }

我們知道一個廣播可能對應多個廣播接收者,在mOrderedBroadcasts列表處理的時候是需要向一個Receiver發送成功后,在向另一個發送。BroadcastRecord的變量nextReceiver表示下一個廣播接收者的序號。

如果為0,說明這個廣播的廣播接收者一個都沒有發送,也就是說這個廣播才剛開始發送,此時就要記錄下當前廣播分發的時間,然后計算下超時時間然后向AMS服務發送一個延時消息。接著,獲取到下一個Receiver,判斷它是否是BroadcastFilter類型,如果是,然后調用deliverToRegisteredReceiverLocked方法來處理,我們在動態注冊的廣播處理的時候已經分析過這個方法了,這邊不再討論。

 ResolveInfo info =
                (ResolveInfo)nextReceiver;
            ComponentName component = new ComponentName(
                    info.activityInfo.applicationInfo.packageName,
                    info.activityInfo.name);
            //判斷權限相關及其他來覺是否跳過這個廣播接收者
            boolean skip = false;
            int perm = mService.checkComponentPermission(info.activityInfo.permission,
            ……
 
            //判斷這個廣播接收者對應的進程是否已經啟動
            ProcessRecord app = mService.getProcessRecordLocked(targetProcess,
                    info.activityInfo.applicationInfo.uid, false);
            if (app != null && app.thread != null) {
                //如果已經啟動則發送廣播給當前的廣播接收者
                try {
                    app.addPackage(info.activityInfo.packageName,
                            info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);
                    processCurBroadcastLocked(r, app);
                    return;
                } 
                ……
            }
 
            //否則的話則新建一個進程,并把mPendingBroadcast設置為當前的廣播接收者,等待新進程啟動完成
            if ((r.curApp=mService.startProcessLocked(targetProcess,
                    info.activityInfo.applicationInfo, true,
                    r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,
                    "broadcast", r.curComponent,
                    (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))
                            == null) {
                ……
            }
            
            mPendingBroadcast = r;
            mPendingBroadcastRecvIndex = recIdx;

廣播接收者分為兩種類型,動態注冊和靜態注冊,靜態注冊的廣播是ResolveInfo類型,所以此處主要來處理靜態注冊的廣播接收者發送廣播的邏輯。
1.首先,判斷相關的權限及其他信息來決定是否可以發送廣播給這個廣播接收者
2.如果可以,查詢廣播接收者對應的進程是否存在,如果已經存在,直接 調用processCurBroadcastLocked方法來處理當前廣播

如果不存在,則啟動一個新的進程,將mPendingBroadcast的值設置為當前的BroadcastRecord的值,然后等待新的進程啟動完成。

后邊發送的過程和動態注冊的廣播mParallelBroadcasts列表處理邏輯一致,不再詳細介紹。由于mOrderedBroadcasts列表中的廣播是一個處理完成在處理另一個,那我們看下發送廣播給receiver后,如何處理的。

在前面分析過,Args的run方法中最終調用了BroadcastReceiver的onReceive方法,在調用完receiver方法后會調用AMS的finishReceiver方法。其實不僅僅是這個地方調用了finishReceiver方法,在發送失敗等情況下也會調用。

IActivityManager mgr = ActivityManagerNative.getDefault();
mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());

AMS的finishReceiver方法中則會繼續處理后續的廣播接收者的發送或者調用processNextBroadcast方法,繼續執行下一個廣播的發送。

到此為止廣播的發送過程也就簡單的分析完了。從分析過程中可以看出AMS服務具體是如何管理Broadcast廣播。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容

  • 1、梅縣碧桂園趕工期,兩個工程公司在做,4天一層,二期十多棟,主體今年6月份完工,估計年底交不了房,還要做環境、裝修
    fa4a50c8ddcd閱讀 196評論 0 0
  • 失落 【原創詩第11首】 文\戀風 失落 莫名的一波波 如潮水 漫過心的沙灘 留下 滿灘回憶的斑斕貝殼 那個 曾經...
    戀風2016閱讀 263評論 0 1
  • 雪天里……… 又在下雪 它姍姍降臨 染白一切 停駐在窗外 遍布每一個角落 我看雪 每個午后 按時來敲門的 總是寂寞...
    樹與樹的對望閱讀 372評論 5 9