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