播报:在Android系统中为什么需要广播机制?
在Android系统中,广播(Broadcast)是在组件之间传播数据(Intent)的一种机制;这些组件甚至是可以位于不同的进程中,这样它就像Binder机制一样,起到进程间通信的作用。
在Android系统中,为什么需要广播机制呢?如果是两个组件位于不同的进程当中,那么可以用Binder机制来实现,如果两个组件是在同一个进程中,那么它们之间可以用来通信的方式就更多了,这样看来,广播机制似乎是多余的。
然而,广播机制却是不可替代的,它和Binder机制不一样的地方在于,广播的发送者和接收者事先是不需要知道对方的存在的,这样带来的好处便是,系统的各个组件可以松耦合地组织在一起,这样系统就具有高度的可扩展性,容易与其它系统进行集成。
(相关资料图)
使用广播的两个步骤: 1、广播的接收者需要通过调用registerReceiver函数告诉系统,它对什么样的广播有兴趣,即指定IntentFilter,并且向系统注册广播接收器,即指定BroadcastReceiver:
IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION); registerReceiver(counterActionReceiver, counterActionFilter);
这里,指定感兴趣的广播就是CounterService.BROADCAST_COUNTER_ACTION了,而指定的广播接收器就是counterActonReceiver,它是一个BroadcastReceiver类型的实例。
2、广播的发送者通过调用sendBroadcast函数来发送一个指定的广播,并且可以指定广播的相关参数:
Intent intent = new Intent(BROADCAST_COUNTER_ACTION); intent.putExtra(COUNTER_VALUE, counter); sendBroadcast(intent)
这里,指定的广播为CounterService.BROADCAST_COUNTER_ACTION,并且附带的带参数当前的计数器值counter。调用了sendBroadcast函数之后,所有注册了CounterService.BROADCAST_COUNTER_ACTION广播的接收者便可以收到这个广播了。
在第1步中,广播的接收者把广播接收器注册到ActivityManagerService中;在第2步中,广播的发送者同样是把广播发送到ActivityManagerService中,由ActivityManagerService去查找注册了这个广播的接收者,然后把广播分发给它们。
在第2步的分发的过程,其实就是把这个广播转换成一个消息,然后放入到接收器所在的线程消息队列中去,最后就可以在消息循环中调用接收器的onReceive函数了。这里有一个要非常注意的地方是,由于ActivityManagerService把这个广播放进接收器所在的线程消息队列后,就返回了,它不关心这个消息什么时候会被处理,因此,对广播的处理是异步的,即调用sendBroadcast时,这个函数不会等待这个广播被处理完后才返回。
虚线上面Step 1到Step 4步是注册广播接收器的过程,其中Step 2通过LoadedApk.getReceiverDispatcher在LoadedApk内部创建了一个IIntentReceiver接口,并且传递给ActivityManagerService;
虚线下面的Step 5到Step 11是发送广播的过程,在Step 8中,ActivityManagerService利用上面得到的IIntentReceiver远程接口,调用LoadedApk.performReceiver接口,LoadedApk.performReceiver接口通过ActivityThread.H接口的post函数将这个广播消息放入到ActivityThread的消息队列中去,最后这个消息在LoadedApk的Args.run函数中处理,LoadedApk.Args.run函数接着调用MainActivity.BroadcastReceiver的onReceive函数来最终处理这个广播。
注册广播接收器(registerReceiver)
在Android的广播机制中,ActivityManagerService扮演着广播中心的角色,负责系统中所有广播的注册和发布操作,因此,Android应用程序注册广播接收器的过程就把是广播接收器注册到ActivityManagerService的过程。Android应用程序是通过调用ContextWrapper类的registerReceiver函数来把广播接收器BroadcastReceiver注册到ActivityManagerService中去的,而ContextWrapper类本身又借助ContextImpl类来注册广播接收器。
在Android应用程序框架中,Activity和Service类都继承了ContextWrapper类,因此,我们可以在Activity或者Service的子类中调用registerReceiver函数来注册广播接收器。
我们先来看一下MainActivity是如何调用registerReceiver函数来注册广播接收器的:
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; } } ...... }
通过两个函数的中转,最终就进入到ContextImpl.registerReceiverInternal这个函数来了。这里的成员变量mPackageInfo是一个LoadedApk实例,它是用来负责处理广播的接收的。
参数broadcastPermission和scheduler都为null,而参数context是上面的函数通过调用函数getOuterContext得到的,这里它就是指向MainActivity了,因为MainActivity是继承于Context类的,因此,这里用Context类型来引用。
由于条件mPackageInfo != null和context != null都成立,而且条件scheduler == null也成立,于是就调用mMainThread.getHandler来获得一个Handler了,这个Hanlder是后面用来分发ActivityManagerService发送过的广播用的。这里的成员变量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之后,就可以分发消息给应用程序处理了。
再回到上一步的ContextImpl.registerReceiverInternal函数中,它通过mPackageInfo.getReceiverDispatcher函数获得一个IIntentReceiver接口对象rd,这是一个Binder对象,接下来会把它传给ActivityManagerService,ActivityManagerService在收到相应的广播时,就是通过这个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函数中,首先看一下参数r是不是已经有相应的ReceiverDispatcher存在了,如果有,就直接返回了,否则就新建一个ReceiverDispatcher,并且以r为Key值保在一个HashMap中,而这个HashMap以Context,这里即为MainActivity为Key值保存在LoadedApk的成员变量mReceivers中,这样,只要给定一个Activity和BroadcastReceiver,就可以查看LoadedApk里面是否已经存在相应的广播接收发布器ReceiverDispatcher了。
在新建广播接收发布器ReceiverDispatcher时,会在构造函数里面创建一个InnerReceiver实例,这是一个Binder对象,实现了IIntentReceiver接口,可以通过ReceiverDispatcher.getIIntentReceiver函数来获得,获得后就会把它传给ActivityManagerService,以便接收广播。
在ReceiverDispatcher类的构造函数中,还会把传进来的Handle类型的参数activityThread保存下来,以便后面在分发广播的时候使用。
现在,再回到ContextImpl.registerReceiverInternal函数,在获得了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; } ...... }
这个函数通过Binder驱动程序就进入到ActivityManagerService中的registerReceiver函数中去了。
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; } } ...... }
函数首先是获得调用registerReceiver函数的应用程序进程记录块:
ProcessRecord callerApp = null; if (caller != null) {callerApp = getRecordForAppLocked(caller); if (callerApp == null) {...... } }
这里得到的便是应用程序Broadcast的进程记录块了,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;
这里传进来的filter只有一个action,就是前面描述的CounterService.BROADCAST_COUNTER_ACTION了,这里先通过getStickiesLocked函数查找一下有没有对应的sticky intent列表存在。什么是Sticky Intent呢?我们在最后一次调用sendStickyBroadcast函数来发送某个Action类型的广播时,系统会把代表这个广播的Intent保存下来,这样,后来调用registerReceiver来注册相同Action类型的广播接收器,就会得到这个最后发出的广播。这就是为什么叫做Sticky Intent了,这个最后发出的广播虽然被处理完了,但是仍然被粘住在ActivityManagerService中,以便下一个注册相应Action类型的广播接收器还能继承处理。
这里,假设我们不使用sendStickyBroadcast来发送CounterService.BROADCAST_COUNTER_ACTION类型的广播,于是,这里得到的allSticky和sticky都为null了。
继续往下看,这里传进来的receiver不为null,于是,继续往下执行:
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列表中,这个列表的宿主进程是rl.app,这里就是MainActivity所在的进程了,在ActivityManagerService中,用一个进程记录块来表示这个应用程序进程,它里面有一个列表receivers,专门用来保存这个进程注册的广播接收器。接着,又把这个ReceiverList列表以receiver为Key值保存在ActivityManagerService的成员变量mRegisteredReceivers中,这些都是为了方便在收到广播时,快速找到对应的广播接收器的。
再往下看:
BroadcastFilter bf = new BroadcastFilter(filter, rl, permission); rl.add(bf); ...... mReceiverResolver.addFilter(bf);
上面只是把广播接收器receiver保存起来了,但是还没有把它和filter关联起来,这里就创建一个BroadcastFilter来把广播接收器列表rl和filter关联起来,然后保存在ActivityManagerService中的成员变量mReceiverResolver中去。
发送广播(sendBroadcast)的过程分析
前面我们分析了Android应用程序注册广播接收器的过程,这个过程只完成了万里长征的第一步,接下来它还要等待ActivityManagerService将广播分发过来。
广播的发送过程比广播接收器的注册过程要复杂得多了,不过这个过程仍然是以ActivityManagerService为中心。广播的发送者将广播发送到ActivityManagerService,ActivityManagerService接收到这个广播以后,就会在自己的注册中心查看有哪些广播接收器订阅了该广播,然后把这个广播逐一发送到这些广播接收器中,但是ActivityManagerService并不等待广播接收器处理这些广播就返回了,因此,广播的发送和处理是异步的。
在分析广播的发送过程前,我们先来看一下广播发送过程的序列图,然后按照这个序图中的步骤来一步一步分析整个过程。
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类型,我们没有设置这个Intent的MIME类型,因此,这里的resolvedType为null。接下来就调用ActivityManagerService的远程接口ActivityManagerProxy把这个广播发送给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; } } ...... }
这里调用broadcastIntentLocked函数来进一步处理。
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; } ...... } ...... }
这个函数首先是根据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) {...... }
回忆一下前面过程分析中的Step 6(ActivityManagerService.registerReceiver)中,我们将一个filter类型为BROADCAST_COUNTER_ACTION类型的BroadcastFilter实例保存在了ActivityManagerService的成员变量mReceiverResolver中,这个BroadcastFilter实例包含了我们所注册的广播接收器,这里就通过mReceiverResolver.queryIntent函数将这个BroadcastFilter实例取回来。由于注册一个广播类型的接收器可能有多个,所以这里把所有符合条件的的BroadcastFilter实例放在一个List中,然后返回来。在我们这个场景中,这个List就只有一个BroadcastFilter实例了,就是MainActivity注册的那个广播接收器。
继续往下看:
final boolean replacePending = (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
这里是查看一下这个intent的Intent.FLAG_RECEIVER_REPLACE_PENDING位有没有设置,如果设置了的话,ActivityManagerService就会在当前的系统中查看有没有相同的intent还未被处理,如果有的话,就有当前这个新的intent来替换旧的intent。这里,我们没有设置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,且传进来的参数ordered为false,表示要将这个广播发送给所有注册了BROADCAST_COUNTER_ACTION类型广播的接收器,因此,会执行下面的if语句。这个if语句首先创建一个广播记录块BroadcastRecord,里面记录了这个广播是由谁发出的以及要发给谁等相关信息。由于前面得到的replacePending变量为false,因此,不会执行接下来的if语句,即不会检查系统中是否有相同类型的未处理的广播。
这样,这里得到的replaced变量的值也为false,于是,就会把这个广播记录块r放在ActivityManagerService的成员变量mParcelBroadcasts中,等待进一步处理;进一步处理的操作由函数scheduleBroadcastsLocked进行。
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当前是不是正在处理其它广播,如果是的话,这里就先不处理直接返回了,保证所有广播串行处理。
注意这里处理广播的方式,它是通过消息循环来处理,每当ActivityManagerService接收到一个广播时,它就把这个广播放进自己的消息队列去就完事了,根本不管这个广播后续是处理的,因此,这里我们可以看出广播的发送和处理是异步的。
这里的成员变量mHandler是一个在ActivityManagerService内部定义的Handler类变量,通过它的sendEmptyMessage函数把一个类型为BROADCAST_INTENT_MSG的空消息放进ActivityManagerService的消息队列中去。这里的空消息是指这个消息除了有类型信息之外,没有任何其它额外的信息,因为前面已经把要处理的广播信息都保存在mParcelBroadcasts中了,等处理这个消息时,从mParcelBroadcasts就可以读回相关的广播信息了,因此,这里不需要把广播信息再放在消息内容中。
Step 7. Handler.sendEmptyMessage
这个自定义的Handler类实现在frameworks/base/services/java/com/android/server/am/ActivityManagerService.java文件中,它是ActivityManagerService的内部类,调用了它的sendEmptyMessage函数来把一个消息放到消息队列后,一会就会调用它的handleMessage函数来真正处理这个消息:
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; ...... } } } ...... }
这里又调用了ActivityManagerService的processNextBroadcast函数来处理下一个未处理的广播。
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); ...... } ...... } } ...... }
这里传进来的参数fromMsg为true,于是把mBroadcastScheduled重新设为false,这样,下一个广播就能进入到消息队列中进行处理了。前面我们在Step 5中,把一个广播记录块BroadcastRecord放在了mParallelBroadcasts中,因此,这里就把它取出来进行处理了。广播记录块BroadcastRecord的receivers列表中包含了要接收这个广播的目标列表,即前面我们注册的广播接收器,用BroadcastFilter来表示,这里while循环中的for循环就是把这个广播发送给每一个订阅了该广播的接收器了,通过deliverToRegisteredReceiverLocked函数执行。
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) {...... } } } ...... }
函数首先是检查一下广播发送和接收的权限,在我们分析的这个场景中,没有设置权限,因此,这个权限检查就跳过了,这里得到的skip为false,于是进入下面的if语句中。由于上面传时来的ordered参数为false,因此,直接就调用performReceiveLocked函数来进一步执行广播发送的操作了。
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 {...... } } ...... }
注意,这里传进来的参数app是注册广播接收器的Activity所在的进程记录块,在我们分析的这个场景中,由于是MainActivity调用registerReceiver函数来注册这个广播接收器的,因此,参数app所代表的ProcessRecord就是MainActivity所在的进程记录块了;
而参数receiver也是注册广播接收器时传给ActivityManagerService的一个Binder对象,它的类型是IIntentReceiver。
MainActivity在注册广播接收器时,已经把自己的ProcessRecord记录下来了,所以这里的参数app和app.thread均不为null,于是,ActivityManagerService就调用app.thread.scheduleRegisteredReceiver函数来把这个广播分发给MainActivity了。这里的app.thread是一个Binder远程对象,它的类型是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驱动程序就进入到ApplicationThread.scheduleRegisteredReceiver函数去了。
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中创建的,它的具体类型是LoadedApk.ReceiverDispatcher.InnerReceiver,即定义在LoadedApk类的内部类ReceiverDispatcher里面的一个内部类InnerReceiver,这里调用它的performReceive函数。
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 {...... } } } ...... } ...... }
这里,它只是简单地调用ReceiverDispatcher的performReceive函数来进一步处理,这里的ReceiverDispatcher类是LoadedApk类里面的一个内部类。
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处理就返回了,这里也体现了广播的发送和处理是异步进行的。
注意这里处理消息的方式是通过Handler.post函数进行的,post函数的参数是Runnable类型的,这个消息最终会调用这个这个参数的run成员函数来处理。这里的Args类是LoadedApk类的内部类ReceiverDispatcher的一个内部类,它继承于Runnable类,因此,可以作为mActivityThread.post的参数传进去,代表这个广播的intent也保存在这个Args实例中。
Step 15. Hanlder.post
这个函数定义在frameworks/base/core/java/android/os/Handler.java文件中,它的作用就是把消息放在消息队列中,然后就返回了,这个消息最终会在传进来的Runnable类型的参数的run成员函数中进行处理。
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注册广播接收器时创建的BroadcastReceiver实例了。
有了这个ReceiverDispatcher实例之后,就可以调用它的onReceive函数把这个广播分发给它处理了。
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就收到这个广播并进行处理了。
至此,Android应用程序发送广播的过程就分析完成了。
最后,我们总结一下这个Android应用程序发送广播的过程:
Step 1 - Step 7,计数器服务CounterService通过sendBroadcast把一个广播通过Binder进程间通信机制发送给ActivityManagerService,ActivityManagerService根据这个广播的Action类型找到相应的广播接收器,然后把这个广播放进自己的消息队列中去,就完成第一阶段对这个广播的异步分发了;Step 8 - Step 15,ActivityManagerService在消息循环中处理这个广播,并通过Binder进程间通信机制把这个广播分发给注册的广播接收分发器ReceiverDispatcher,ReceiverDispatcher把这个广播放进MainActivity所在的线程的消息队列中去,就完成第二阶段对这个广播的异步分发了;Step 16 - Step 17, ReceiverDispatcher的内部类Args在MainActivity所在的线程消息循环中处理这个广播,最终是将这个广播分发给所注册的BroadcastReceiver实例的onReceive函数进行处理。标签:
相关推荐:
精彩放送:
- []天天安卓模拟器是什么?安卓模拟器电脑版下载
- []csv文件用什么打开?什么是CSV文件?
- []利好政策渐次落地 多地房贷利率继续下行
- []当前最新:庞溟专栏丨因城施策、动态调整,更好满足刚性住房需求
- []当前热门:教育金规划的主要原则是什么
- []热点在线丨旅游险的个人责任险和旅行责任险的区别
- []天天最资讯丨济南市医疗保险查询方法
- []环球速递!领取养老保险金的条件是什么
- []每日看点!济宁医疗保险个人账户余额查询方法
- []世界速讯:天津首个成规模M0新型产业用地项目滨海—中关村北塘湾产业园启动
- []天天观察:住宅销售待回温 龙湖抢先披露经营性收入234亿元
- []焦点短讯!深港口岸1月8日起分阶段有序恢复人员正常往来
- []全球焦点!广州最大古村落保留地之一文冲幸福里历史文化街区开街营业
- []资本月报 | 融资总额7年来首次跌破万亿,年末迎来配股潮(2022年12月)
- []刚刚!800亿A股巨头宣布:实控人由马云变为无实控人!多家上市公司披露权益变动
- []世界热点!印力集团与上海港城达成战略合作 规划打造上海临港片区商业地标
- []今日快看!福州住房公积金中心:第一季度住房公积金最高可贷80万
- []阿里巴巴:马云将不再控制君瀚和君澳持有的蚂蚁集团多数投票权
- []全球热点评!羊绒世家冲刺深交所上市:不再新增加盟商,蒋庆云父子为实控人
- []建发股份拟以现金方式协议收购红星美凯龙不超过30%股份
- []焦点快看:新湖中宝计划减持不超过7505.15万股已回购股份 占总股本0.87%
- []当前热文:美康生物:2022年12月30日江西省医保局公示肝功生化试剂带量采购拟中选结果,降价幅度符合市场预期
- []微信版花呗怎么开通
- []老马茶室 | 新年开门红,接近压力位宜减仓
- []全球今日讯!港股暴涨40%!后市行情持续性如何?基金经理最新解读
- []热点在线丨储蓄卡和信用卡的区别
- []香港内地今日通关,香港赴内地机酒订单提前三天大涨两倍
- []瑞信:预计今年恒生指数、沪深300等均存在20%以上上涨空间
- []世界聚焦:近十年主动权益冠军基金盘点:“冠军魔咒”频现 高收益率难保持
- []格瑞士马来西亚4MW铝合金光伏地面工程
- []天天微资讯!哪里的增量配电有钱赚?各地配电价格指导意见盘点:滇贵实操性强 广东可指定套餐
- []世界即时看!光伏支架龙头成为“失信被执行人”
- []【环球新视野】权威数据 | 2022年二季度全国新能源电力消纳评估分析
- []每日热闻!黄山睿基新能源公司成为“失信被执行人”
- []税优识别码在保单上怎么找到,一般是在右上角
- []每日速讯:网上退保如何办理?
- []车险千万不要提前买,提前10到30天比较好
- []沃森生物:股权转让前,江苏沃森主要承担公司4价流感病毒裂解疫苗的开发工作
- []车胎没气可以找保险公司吗?
- []只买三者险不买车损险,当然可以
- []即时看!深圳10大最惨新盘,谁碰谁倒霉?
- []春运人群旅行距离达三年新高,航班、铁路出行半径大增四到七成
- []坚持用户至上,拥抱新能源时代红旗品牌数字化转型看点十足
- []当前快播:信质集团:公司与BYD一直保持紧密合作关系,公司为其提供新能源定、转子铁芯及部分总成业务
- []世界时讯:环京楼市调查:元旦期间销量环比增长,房价仍在“筑底”
- []华统股份:公司火腿产能可达年80万只,目前向市场保持正常有序供应
- []快看:豪迈科技:公司燃气轮机零部件业务客户国外有三菱、西门子和GE,也已经与东方电气和上海电气开展合作
- []全球看热讯:【金融头条】38城房价连降三个月 首套房贷利率开启动态调整
- []天天视点!英力股份:公司将利用现有产能生产接线盒、边框、背板等光伏组件配套产品,但暂时还未生产
- []天天报道:濮阳惠成:公司未开展光刻胶业务,公司的产品主要应用于电气绝缘、复合材料、OLED光电材料等多个领域
- []香港旅游业迎曙光;旅行社踏上复苏之路 | 一周速览
- []环球观焦点:爱彼迎中国出境游流量激增;达美航空投资10亿美元提供免费 WiFi | 大公司简报
- []当前快播:逻辑更正的新线索出现!“春季”行情已经发动
- []世界今日报丨百余家上市公司业绩预告 超七成预喜
- []民航局:取消目的地为北京的国际客运航班从指定第一入境点入境
- []桂浩明:券商配股的悖论 券商为什么热衷于配股?
- []世界报道:除夕火车票今日开售,热门线路再现一票难求
- []全球热点评!2023年首周5只基金按下“终止键” 近千只基金濒临清盘红线
- []当前消息!专家:首套房贷利率下限更灵活,刚需、改善人群将受益
- []世界今亮点!消费全面复苏!2023年北京零售市场预计新增67.5万平方米
- []政策加码力促首套住房消费需求释放
- []“仰望”概念爆发,多股涨停!人气龙头股罕见“炸板”
- []私募看市:春季攻势箭在弦上 看好复苏预期下的龙头股
- []焦点讯息:什么情况?基金暴赚买的人少 亏钱反而人气旺 “北热南冷”如何解?
- []国家电投浙江公司:分布式光伏电站数字化管控应用实践及经验分享
- []每日时讯!TOPCon Show Time!9.7晶科能源邀行业大咖共聚N型浦江夜
- []焦点速讯:氢能供需失调问题突出
- []当前资讯!强强联合!天海防务&中舟风电携手进军海上光伏!
- []氢能标准丨《氢能汽车用燃料 液氢》全文发布
- []环球看热讯:户外运动保险和普通旅游险有哪些不同
- []环球播报:易方达科创50ETF单日成交创历史新高 科创板今年一季度行情可期
- []世界今日讯!大病保险出险报案后怎么理赔?
- []今日要闻!英皇国际拟11.37亿港元出售香港屯门13层高商厦
- []世界微资讯!被忽悠买错保险怎么办?
- []世界视点!龙湖2022年总合同销售金额2016亿元 实现经营性收入234亿
- []环球滚动:星河湾旗下首座商业楼宇广州“星河湾中心”启用 建面逾12万平
- []焦点速读:不记名团体意外险的特点
- []仁恒置地2022年合约预售680.9亿元 同比上升14.3%
- []重大疾病保险怎么理赔?
- []世界即时:伊戈尔:公司高频电感、智能箱变等产品可以应用在储能领域
- []全球热点评!南京市秦淮区开展食品安全突发事件应急演练
- []盛视科技:1月5日公司高管赖时伍减持公司股份合计3000股
- []世界报道:大唐集团2022年合约销售196亿元 销售面积197万平米
- []【天天快播报】中原建业2022年在管项目合约销售213.17亿元
- []【独家】阳光100中国2022年全年合约销售12.05亿元
- []速读:深免集团中标深圳机场T3航站楼进出境免税店项目运营
- []环球热头条丨央行:2022年第三季度我国支付体系运行平稳
- []信息:蓝海华腾:1月5日公司高管徐学海、时仁帅减持公司股份合计33.22万股
- []世界微动态丨中公教育:1月5日公司高管王振东减持公司股份合计130万股
- []天天最新:康龙化成:1月5日公司高管郑北、楼小强减持公司股份合计65万股
- []每日讯息!王导:黄金1837继续放空,目标1820
- []美国12月非农增幅下滑,薪资增幅大跌,黄金短线跳涨逾8美元
- []【天天聚看点】绿城集团2022年总合同销售3003亿元 代建销售额约875亿
- []报道:A股现抗病毒毛巾,抗病毒活性率超99.99%!公司称已小批量发货
- []天天关注:元旦后旅游复苏现“二级火箭”:携程春节游订单连续5天日增30%
- []“致敬最美的你”——市委互联网企业工委“践行二十大精神 踔厉奋发新征程”活动圆满举行
- []玉龙股份:1月5日公司高管李振川、张鹏增持公司股份合计8000股
- []世界今日讯!融信中国单月合约销售10.76亿 2022年累计销售578.73亿元
- []天天新资讯:普元信息:1月4日至1月5日王葱权减持公司股份合计12.13万股
- []全球热议:杰瑞股份:我们会积极与投资者保持沟通
- B站注册资本增幅400%至5亿 目前由陈睿全资持股
- 光源资本出任独家财务顾问 沐曦集成电路10亿元A轮融资宣告完成
- 巨轮智能2021年上半年营收11.24亿元 期内研发费用投入增长19.05%
- 红枣期货尾盘拉升大涨近6% 目前红枣市场总库存约30万吨
- 嘉银金科发布2021年Q2财报 期内净利润达1.27亿元同比增长208%
- 成都银行2021上半年净利33.89亿元 期内实现营收同比增长17.27亿元
- 汽车之家发布2021年第二季度业绩 期内新能源汽车品牌收入增长238%
- 中信银行上半年实现净利润290.31亿元 期末不良贷款余额706.82亿元
- 光伏概念掀起涨停潮交易价格创新高 全天成交额达1.29亿元
- 上半年生物药大增45% 关键财务指标好转营收账款持续下降
- 热门看点:海口明光国际大厦1-5层资产第七次网拍 起拍价降至8603万元
- 聚焦:赤峰黄金:1月4日李金千减持公司股份合计2.47万股
- 正荣地产2022年全年实现合约销售334.32亿元
- 天天观热点:复星国际出售4家公司股权 总价67亿元
- 中公教育:大股东鲁忠芳女士的借款正在陆续到账
- 南极电商:公司会根据各渠道发展情况积极调整业务规划,以期用最便利的方式向消费者提供高性价比的产品
- 环球热文:佳源国际第八次延长票据交换要约及同意征求届满期限
- 世界实时:国家邮政局:2022年12月中国快递发展指数为327.2,同比提升5%
- 环球实时:中海商业签约上海世博CK商办、深圳星通大厦两个轻资产项目
- 中海2022年合约销售2947.62亿元 收购了609万平方米土地
- 速递!飞猪升级春节服务保障:订机票送防疫包、火车票无忧退、跟团游享五重保障
- 天天微速讯:产业类租户成为写字楼和商务园区需求增长点 仓储物流供需均破历史记录
- 辛合学堂:玩转短视频,他将兴趣变成能力
- 看热讯:1月6日鹿山新材涨停分析:异质结电池HJT,光伏,OLED概念热股
- 世界视讯!弘阳地产:2022累计合约销售额为352.02亿元
- 【全球新要闻】完美世界:公司下属基金通过与环球影业的片单合作参与了《侏罗纪世界3:统治》的投资
- 全球报道:2023年房地产行业怎么走?多部门表态大力支持刚需购房
- 全球播报:1月6日东方盛虹涨停分析:光伏,PTA,涤纶概念热股
- 当前时讯:楼市再迎重磅利好!首套房贷利率新政影响有多大?机构这么说丨火线解读
- 快播:1月6日福莱特涨停分析:光伏,玻璃概念热股
- 金地商置:2022累计合约销售额617.73亿元
- 全球今日报丨1月6日芯能科技涨停分析:光伏,BIPV概念,储能概念热股
- Halo 2023,携手HALO光环家居一同探寻原生生活的100种可能
- 环球热门:洛阳老君山,被制造的顶流景区
- 焦点快报!一张图:黄金原油汇股"枢纽点+多空占比"一览(2023/01/06周五)
- 环球观热点:中信建投期货1月6日交易策略
- 环球即时:现货黄金交易策略:决战非农!金价面临大跌风险?
- 美原油交易策略:油价温和反弹,后多头机会来了?
- 天天快资讯丨1月6日汇市观潮:欧元、英镑和日元技术分析
- 2023春节车险优惠吗,一般有优惠
- 世界今日讯!2023保险公司春节上班吗,不上班
- 天天微速讯:2023春节公积金提取多久到账,不能到账
- 环球看点!2023公积金春节提现能到账吗,不能到账
- 从让理想飞扬,到梦想点亮未来,红旗品牌吹响新能源号角
- 公积金有多少可以贷款,看当地的具体规定
- 法恩莎的2022:让生活更加艺术
- 【当前热闻】教师节的名人名言有哪些?分享一些教师节的名人名言?
- 多只ST股亮出“保壳”组合拳,退市风险能否解除?
- 当前热门:北京自住商品房条件是什么?申请购买自住商品房的条件有哪些?
- 填报志愿的第一二志愿有什么区别?平行志愿是什么意思?
- 天天信息:中国什么时候成为常任理事国?中国成为常任理事国的意义?
- 杏子酒怎么酿制?杏子酒的酿制方法?
- 当前焦点!杨光的快乐生活砸车是第几部第几集?讲述了什么剧情?
- 当前资讯!铁杵磨针的作者是谁?铁杵磨针的作者资料介绍?
- 超光速会发生什么?超光速源自于什么?
- 世界百事通!关于名胜古迹的对联有哪些?
- 环球速读:铁生锈是物理变化还是化学变化?物理反应和化学反应有什么区别?
- 每日快讯!全球多个地区计划运力全面复苏,东南亚同比增幅超过50%
- 当前通讯!爱彼迎中国出境游流量激增,旅游业迎新年全面提振
- 八大券商主题策略:起底“锂、电、光、储”四大新能源赛道!2023年看好哪些标的?
- 告别桌面“盘丝洞” 倍思快充插线板给你整洁的桌面
- 变配电工程包括什么?
- 环球速读:饮食安全小知识有哪些?
- 天天热推荐:25tolife是什么意思?25tolife歌词简介?
- 世界简讯:七台河有哪些个大学?七台河大学名单一览?
- 今日快讯:超声波导入仪是什么?超声波导入仪的工作原理是什么?
- 全球最资讯丨炒黄金怎么开户?开户的基本流程是怎样的?
- 最新消息:事业单位文秘专业知识考什么?
- 【环球报资讯】王者荣耀里的甄姬是哪个历史人物?甄姬历史资料介绍?
- 每日简讯:南宁有哪些大学学校?
- 当前报道:粉玫瑰33朵代表什么意思?粉玫瑰的花语是什么?
- 旅游复苏与分化:过万别墅抢不到,三百元民宿无人问
- 【新视野】环球影城,又开始排长队了
- 每日视讯:元旦三天海南免税消费4.22亿元,太古地产、中免布局零售项目欲借“东风”
- 2022年国际航空运输市场回顾:复苏路上的合作与发展
- 当前视讯!华泰证券:预计2023年光伏玻璃行业竞争或将更趋激烈
- 天天热门:【东海期货1月6日产业链日报】能化篇:成品油库存数据良好,油价小幅反弹
- 张坤超级大反攻!两月零3天 暴涨超40%!大批基金快速回血 有这个共同特点!
- 当前关注:深圳允许二手房交易可“带押过户”
- 紫鑫药业:本公司生产的通窍鼻炎片属于医保乙类药品
- 昊海生科:国家知识产权局已于2022年12月30日作出决定,认定爱博医疗的三项专利均存在有效性问题
- 时讯:餐饮与时尚业态收缩 北京零售市场2022年四季度租金下降近3%
- 【独家】杭氧股份:公司未参与上海宝钢气体有限公司35%股权转让项目
- 中原内配:截止2022年12月31日,公司股东总户数为59,471户
- 专注空间更新及场景营造,京投发展TOD价值持续兑现
- 深圳推广二手房“带押过户”模式
- 全球头条:国内首个具备独立运行能力的新能源储能项目在内蒙古并网通电
- 实时:固态电池量产装车可期 互联网巨头入局加速商业化进程
- 国泰君安:港股现时仍处于牛熊反转初期 有望进入三级火箭行情
- 【全球独家】外资投行建议“超配”中国资产 多只海外ETF强势“吸金”
- 热讯:湖州零碳电厂:建集中储能46MW/468MWh,实现整村户用储能试点突破
- 天天微速讯:五一祝福图片大全高清 五一祝福图片大全
- 环球关注:DNF十三周年大飞空时代全地图攻略(附下载)
- 股城网模拟炒股 哪个网站模拟股市?
- 天天速递!你的曾经不想再去解释是什么歌?厚颜无耻歌曲介绍
- 深圳住房公积金的提取条件是什么?深圳住房公积金提取流程
- 提高&优化CS反恐画面效果/画质(上)
- 孔维诗苑是谁?孔维诗苑的个人资料曝光
- 天天亮点!晋城一中2019中考录取分数线是多少?多少分能上一中?
- 每日信息:安装必备软件——SharePointServer2010版本
- 乐果q9怎么样?有哪些优势?
- 每日热门:全球首创!区块链技术第一次用于塞拉利昂总统选举
- 犯非法采矿罪!紫金矿业被判没收违法所得4.6亿元 并处罚金1500万元
- 今亮点!Pythonbridge:10个Python开源项目
- 删除数据的方法:GridView1_RowingEdit
- 三文鱼被洗白后 病毒究竟如何进入北京新发地?
- 【时快讯】QQ空间怎么设置签名?QQ空间的签名档在哪里设置?
- 刘信达痛批白敬亭:你怎么连嘴都不会亲?
- 世界头条:关于椁的读音你知道多少?椁字详情介绍
- 【环球速看料】外贸必备!常用搜索引擎查询命令汇总