播報:在Android系統(tǒng)中為什么需要廣播機制?
在Android系統(tǒng)中,廣播(Broadcast)是在組件之間傳播數(shù)據(jù)(Intent)的一種機制;這些組件甚至是可以位于不同的進(jìn)程中,這樣它就像Binder機制一樣,起到進(jìn)程間通信的作用。
在Android系統(tǒng)中,為什么需要廣播機制呢?如果是兩個組件位于不同的進(jìn)程當(dāng)中,那么可以用Binder機制來實現(xiàn),如果兩個組件是在同一個進(jìn)程中,那么它們之間可以用來通信的方式就更多了,這樣看來,廣播機制似乎是多余的。
然而,廣播機制卻是不可替代的,它和Binder機制不一樣的地方在于,廣播的發(fā)送者和接收者事先是不需要知道對方的存在的,這樣帶來的好處便是,系統(tǒng)的各個組件可以松耦合地組織在一起,這樣系統(tǒng)就具有高度的可擴展性,容易與其它系統(tǒng)進(jìn)行集成。
(相關(guān)資料圖)
使用廣播的兩個步驟: 1、廣播的接收者需要通過調(diào)用registerReceiver函數(shù)告訴系統(tǒng),它對什么樣的廣播有興趣,即指定IntentFilter,并且向系統(tǒng)注冊廣播接收器,即指定BroadcastReceiver:
IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION); registerReceiver(counterActionReceiver, counterActionFilter);
這里,指定感興趣的廣播就是CounterService.BROADCAST_COUNTER_ACTION了,而指定的廣播接收器就是counterActonReceiver,它是一個BroadcastReceiver類型的實例。
2、廣播的發(fā)送者通過調(diào)用sendBroadcast函數(shù)來發(fā)送一個指定的廣播,并且可以指定廣播的相關(guān)參數(shù):
Intent intent = new Intent(BROADCAST_COUNTER_ACTION); intent.putExtra(COUNTER_VALUE, counter); sendBroadcast(intent)
這里,指定的廣播為CounterService.BROADCAST_COUNTER_ACTION,并且附帶的帶參數(shù)當(dāng)前的計數(shù)器值counter。調(diào)用了sendBroadcast函數(shù)之后,所有注冊了CounterService.BROADCAST_COUNTER_ACTION廣播的接收者便可以收到這個廣播了。
在第1步中,廣播的接收者把廣播接收器注冊到ActivityManagerService中;在第2步中,廣播的發(fā)送者同樣是把廣播發(fā)送到ActivityManagerService中,由ActivityManagerService去查找注冊了這個廣播的接收者,然后把廣播分發(fā)給它們。
在第2步的分發(fā)的過程,其實就是把這個廣播轉(zhuǎn)換成一個消息,然后放入到接收器所在的線程消息隊列中去,最后就可以在消息循環(huán)中調(diào)用接收器的onReceive函數(shù)了。這里有一個要非常注意的地方是,由于ActivityManagerService把這個廣播放進(jìn)接收器所在的線程消息隊列后,就返回了,它不關(guān)心這個消息什么時候會被處理,因此,對廣播的處理是異步的,即調(diào)用sendBroadcast時,這個函數(shù)不會等待這個廣播被處理完后才返回。
虛線上面Step 1到Step 4步是注冊廣播接收器的過程,其中Step 2通過LoadedApk.getReceiverDispatcher在LoadedApk內(nèi)部創(chuàng)建了一個IIntentReceiver接口,并且傳遞給ActivityManagerService;
虛線下面的Step 5到Step 11是發(fā)送廣播的過程,在Step 8中,ActivityManagerService利用上面得到的IIntentReceiver遠(yuǎn)程接口,調(diào)用LoadedApk.performReceiver接口,LoadedApk.performReceiver接口通過ActivityThread.H接口的post函數(shù)將這個廣播消息放入到ActivityThread的消息隊列中去,最后這個消息在LoadedApk的Args.run函數(shù)中處理,LoadedApk.Args.run函數(shù)接著調(diào)用MainActivity.BroadcastReceiver的onReceive函數(shù)來最終處理這個廣播。
注冊廣播接收器(registerReceiver)
在Android的廣播機制中,ActivityManagerService扮演著廣播中心的角色,負(fù)責(zé)系統(tǒng)中所有廣播的注冊和發(fā)布操作,因此,Android應(yīng)用程序注冊廣播接收器的過程就把是廣播接收器注冊到ActivityManagerService的過程。Android應(yīng)用程序是通過調(diào)用ContextWrapper類的registerReceiver函數(shù)來把廣播接收器BroadcastReceiver注冊到ActivityManagerService中去的,而ContextWrapper類本身又借助ContextImpl類來注冊廣播接收器。
在Android應(yīng)用程序框架中,Activity和Service類都繼承了ContextWrapper類,因此,我們可以在Activity或者Service的子類中調(diào)用registerReceiver函數(shù)來注冊廣播接收器。
我們先來看一下MainActivity是如何調(diào)用registerReceiver函數(shù)來注冊廣播接收器的:
public class MainActivity extends Activity implements OnClickListener {...... @Override public void onResume() {super.onResume(); IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION); registerReceiver(counterActionReceiver, counterActionFilter); } ...... }
Step 1. ContextWrapper.registerReceiver
public class ContextWrapper extends Context {Context mBase; ...... @Override public Intent registerReceiver( BroadcastReceiver receiver, IntentFilter filter) {return mBase.registerReceiver(receiver, filter); } ...... }
這里的成員變量mBase是一個ContextImpl實例。
Step 2. ContextImpl.registerReceiver
class ContextImpl extends Context {...... @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {return registerReceiver(receiver, filter, null, null); } @Override public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) {return registerReceiverInternal(receiver, filter, broadcastPermission, scheduler, getOuterContext()); } private Intent registerReceiverInternal(BroadcastReceiver receiver, 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 {...... } } try {return ActivityManagerNative.getDefault().registerReceiver( mMainThread.getApplicationThread(), rd, filter, broadcastPermission); } catch (RemoteException e) {return null; } } ...... }
通過兩個函數(shù)的中轉(zhuǎn),最終就進(jìn)入到ContextImpl.registerReceiverInternal這個函數(shù)來了。這里的成員變量mPackageInfo是一個LoadedApk實例,它是用來負(fù)責(zé)處理廣播的接收的。
參數(shù)broadcastPermission和scheduler都為null,而參數(shù)context是上面的函數(shù)通過調(diào)用函數(shù)getOuterContext得到的,這里它就是指向MainActivity了,因為MainActivity是繼承于Context類的,因此,這里用Context類型來引用。
由于條件mPackageInfo != null和context != null都成立,而且條件scheduler == null也成立,于是就調(diào)用mMainThread.getHandler來獲得一個Handler了,這個Hanlder是后面用來分發(fā)ActivityManagerService發(fā)送過的廣播用的。這里的成員變量mMainThread是一個ActivityThread實例。
Step 3. ActivityThread.getHandler
public final class ActivityThread {...... final H mH = new H(); private final class H extends Handler {...... public void handleMessage(Message msg) {...... switch (msg.what) {...... } ...... } ...... } ...... final Handler getHandler() {return mH; } ...... }
有了這個Handler之后,就可以分發(fā)消息給應(yīng)用程序處理了。
再回到上一步的ContextImpl.registerReceiverInternal函數(shù)中,它通過mPackageInfo.getReceiverDispatcher函數(shù)獲得一個IIntentReceiver接口對象rd,這是一個Binder對象,接下來會把它傳給ActivityManagerService,ActivityManagerService在收到相應(yīng)的廣播時,就是通過這個Binder對象來通知MainActivity來接收的。
Step 4. LoadedApk.getReceiverDispatcher
final class LoadedApk {...... public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r, Context context, Handler handler, Instrumentation instrumentation, boolean registered) {synchronized (mReceivers) {LoadedApk.ReceiverDispatcher rd = null; HashMapmap = null; if (registered) {map = mReceivers.get(context); if (map != null) {rd = map.get(r); } } if (rd == null) {rd = new ReceiverDispatcher(r, context, handler, instrumentation, registered); if (registered) {if (map == null) {map = new HashMap (); mReceivers.put(context, map); } map.put(r, rd); } } else {rd.validate(context, handler); } return rd.getIIntentReceiver(); } } ...... static final class ReceiverDispatcher {final static class InnerReceiver extends IIntentReceiver.Stub {final WeakReference mDispatcher; ...... InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {mDispatcher = new WeakReference (rd); ...... } ...... } ...... final IIntentReceiver.Stub mIIntentReceiver; final Handler mActivityThread; ...... ReceiverDispatcher(BroadcastReceiver receiver, Context context, Handler activityThread, Instrumentation instrumentation, boolean registered) {...... mIIntentReceiver = new InnerReceiver(this, !registered); mActivityThread = activityThread; ...... } ...... IIntentReceiver getIIntentReceiver() {return mIIntentReceiver; } } ...... }
在LoadedApk.getReceiverDispatcher函數(shù)中,首先看一下參數(shù)r是不是已經(jīng)有相應(yīng)的ReceiverDispatcher存在了,如果有,就直接返回了,否則就新建一個ReceiverDispatcher,并且以r為Key值保在一個HashMap中,而這個HashMap以Context,這里即為MainActivity為Key值保存在LoadedApk的成員變量mReceivers中,這樣,只要給定一個Activity和BroadcastReceiver,就可以查看LoadedApk里面是否已經(jīng)存在相應(yīng)的廣播接收發(fā)布器ReceiverDispatcher了。
在新建廣播接收發(fā)布器ReceiverDispatcher時,會在構(gòu)造函數(shù)里面創(chuàng)建一個InnerReceiver實例,這是一個Binder對象,實現(xiàn)了IIntentReceiver接口,可以通過ReceiverDispatcher.getIIntentReceiver函數(shù)來獲得,獲得后就會把它傳給ActivityManagerService,以便接收廣播。
在ReceiverDispatcher類的構(gòu)造函數(shù)中,還會把傳進(jìn)來的Handle類型的參數(shù)activityThread保存下來,以便后面在分發(fā)廣播的時候使用。
現(xiàn)在,再回到ContextImpl.registerReceiverInternal函數(shù),在獲得了IIntentReceiver類型的Binder對象后,就開始要把它注冊到ActivityManagerService中去了。
Step 5. ActivityManagerProxy.registerReceiver
class ActivityManagerProxy implements IActivityManager {...... public Intent registerReceiver(IApplicationThread caller, IIntentReceiver receiver, IntentFilter filter, String perm) throws RemoteException {Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); data.writeStrongBinder(receiver != null ? receiver.asBinder() : null); filter.writeToParcel(data, 0); data.writeString(perm); mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0); reply.readException(); Intent intent = null; int haveIntent = reply.readInt(); if (haveIntent != 0) {intent = Intent.CREATOR.createFromParcel(reply); } reply.recycle(); data.recycle(); return intent; } ...... }
這個函數(shù)通過Binder驅(qū)動程序就進(jìn)入到ActivityManagerService中的registerReceiver函數(shù)中去了。
Step 6. ActivityManagerService.registerReceiver
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... public Intent registerReceiver(IApplicationThread caller, IIntentReceiver receiver, IntentFilter filter, String permission) {synchronized(this) {ProcessRecord callerApp = null; if (caller != null) {callerApp = getRecordForAppLocked(caller); if (callerApp == null) {...... } } List allSticky = null; // Look for any matching sticky broadcasts... Iterator actions = filter.actionsIterator(); if (actions != null) {while (actions.hasNext()) {String action = (String)actions.next(); allSticky = getStickiesLocked(action, filter, allSticky); } } else {...... } // The first sticky in the list is returned directly back to // the client. Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null; ...... if (receiver == null) {return sticky; } ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); if (rl == null) {rl = new ReceiverList(this, callerApp, Binder.getCallingPid(), Binder.getCallingUid(), receiver); if (rl.app != null) {rl.app.receivers.add(rl); } else {...... } mRegisteredReceivers.put(receiver.asBinder(), rl); } BroadcastFilter bf = new BroadcastFilter(filter, rl, permission); rl.add(bf); ...... mReceiverResolver.addFilter(bf); // Enqueue broadcasts for all existing stickies that match // this filter. if (allSticky != null) {...... } return sticky; } } ...... }
函數(shù)首先是獲得調(diào)用registerReceiver函數(shù)的應(yīng)用程序進(jìn)程記錄塊:
ProcessRecord callerApp = null; if (caller != null) {callerApp = getRecordForAppLocked(caller); if (callerApp == null) {...... } }
這里得到的便是應(yīng)用程序Broadcast的進(jìn)程記錄塊了,MainActivity就是在里面啟動起來的。
List allSticky = null; // Look for any matching sticky broadcasts... Iterator actions = filter.actionsIterator(); if (actions != null) {while (actions.hasNext()) {String action = (String)actions.next(); allSticky = getStickiesLocked(action, filter, allSticky); } } else {...... } // The first sticky in the list is returned directly back to // the client. Intent sticky = allSticky != null ? (Intent)allSticky.get(0) : null;
這里傳進(jìn)來的filter只有一個action,就是前面描述的CounterService.BROADCAST_COUNTER_ACTION了,這里先通過getStickiesLocked函數(shù)查找一下有沒有對應(yīng)的sticky intent列表存在。什么是Sticky Intent呢?我們在最后一次調(diào)用sendStickyBroadcast函數(shù)來發(fā)送某個Action類型的廣播時,系統(tǒng)會把代表這個廣播的Intent保存下來,這樣,后來調(diào)用registerReceiver來注冊相同Action類型的廣播接收器,就會得到這個最后發(fā)出的廣播。這就是為什么叫做Sticky Intent了,這個最后發(fā)出的廣播雖然被處理完了,但是仍然被粘住在ActivityManagerService中,以便下一個注冊相應(yīng)Action類型的廣播接收器還能繼承處理。
這里,假設(shè)我們不使用sendStickyBroadcast來發(fā)送CounterService.BROADCAST_COUNTER_ACTION類型的廣播,于是,這里得到的allSticky和sticky都為null了。
繼續(xù)往下看,這里傳進(jìn)來的receiver不為null,于是,繼續(xù)往下執(zhí)行:
ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); if (rl == null) {rl = new ReceiverList(this, callerApp, Binder.getCallingPid(), Binder.getCallingUid(), receiver); if (rl.app != null) {rl.app.receivers.add(rl); } else {...... } mRegisteredReceivers.put(receiver.asBinder(), rl); }
這里其實就是把廣播接收器receiver保存一個ReceiverList列表中,這個列表的宿主進(jìn)程是rl.app,這里就是MainActivity所在的進(jìn)程了,在ActivityManagerService中,用一個進(jìn)程記錄塊來表示這個應(yīng)用程序進(jìn)程,它里面有一個列表receivers,專門用來保存這個進(jìn)程注冊的廣播接收器。接著,又把這個ReceiverList列表以receiver為Key值保存在ActivityManagerService的成員變量mRegisteredReceivers中,這些都是為了方便在收到廣播時,快速找到對應(yīng)的廣播接收器的。
再往下看:
BroadcastFilter bf = new BroadcastFilter(filter, rl, permission); rl.add(bf); ...... mReceiverResolver.addFilter(bf);
上面只是把廣播接收器receiver保存起來了,但是還沒有把它和filter關(guān)聯(lián)起來,這里就創(chuàng)建一個BroadcastFilter來把廣播接收器列表rl和filter關(guān)聯(lián)起來,然后保存在ActivityManagerService中的成員變量mReceiverResolver中去。
發(fā)送廣播(sendBroadcast)的過程分析
前面我們分析了Android應(yīng)用程序注冊廣播接收器的過程,這個過程只完成了萬里長征的第一步,接下來它還要等待ActivityManagerService將廣播分發(fā)過來。
廣播的發(fā)送過程比廣播接收器的注冊過程要復(fù)雜得多了,不過這個過程仍然是以ActivityManagerService為中心。廣播的發(fā)送者將廣播發(fā)送到ActivityManagerService,ActivityManagerService接收到這個廣播以后,就會在自己的注冊中心查看有哪些廣播接收器訂閱了該廣播,然后把這個廣播逐一發(fā)送到這些廣播接收器中,但是ActivityManagerService并不等待廣播接收器處理這些廣播就返回了,因此,廣播的發(fā)送和處理是異步的。
在分析廣播的發(fā)送過程前,我們先來看一下廣播發(fā)送過程的序列圖,然后按照這個序圖中的步驟來一步一步分析整個過程。
Step 1. ContextWrapper.sendBroadcast
public class ContextWrapper extends Context {Context mBase; ...... @Override public void sendBroadcast(Intent intent) {mBase.sendBroadcast(intent); } ...... }
Step 2. ContextImpl.sendBroadcast
class ContextImpl extends Context {...... @Override public void sendBroadcast(Intent intent) {String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try {ActivityManagerNative.getDefault().broadcastIntent( mMainThread.getApplicationThread(), intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false); } catch (RemoteException e) {} } ...... }
這里的resolvedType表示這個Intent的MIME類型,我們沒有設(shè)置這個Intent的MIME類型,因此,這里的resolvedType為null。接下來就調(diào)用ActivityManagerService的遠(yuǎn)程接口ActivityManagerProxy把這個廣播發(fā)送給ActivityManagerService了。
Step 3. ActivityManagerProxy.broadcastIntent
class ActivityManagerProxy implements IActivityManager {...... public int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, boolean sticky) throws RemoteException {Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(caller != null ? caller.asBinder() : null); intent.writeToParcel(data, 0); data.writeString(resolvedType); data.writeStrongBinder(resultTo != null ? resultTo.asBinder() : null); data.writeInt(resultCode); data.writeString(resultData); data.writeBundle(map); data.writeString(requiredPermission); data.writeInt(serialized ? 1 : 0); data.writeInt(sticky ? 1 : 0); mRemote.transact(BROADCAST_INTENT_TRANSACTION, data, reply, 0); reply.readException(); int res = reply.readInt(); reply.recycle(); data.recycle(); return res; } ...... }
Step 4. ActivityManagerService.broadcastIntent
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... public final int broadcastIntent(IApplicationThread caller, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean serialized, boolean sticky) {synchronized(this) {intent = verifyBroadcastLocked(intent); final ProcessRecord callerApp = getRecordForAppLocked(caller); final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); int res = broadcastIntentLocked(callerApp, callerApp != null ? callerApp.info.packageName : null, intent, resolvedType, resultTo, resultCode, resultData, map, requiredPermission, serialized, sticky, callingPid, callingUid); Binder.restoreCallingIdentity(origId); return res; } } ...... }
這里調(diào)用broadcastIntentLocked函數(shù)來進(jìn)一步處理。
Step 5. ActivityManagerService.broadcastIntentLocked
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... private final int broadcastIntentLocked(ProcessRecord callerApp, String callerPackage, Intent intent, String resolvedType, IIntentReceiver resultTo, int resultCode, String resultData, Bundle map, String requiredPermission, boolean ordered, boolean sticky, int callingPid, int callingUid) {intent = new Intent(intent); ...... // Figure out who all will receive this broadcast. List receivers = null; ListregisteredReceivers = null; try {if (intent.getComponent() != null) {...... } else {...... registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false); } } catch (RemoteException ex) {...... } final boolean replacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0; int NR = registeredReceivers != null ? registeredReceivers.size() : 0; if (!ordered && NR > 0) {// If we are not serializing this broadcast, then send the // registered receivers separately so they don"t wait for the // components to be launched. BroadcastRecord r = new BroadcastRecord(intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, ordered, sticky, false); ...... boolean replaced = false; if (replacePending) {for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {...... mParallelBroadcasts.set(i, r); replaced = true; break; } } } if (!replaced) {mParallelBroadcasts.add(r); scheduleBroadcastsLocked(); } registeredReceivers = null; NR = 0; } ...... } ...... }
這個函數(shù)首先是根據(jù)intent找出相應(yīng)的廣播接收器:
// Figure out who all will receive this broadcast. List receivers = null; ListregisteredReceivers = null; try {if (intent.getComponent() != null) {...... } else {...... registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false); } } catch (RemoteException ex) {...... }
回憶一下前面過程分析中的Step 6(ActivityManagerService.registerReceiver)中,我們將一個filter類型為BROADCAST_COUNTER_ACTION類型的BroadcastFilter實例保存在了ActivityManagerService的成員變量mReceiverResolver中,這個BroadcastFilter實例包含了我們所注冊的廣播接收器,這里就通過mReceiverResolver.queryIntent函數(shù)將這個BroadcastFilter實例取回來。由于注冊一個廣播類型的接收器可能有多個,所以這里把所有符合條件的的BroadcastFilter實例放在一個List中,然后返回來。在我們這個場景中,這個List就只有一個BroadcastFilter實例了,就是MainActivity注冊的那個廣播接收器。
繼續(xù)往下看:
final boolean replacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
這里是查看一下這個intent的Intent.FLAG_RECEIVER_REPLACE_PENDING位有沒有設(shè)置,如果設(shè)置了的話,ActivityManagerService就會在當(dāng)前的系統(tǒng)中查看有沒有相同的intent還未被處理,如果有的話,就有當(dāng)前這個新的intent來替換舊的intent。這里,我們沒有設(shè)置intent的Intent.FLAG_RECEIVER_REPLACE_PENDING位,因此,這里的replacePending變量為false。
再接著往下看:
int NR = registeredReceivers != null ? registeredReceivers.size() : 0; if (!ordered && NR > 0) {// If we are not serializing this broadcast, then send the // registered receivers separately so they don"t wait for the // components to be launched. BroadcastRecord r = new BroadcastRecord(intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, ordered, sticky, false); ...... boolean replaced = false; if (replacePending) {for (int i=mParallelBroadcasts.size()-1; i>=0; i--) {if (intent.filterEquals(mParallelBroadcasts.get(i).intent)) {...... mParallelBroadcasts.set(i, r); replaced = true; break; } } } if (!replaced) {mParallelBroadcasts.add(r); scheduleBroadcastsLocked(); } registeredReceivers = null; NR = 0; }
前面我們說到,這里得到的列表registeredReceivers的大小為1,且傳進(jìn)來的參數(shù)ordered為false,表示要將這個廣播發(fā)送給所有注冊了BROADCAST_COUNTER_ACTION類型廣播的接收器,因此,會執(zhí)行下面的if語句。這個if語句首先創(chuàng)建一個廣播記錄塊BroadcastRecord,里面記錄了這個廣播是由誰發(fā)出的以及要發(fā)給誰等相關(guān)信息。由于前面得到的replacePending變量為false,因此,不會執(zhí)行接下來的if語句,即不會檢查系統(tǒng)中是否有相同類型的未處理的廣播。
這樣,這里得到的replaced變量的值也為false,于是,就會把這個廣播記錄塊r放在ActivityManagerService的成員變量mParcelBroadcasts中,等待進(jìn)一步處理;進(jìn)一步處理的操作由函數(shù)scheduleBroadcastsLocked進(jìn)行。
Step 6. ActivityManagerService.scheduleBroadcastsLocked
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... private final void scheduleBroadcastsLocked() {...... if (mBroadcastsScheduled) {return; } mHandler.sendEmptyMessage(BROADCAST_INTENT_MSG); mBroadcastsScheduled = true; } ...... }
這里的mBroadcastsScheduled表示ActivityManagerService當(dāng)前是不是正在處理其它廣播,如果是的話,這里就先不處理直接返回了,保證所有廣播串行處理。
注意這里處理廣播的方式,它是通過消息循環(huán)來處理,每當(dāng)ActivityManagerService接收到一個廣播時,它就把這個廣播放進(jìn)自己的消息隊列去就完事了,根本不管這個廣播后續(xù)是處理的,因此,這里我們可以看出廣播的發(fā)送和處理是異步的。
這里的成員變量mHandler是一個在ActivityManagerService內(nèi)部定義的Handler類變量,通過它的sendEmptyMessage函數(shù)把一個類型為BROADCAST_INTENT_MSG的空消息放進(jìn)ActivityManagerService的消息隊列中去。這里的空消息是指這個消息除了有類型信息之外,沒有任何其它額外的信息,因為前面已經(jīng)把要處理的廣播信息都保存在mParcelBroadcasts中了,等處理這個消息時,從mParcelBroadcasts就可以讀回相關(guān)的廣播信息了,因此,這里不需要把廣播信息再放在消息內(nèi)容中。
Step 7. Handler.sendEmptyMessage
這個自定義的Handler類實現(xiàn)在frameworks/base/services/java/com/android/server/am/ActivityManagerService.java文件中,它是ActivityManagerService的內(nèi)部類,調(diào)用了它的sendEmptyMessage函數(shù)來把一個消息放到消息隊列后,一會就會調(diào)用它的handleMessage函數(shù)來真正處理這個消息:
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... final Handler mHandler = new Handler() {public void handleMessage(Message msg) {switch (msg.what) {...... case BROADCAST_INTENT_MSG: {...... processNextBroadcast(true); } break; ...... } } } ...... }
這里又調(diào)用了ActivityManagerService的processNextBroadcast函數(shù)來處理下一個未處理的廣播。
Step 8. ActivityManagerService.processNextBroadcast
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... private final void processNextBroadcast(boolean fromMsg) {synchronized(this) {BroadcastRecord r; ...... if (fromMsg) {mBroadcastsScheduled = false; } // First, deliver any non-serialized broadcasts right away. while (mParallelBroadcasts.size() > 0) {r = mParallelBroadcasts.remove(0); ...... final int N = r.receivers.size(); ...... for (int i=0; iObject target = r.receivers.get(i); ...... deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false); } addBroadcastToHistoryLocked(r); ...... } ...... } } ...... }
這里傳進(jìn)來的參數(shù)fromMsg為true,于是把mBroadcastScheduled重新設(shè)為false,這樣,下一個廣播就能進(jìn)入到消息隊列中進(jìn)行處理了。前面我們在Step 5中,把一個廣播記錄塊BroadcastRecord放在了mParallelBroadcasts中,因此,這里就把它取出來進(jìn)行處理了。廣播記錄塊BroadcastRecord的receivers列表中包含了要接收這個廣播的目標(biāo)列表,即前面我們注冊的廣播接收器,用BroadcastFilter來表示,這里while循環(huán)中的for循環(huán)就是把這個廣播發(fā)送給每一個訂閱了該廣播的接收器了,通過deliverToRegisteredReceiverLocked函數(shù)執(zhí)行。
Step 9. ActivityManagerService.deliverToRegisteredReceiverLocked
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... private final void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered) {boolean skip = false; if (filter.requiredPermission != null) {...... } if (r.requiredPermission != null) {...... } if (!skip) {// If this is not being sent as an ordered broadcast, then we // don"t want to touch the fields that keep track of the current // state of ordered broadcasts. if (ordered) {...... } try {...... performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky); ...... } catch (RemoteException e) {...... } } } ...... }
函數(shù)首先是檢查一下廣播發(fā)送和接收的權(quán)限,在我們分析的這個場景中,沒有設(shè)置權(quán)限,因此,這個權(quán)限檢查就跳過了,這里得到的skip為false,于是進(jìn)入下面的if語句中。由于上面?zhèn)鲿r來的ordered參數(shù)為false,因此,直接就調(diào)用performReceiveLocked函數(shù)來進(jìn)一步執(zhí)行廣播發(fā)送的操作了。
Step 10. ActivityManagerService.performReceiveLocked
public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {...... static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) throws RemoteException {// Send the intent to the receiver asynchronously using one-way binder calls. if (app != null && app.thread != null) {// If we have an app thread, do the call through that so it is // correctly ordered with other one-way calls. app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, data, extras, ordered, sticky); } else {...... } } ...... }
注意,這里傳進(jìn)來的參數(shù)app是注冊廣播接收器的Activity所在的進(jìn)程記錄塊,在我們分析的這個場景中,由于是MainActivity調(diào)用registerReceiver函數(shù)來注冊這個廣播接收器的,因此,參數(shù)app所代表的ProcessRecord就是MainActivity所在的進(jìn)程記錄塊了;
而參數(shù)receiver也是注冊廣播接收器時傳給ActivityManagerService的一個Binder對象,它的類型是IIntentReceiver。
MainActivity在注冊廣播接收器時,已經(jīng)把自己的ProcessRecord記錄下來了,所以這里的參數(shù)app和app.thread均不為null,于是,ActivityManagerService就調(diào)用app.thread.scheduleRegisteredReceiver函數(shù)來把這個廣播分發(fā)給MainActivity了。這里的app.thread是一個Binder遠(yuǎn)程對象,它的類型是ApplicationThreadProxy。
Step 11. ApplicationThreadProxy.scheduleRegisteredReceiver
class ApplicationThreadProxy implements IApplicationThread {...... public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode, String dataStr, Bundle extras, boolean ordered, boolean sticky) throws RemoteException {Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(receiver.asBinder()); intent.writeToParcel(data, 0); data.writeInt(resultCode); data.writeString(dataStr); data.writeBundle(extras); data.writeInt(ordered ? 1 : 0); data.writeInt(sticky ? 1 : 0); mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); } ...... }
這里通過Binder驅(qū)動程序就進(jìn)入到ApplicationThread.scheduleRegisteredReceiver函數(shù)去了。
Step 12. ApplicaitonThread.scheduleRegisteredReceiver
public final class ActivityThread {...... private final class ApplicationThread extends ApplicationThreadNative {...... // This function exists to make sure all receiver dispatching is // correctly ordered, since these are one-way calls and the binder driver // applies transaction ordering per object for such calls. public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, int resultCode, String dataStr, Bundle extras, boolean ordered, boolean sticky) throws RemoteException {receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky); } ...... } ...... }
這里的receiver是在前面過程分析中的Step 4中創(chuàng)建的,它的具體類型是LoadedApk.ReceiverDispatcher.InnerReceiver,即定義在LoadedApk類的內(nèi)部類ReceiverDispatcher里面的一個內(nèi)部類InnerReceiver,這里調(diào)用它的performReceive函數(shù)。
Step 13. InnerReceiver.performReceive
final class LoadedApk {...... static final class ReceiverDispatcher {final static class InnerReceiver extends IIntentReceiver.Stub {...... public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) {LoadedApk.ReceiverDispatcher rd = mDispatcher.get(); ...... if (rd != null) {rd.performReceive(intent, resultCode, data, extras, ordered, sticky); } else {...... } } } ...... } ...... }
這里,它只是簡單地調(diào)用ReceiverDispatcher的performReceive函數(shù)來進(jìn)一步處理,這里的ReceiverDispatcher類是LoadedApk類里面的一個內(nèi)部類。
Step 14. ReceiverDispatcher.performReceive
final class LoadedApk {...... static final class ReceiverDispatcher {...... public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) {...... Args args = new Args(); args.mCurIntent = intent; args.mCurCode = resultCode; args.mCurData = data; args.mCurMap = extras; args.mCurOrdered = ordered; args.mCurSticky = sticky; if (!mActivityThread.post(args)) {...... } } ...... } ...... }
這里mActivityThread成員變量的類型為Handler,它是前面MainActivity注冊廣播接收器時,從ActivityThread取得的。
這里ReceiverDispatcher借助這個Handler,把這個廣播以消息的形式放到MainActivity所在的這個ActivityThread的消息隊列中去,因此,ReceiverDispatcher不等這個廣播被MainActivity處理就返回了,這里也體現(xiàn)了廣播的發(fā)送和處理是異步進(jìn)行的。
注意這里處理消息的方式是通過Handler.post函數(shù)進(jìn)行的,post函數(shù)的參數(shù)是Runnable類型的,這個消息最終會調(diào)用這個這個參數(shù)的run成員函數(shù)來處理。這里的Args類是LoadedApk類的內(nèi)部類ReceiverDispatcher的一個內(nèi)部類,它繼承于Runnable類,因此,可以作為mActivityThread.post的參數(shù)傳進(jìn)去,代表這個廣播的intent也保存在這個Args實例中。
Step 15. Hanlder.post
這個函數(shù)定義在frameworks/base/core/java/android/os/Handler.java文件中,它的作用就是把消息放在消息隊列中,然后就返回了,這個消息最終會在傳進(jìn)來的Runnable類型的參數(shù)的run成員函數(shù)中進(jìn)行處理。
Step 16. Args.run
final class LoadedApk {...... static final class ReceiverDispatcher {...... final class Args implements Runnable {...... public void run() {BroadcastReceiver receiver = mReceiver; ...... Intent intent = mCurIntent; ...... try {ClassLoader cl = mReceiver.getClass().getClassLoader(); intent.setExtrasClassLoader(cl); if (mCurMap != null) {mCurMap.setClassLoader(cl); } receiver.setOrderedHint(true); receiver.setResult(mCurCode, mCurData, mCurMap); receiver.clearAbortBroadcast(); receiver.setOrderedHint(mCurOrdered); receiver.setInitialStickyHint(mCurSticky); receiver.onReceive(mContext, intent); } catch (Exception e) {...... } ...... } ...... } ...... } ...... }
這里的mReceiver是ReceiverDispatcher類的成員變量,它的類型是BroadcastReceiver,這里它就是MainActivity注冊廣播接收器時創(chuàng)建的BroadcastReceiver實例了。
有了這個ReceiverDispatcher實例之后,就可以調(diào)用它的onReceive函數(shù)把這個廣播分發(fā)給它處理了。
Step 17. BroadcastReceiver.onReceive
public class MainActivity extends Activity implements OnClickListener {...... private BroadcastReceiver counterActionReceiver = new BroadcastReceiver(){public void onReceive(Context context, Intent intent) {int counter = intent.getIntExtra(CounterService.COUNTER_VALUE, 0); String text = String.valueOf(counter); counterText.setText(text); Log.i(LOG_TAG, "Receive counter event"); } } ...... }
這樣,MainActivity里面的定義的BroadcastReceiver實例counterActionReceiver就收到這個廣播并進(jìn)行處理了。
至此,Android應(yīng)用程序發(fā)送廣播的過程就分析完成了。
最后,我們總結(jié)一下這個Android應(yīng)用程序發(fā)送廣播的過程:
Step 1 - Step 7,計數(shù)器服務(wù)CounterService通過sendBroadcast把一個廣播通過Binder進(jìn)程間通信機制發(fā)送給ActivityManagerService,ActivityManagerService根據(jù)這個廣播的Action類型找到相應(yīng)的廣播接收器,然后把這個廣播放進(jìn)自己的消息隊列中去,就完成第一階段對這個廣播的異步分發(fā)了;Step 8 - Step 15,ActivityManagerService在消息循環(huán)中處理這個廣播,并通過Binder進(jìn)程間通信機制把這個廣播分發(fā)給注冊的廣播接收分發(fā)器ReceiverDispatcher,ReceiverDispatcher把這個廣播放進(jìn)MainActivity所在的線程的消息隊列中去,就完成第二階段對這個廣播的異步分發(fā)了;Step 16 - Step 17, ReceiverDispatcher的內(nèi)部類Args在MainActivity所在的線程消息循環(huán)中處理這個廣播,最終是將這個廣播分發(fā)給所注冊的BroadcastReceiver實例的onReceive函數(shù)進(jìn)行處理。標(biāo)簽:
相關(guān)推薦:
精彩放送:
- []天天安卓模擬器是什么?安卓模擬器電腦版下載
- []csv文件用什么打開?什么是CSV文件?
- []利好政策漸次落地 多地房貸利率繼續(xù)下行
- []當(dāng)前最新:龐溟專欄丨因城施策、動態(tài)調(diào)整,更好滿足剛性住房需求
- []當(dāng)前熱門:教育金規(guī)劃的主要原則是什么
- []熱點在線丨旅游險的個人責(zé)任險和旅行責(zé)任險的區(qū)別
- []天天最資訊丨濟南市醫(yī)療保險查詢方法
- []環(huán)球速遞!領(lǐng)取養(yǎng)老保險金的條件是什么
- []每日看點!濟寧醫(yī)療保險個人賬戶余額查詢方法
- []世界速訊:天津首個成規(guī)模M0新型產(chǎn)業(yè)用地項目濱海—中關(guān)村北塘灣產(chǎn)業(yè)園啟動
- []天天觀察:住宅銷售待回溫 龍湖搶先披露經(jīng)營性收入234億元
- []焦點短訊!深港口岸1月8日起分階段有序恢復(fù)人員正常往來
- []全球焦點!廣州最大古村落保留地之一文沖幸福里歷史文化街區(qū)開街營業(yè)
- []資本月報 | 融資總額7年來首次跌破萬億,年末迎來配股潮(2022年12月)
- []剛剛!800億A股巨頭宣布:實控人由馬云變?yōu)闊o實控人!多家上市公司披露權(quán)益變動
- []世界熱點!印力集團與上海港城達(dá)成戰(zhàn)略合作 規(guī)劃打造上海臨港片區(qū)商業(yè)地標(biāo)
- []今日快看!福州住房公積金中心:第一季度住房公積金最高可貸80萬
- []阿里巴巴:馬云將不再控制君瀚和君澳持有的螞蟻集團多數(shù)投票權(quán)
- []全球熱點評!羊絨世家沖刺深交所上市:不再新增加盟商,蔣慶云父子為實控人
- []建發(fā)股份擬以現(xiàn)金方式協(xié)議收購紅星美凱龍不超過30%股份
- []焦點快看:新湖中寶計劃減持不超過7505.15萬股已回購股份 占總股本0.87%
- []當(dāng)前熱文:美康生物:2022年12月30日江西省醫(yī)保局公示肝功生化試劑帶量采購擬中選結(jié)果,降價幅度符合市場預(yù)期
- []微信版花唄怎么開通
- []老馬茶室 | 新年開門紅,接近壓力位宜減倉
- []全球今日訊!港股暴漲40%!后市行情持續(xù)性如何?基金經(jīng)理最新解讀
- []熱點在線丨儲蓄卡和信用卡的區(qū)別
- []香港內(nèi)地今日通關(guān),香港赴內(nèi)地機酒訂單提前三天大漲兩倍
- []瑞信:預(yù)計今年恒生指數(shù)、滬深300等均存在20%以上上漲空間
- []世界聚焦:近十年主動權(quán)益冠軍基金盤點:“冠軍魔咒”頻現(xiàn) 高收益率難保持
- []格瑞士馬來西亞4MW鋁合金光伏地面工程
- []天天微資訊!哪里的增量配電有錢賺?各地配電價格指導(dǎo)意見盤點:滇貴實操性強 廣東可指定套餐
- []世界即時看!光伏支架龍頭成為“失信被執(zhí)行人”
- []【環(huán)球新視野】權(quán)威數(shù)據(jù) | 2022年二季度全國新能源電力消納評估分析
- []每日熱聞!黃山睿基新能源公司成為“失信被執(zhí)行人”
- []稅優(yōu)識別碼在保單上怎么找到,一般是在右上角
- []每日速訊:網(wǎng)上退保如何辦理?
- []車險千萬不要提前買,提前10到30天比較好
- []沃森生物:股權(quán)轉(zhuǎn)讓前,江蘇沃森主要承擔(dān)公司4價流感病毒裂解疫苗的開發(fā)工作
- []車胎沒氣可以找保險公司嗎?
- []只買三者險不買車損險,當(dāng)然可以
- []即時看!深圳10大最慘新盤,誰碰誰倒霉?
- []春運人群旅行距離達(dá)三年新高,航班、鐵路出行半徑大增四到七成
- []堅持用戶至上,擁抱新能源時代紅旗品牌數(shù)字化轉(zhuǎn)型看點十足
- []當(dāng)前快播:信質(zhì)集團:公司與BYD一直保持緊密合作關(guān)系,公司為其提供新能源定、轉(zhuǎn)子鐵芯及部分總成業(yè)務(wù)
- []世界時訊:環(huán)京樓市調(diào)查:元旦期間銷量環(huán)比增長,房價仍在“筑底”
- []華統(tǒng)股份:公司火腿產(chǎn)能可達(dá)年80萬只,目前向市場保持正常有序供應(yīng)
- []快看:豪邁科技:公司燃?xì)廨啓C零部件業(yè)務(wù)客戶國外有三菱、西門子和GE,也已經(jīng)與東方電氣和上海電氣開展合作
- []全球看熱訊:【金融頭條】38城房價連降三個月 首套房貸利率開啟動態(tài)調(diào)整
- []天天視點!英力股份:公司將利用現(xiàn)有產(chǎn)能生產(chǎn)接線盒、邊框、背板等光伏組件配套產(chǎn)品,但暫時還未生產(chǎn)
- []天天報道:濮陽惠成:公司未開展光刻膠業(yè)務(wù),公司的產(chǎn)品主要應(yīng)用于電氣絕緣、復(fù)合材料、OLED光電材料等多個領(lǐng)域
- []香港旅游業(yè)迎曙光;旅行社踏上復(fù)蘇之路 | 一周速覽
- []環(huán)球觀焦點:愛彼迎中國出境游流量激增;達(dá)美航空投資10億美元提供免費 WiFi | 大公司簡報
- []當(dāng)前快播:邏輯更正的新線索出現(xiàn)!“春季”行情已經(jīng)發(fā)動
- []世界今日報丨百余家上市公司業(yè)績預(yù)告 超七成預(yù)喜
- []民航局:取消目的地為北京的國際客運航班從指定第一入境點入境
- []桂浩明:券商配股的悖論 券商為什么熱衷于配股?
- []世界報道:除夕火車票今日開售,熱門線路再現(xiàn)一票難求
- []全球熱點評!2023年首周5只基金按下“終止鍵” 近千只基金瀕臨清盤紅線
- []當(dāng)前消息!專家:首套房貸利率下限更靈活,剛需、改善人群將受益
- []世界今亮點!消費全面復(fù)蘇!2023年北京零售市場預(yù)計新增67.5萬平方米
- []政策加碼力促首套住房消費需求釋放
- []“仰望”概念爆發(fā),多股漲停!人氣龍頭股罕見“炸板”
- []私募看市:春季攻勢箭在弦上 看好復(fù)蘇預(yù)期下的龍頭股
- []焦點訊息:什么情況?基金暴賺買的人少 虧錢反而人氣旺 “北熱南冷”如何解?
- []國家電投浙江公司:分布式光伏電站數(shù)字化管控應(yīng)用實踐及經(jīng)驗分享
- []每日時訊!TOPCon Show Time!9.7晶科能源邀行業(yè)大咖共聚N型浦江夜
- []焦點速訊:氫能供需失調(diào)問題突出
- []當(dāng)前資訊!強強聯(lián)合!天海防務(wù)&中舟風(fēng)電攜手進(jìn)軍海上光伏!
- []氫能標(biāo)準(zhǔn)丨《氫能汽車用燃料 液氫》全文發(fā)布
- []環(huán)球看熱訊:戶外運動保險和普通旅游險有哪些不同
- []環(huán)球播報:易方達(dá)科創(chuàng)50ETF單日成交創(chuàng)歷史新高 科創(chuàng)板今年一季度行情可期
- []世界今日訊!大病保險出險報案后怎么理賠?
- []今日要聞!英皇國際擬11.37億港元出售香港屯門13層高商廈
- []世界微資訊!被忽悠買錯保險怎么辦?
- []世界視點!龍湖2022年總合同銷售金額2016億元 實現(xiàn)經(jīng)營性收入234億
- []環(huán)球滾動:星河灣旗下首座商業(yè)樓宇廣州“星河灣中心”啟用 建面逾12萬平
- []焦點速讀:不記名團體意外險的特點
- []仁恒置地2022年合約預(yù)售680.9億元 同比上升14.3%
- []重大疾病保險怎么理賠?
- []世界即時:伊戈爾:公司高頻電感、智能箱變等產(chǎn)品可以應(yīng)用在儲能領(lǐng)域
- []全球熱點評!南京市秦淮區(qū)開展食品安全突發(fā)事件應(yīng)急演練
- []盛視科技:1月5日公司高管賴時伍減持公司股份合計3000股
- []世界報道:大唐集團2022年合約銷售196億元 銷售面積197萬平米
- []【天天快播報】中原建業(yè)2022年在管項目合約銷售213.17億元
- []【獨家】陽光100中國2022年全年合約銷售12.05億元
- []速讀:深免集團中標(biāo)深圳機場T3航站樓進(jìn)出境免稅店項目運營
- []環(huán)球熱頭條丨央行:2022年第三季度我國支付體系運行平穩(wěn)
- []信息:藍(lán)海華騰:1月5日公司高管徐學(xué)海、時仁帥減持公司股份合計33.22萬股
- []世界微動態(tài)丨中公教育:1月5日公司高管王振東減持公司股份合計130萬股
- []天天最新:康龍化成:1月5日公司高管鄭北、樓小強減持公司股份合計65萬股
- []每日訊息!王導(dǎo):黃金1837繼續(xù)放空,目標(biāo)1820
- []美國12月非農(nóng)增幅下滑,薪資增幅大跌,黃金短線跳漲逾8美元
- []【天天聚看點】綠城集團2022年總合同銷售3003億元 代建銷售額約875億
- []報道:A股現(xiàn)抗病毒毛巾,抗病毒活性率超99.99%!公司稱已小批量發(fā)貨
- []天天關(guān)注:元旦后旅游復(fù)蘇現(xiàn)“二級火箭”:攜程春節(jié)游訂單連續(xù)5天日增30%
- []“致敬最美的你”——市委互聯(lián)網(wǎng)企業(yè)工委“踐行二十大精神 踔厲奮發(fā)新征程”活動圓滿舉行
- []玉龍股份:1月5日公司高管李振川、張鵬增持公司股份合計8000股
- []世界今日訊!融信中國單月合約銷售10.76億 2022年累計銷售578.73億元
- []天天新資訊:普元信息:1月4日至1月5日王蔥權(quán)減持公司股份合計12.13萬股
- []全球熱議:杰瑞股份:我們會積極與投資者保持溝通
- 全球觀速訊丨圖片網(wǎng)站有哪些推薦?10個免費商用的圖片網(wǎng)站分享
- 觀熱點:筆記本鏈接不上wifi怎么辦?解決辦法如下
- 全球今熱點:數(shù)據(jù)庫設(shè)計中的關(guān)系模型——數(shù)據(jù)模型
- 工況密度和標(biāo)況密度怎么換算?通常工況參數(shù)有哪些?
- Exadata的防火墻端口是什么?詳情介紹
- 全球視點!SMT生產(chǎn)線的組成及分類 你了解多少?
- 環(huán)球今日訊!C++/MFC串口通信——光源控制器控制
- 播報:在Android系統(tǒng)中為什么需要廣播機制?
- 每日熱文:如何快速學(xué)會一門編程語言?5種編程入門方法分享
- 世界微速訊:免費且超級好用的搜索引擎INSO上線 界面UI是采用FlatUI設(shè)計
- B站注冊資本增幅400%至5億 目前由陳睿全資持股
- 光源資本出任獨家財務(wù)顧問 沐曦集成電路10億元A輪融資宣告完成
- 巨輪智能2021年上半年營收11.24億元 期內(nèi)研發(fā)費用投入增長19.05%
- 紅棗期貨尾盤拉升大漲近6% 目前紅棗市場總庫存約30萬噸
- 嘉銀金科發(fā)布2021年Q2財報 期內(nèi)凈利潤達(dá)1.27億元同比增長208%
- 成都銀行2021上半年凈利33.89億元 期內(nèi)實現(xiàn)營收同比增長17.27億元
- 汽車之家發(fā)布2021年第二季度業(yè)績 期內(nèi)新能源汽車品牌收入增長238%
- 中信銀行上半年實現(xiàn)凈利潤290.31億元 期末不良貸款余額706.82億元
- 光伏概念掀起漲停潮交易價格創(chuàng)新高 全天成交額達(dá)1.29億元
- 上半年生物藥大增45% 關(guān)鍵財務(wù)指標(biāo)好轉(zhuǎn)營收賬款持續(xù)下降
- 熱門看點:海口明光國際大廈1-5層資產(chǎn)第七次網(wǎng)拍 起拍價降至8603萬元
- 聚焦:赤峰黃金:1月4日李金千減持公司股份合計2.47萬股
- 正榮地產(chǎn)2022年全年實現(xiàn)合約銷售334.32億元
- 天天觀熱點:復(fù)星國際出售4家公司股權(quán) 總價67億元
- 中公教育:大股東魯忠芳女士的借款正在陸續(xù)到賬
- 南極電商:公司會根據(jù)各渠道發(fā)展情況積極調(diào)整業(yè)務(wù)規(guī)劃,以期用最便利的方式向消費者提供高性價比的產(chǎn)品
- 環(huán)球熱文:佳源國際第八次延長票據(jù)交換要約及同意征求屆滿期限
- 世界實時:國家郵政局:2022年12月中國快遞發(fā)展指數(shù)為327.2,同比提升5%
- 環(huán)球?qū)崟r:中海商業(yè)簽約上海世博CK商辦、深圳星通大廈兩個輕資產(chǎn)項目
- 中海2022年合約銷售2947.62億元 收購了609萬平方米土地
- 速遞!飛豬升級春節(jié)服務(wù)保障:訂機票送防疫包、火車票無憂退、跟團游享五重保障
- 天天微速訊:產(chǎn)業(yè)類租戶成為寫字樓和商務(wù)園區(qū)需求增長點 倉儲物流供需均破歷史記錄
- 辛合學(xué)堂:玩轉(zhuǎn)短視頻,他將興趣變成能力
- 看熱訊:1月6日鹿山新材漲停分析:異質(zhì)結(jié)電池HJT,光伏,OLED概念熱股
- 世界視訊!弘陽地產(chǎn):2022累計合約銷售額為352.02億元
- 【全球新要聞】完美世界:公司下屬基金通過與環(huán)球影業(yè)的片單合作參與了《侏羅紀(jì)世界3:統(tǒng)治》的投資
- 全球報道:2023年房地產(chǎn)行業(yè)怎么走?多部門表態(tài)大力支持剛需購房
- 全球播報:1月6日東方盛虹漲停分析:光伏,PTA,滌綸概念熱股
- 當(dāng)前時訊:樓市再迎重磅利好!首套房貸利率新政影響有多大?機構(gòu)這么說丨火線解讀
- 快播:1月6日福萊特漲停分析:光伏,玻璃概念熱股
- 金地商置:2022累計合約銷售額617.73億元
- 全球今日報丨1月6日芯能科技漲停分析:光伏,BIPV概念,儲能概念熱股
- Halo 2023,攜手HALO光環(huán)家居一同探尋原生生活的100種可能
- 環(huán)球熱門:?洛陽老君山,被制造的頂流景區(qū)
- 焦點快報!一張圖:黃金原油匯股"樞紐點+多空占比"一覽(2023/01/06周五)
- 環(huán)球觀熱點:中信建投期貨1月6日交易策略
- 環(huán)球即時:現(xiàn)貨黃金交易策略:決戰(zhàn)非農(nóng)!金價面臨大跌風(fēng)險?
- 美原油交易策略:油價溫和反彈,后多頭機會來了?
- 天天快資訊丨1月6日匯市觀潮:歐元、英鎊和日元技術(shù)分析
- 2023春節(jié)車險優(yōu)惠嗎,一般有優(yōu)惠
- 世界今日訊!2023保險公司春節(jié)上班嗎,不上班
- 天天微速訊:2023春節(jié)公積金提取多久到賬,不能到賬
- 環(huán)球看點!2023公積金春節(jié)提現(xiàn)能到賬嗎,不能到賬
- 從讓理想飛揚,到夢想點亮未來,紅旗品牌吹響新能源號角
- 公積金有多少可以貸款,看當(dāng)?shù)氐木唧w規(guī)定
- 法恩莎的2022:讓生活更加藝術(shù)
- 【當(dāng)前熱聞】教師節(jié)的名人名言有哪些?分享一些教師節(jié)的名人名言?
- 多只ST股亮出“保殼”組合拳,退市風(fēng)險能否解除?
- 當(dāng)前熱門:北京自住商品房條件是什么?申請購買自住商品房的條件有哪些?
- 填報志愿的第一二志愿有什么區(qū)別?平行志愿是什么意思?
- 天天信息:中國什么時候成為常任理事國?中國成為常任理事國的意義?
- 杏子酒怎么釀制?杏子酒的釀制方法?
- 當(dāng)前焦點!楊光的快樂生活砸車是第幾部第幾集?講述了什么劇情?
- 當(dāng)前資訊!鐵杵磨針的作者是誰?鐵杵磨針的作者資料介紹?
- 超光速會發(fā)生什么?超光速源自于什么?
- 世界百事通!關(guān)于名勝古跡的對聯(lián)有哪些?
- 環(huán)球速讀:鐵生銹是物理變化還是化學(xué)變化?物理反應(yīng)和化學(xué)反應(yīng)有什么區(qū)別?
- 每日快訊!全球多個地區(qū)計劃運力全面復(fù)蘇,東南亞同比增幅超過50%
- 當(dāng)前通訊!愛彼迎中國出境游流量激增,旅游業(yè)迎新年全面提振
- 八大券商主題策略:起底“鋰、電、光、儲”四大新能源賽道!2023年看好哪些標(biāo)的?
- 告別桌面“盤絲洞” 倍思快充插線板給你整潔的桌面
- 變配電工程包括什么?
- 環(huán)球速讀:飲食安全小知識有哪些?
- 天天熱推薦:25tolife是什么意思?25tolife歌詞簡介?
- 世界簡訊:七臺河有哪些個大學(xué)?七臺河大學(xué)名單一覽?
- 今日快訊:超聲波導(dǎo)入儀是什么?超聲波導(dǎo)入儀的工作原理是什么?
- 全球最資訊丨炒黃金怎么開戶?開戶的基本流程是怎樣的?
- 最新消息:事業(yè)單位文秘專業(yè)知識考什么?
- 【環(huán)球報資訊】王者榮耀里的甄姬是哪個歷史人物?甄姬歷史資料介紹?
- 每日簡訊:南寧有哪些大學(xué)學(xué)校?
- 當(dāng)前報道:粉玫瑰33朵代表什么意思?粉玫瑰的花語是什么?
- 旅游復(fù)蘇與分化:過萬別墅搶不到,三百元民宿無人問
- 【新視野】環(huán)球影城,又開始排長隊了
- 每日視訊:元旦三天海南免稅消費4.22億元,太古地產(chǎn)、中免布局零售項目欲借“東風(fēng)”
- 2022年國際航空運輸市場回顧:復(fù)蘇路上的合作與發(fā)展
- 天天熱門:【東海期貨1月6日產(chǎn)業(yè)鏈日報】能化篇:成品油庫存數(shù)據(jù)良好,油價小幅反彈
- 張坤超級大反攻!兩月零3天 暴漲超40%!大批基金快速回血 有這個共同特點!
- 當(dāng)前關(guān)注:深圳允許二手房交易可“帶押過戶”
- 紫鑫藥業(yè):本公司生產(chǎn)的通竅鼻炎片屬于醫(yī)保乙類藥品
- 昊海生科:國家知識產(chǎn)權(quán)局已于2022年12月30日作出決定,認(rèn)定愛博醫(yī)療的三項專利均存在有效性問題
- 時訊:餐飲與時尚業(yè)態(tài)收縮 北京零售市場2022年四季度租金下降近3%
- 【獨家】杭氧股份:公司未參與上海寶鋼氣體有限公司35%股權(quán)轉(zhuǎn)讓項目
- 中原內(nèi)配:截止2022年12月31日,公司股東總戶數(shù)為59,471戶
- 專注空間更新及場景營造,京投發(fā)展TOD價值持續(xù)兌現(xiàn)
- 深圳推廣二手房“帶押過戶”模式
- 全球頭條:國內(nèi)首個具備獨立運行能力的新能源儲能項目在內(nèi)蒙古并網(wǎng)通電
- 實時:固態(tài)電池量產(chǎn)裝車可期 互聯(lián)網(wǎng)巨頭入局加速商業(yè)化進(jìn)程
- 國泰君安:港股現(xiàn)時仍處于牛熊反轉(zhuǎn)初期 有望進(jìn)入三級火箭行情
- 【全球獨家】外資投行建議“超配”中國資產(chǎn) 多只海外ETF強勢“吸金”
- 熱訊:湖州零碳電廠:建集中儲能46MW/468MWh,實現(xiàn)整村戶用儲能試點突破
- 天天微速訊:五一祝福圖片大全高清 五一祝福圖片大全
- 環(huán)球關(guān)注:DNF十三周年大飛空時代全地圖攻略(附下載)
- 股城網(wǎng)模擬炒股 哪個網(wǎng)站模擬股市?
- 天天速遞!你的曾經(jīng)不想再去解釋是什么歌?厚顏無恥歌曲介紹
- 深圳住房公積金的提取條件是什么?深圳住房公積金提取流程
- 提高&優(yōu)化CS反恐畫面效果/畫質(zhì)(上)
- 孔維詩苑是誰?孔維詩苑的個人資料曝光
- 天天亮點!晉城一中2019中考錄取分?jǐn)?shù)線是多少?多少分能上一中?
- 每日信息:安裝必備軟件——SharePointServer2010版本
- 樂果q9怎么樣?有哪些優(yōu)勢?
- 每日熱門:全球首創(chuàng)!區(qū)塊鏈技術(shù)第一次用于塞拉利昂總統(tǒng)選舉
- 犯非法采礦罪!紫金礦業(yè)被判沒收違法所得4.6億元 并處罰金1500萬元
- 今亮點!Pythonbridge:10個Python開源項目
- 刪除數(shù)據(jù)的方法:GridView1_RowingEdit
- 三文魚被洗白后 病毒究竟如何進(jìn)入北京新發(fā)地?
- 【時快訊】QQ空間怎么設(shè)置簽名?QQ空間的簽名檔在哪里設(shè)置?
- 劉信達(dá)痛批白敬亭:你怎么連嘴都不會親?
- 世界頭條:關(guān)于槨的讀音你知道多少?槨字詳情介紹
- 【環(huán)球速看料】外貿(mào)必備!常用搜索引擎查詢命令匯總