品茗论道说广播(Broadcast内部机制讲解)(下)
下面我们来看,递送广播动作中最重要的processNextBroadcast()。
3.2 最重要的processNextBroadcast()
从processNextBroadcast()的代码,我们就可以看清楚前面说的“平行广播”、“有序广播”和“动态receiver”、“静态receiver”之间的关系了。
我们在前文已经说过,所有的静态receiver都是串行处理的,而动态receiver则会按照发广播时指定的方式,进行“并行”或“串行”处理。能够并行处理的广播,其对应的若干receiver一定都已经存在了,不会牵扯到启动新进程的操作,所以可以在一个while循环中,一次性全部deliver。而有序广播,则需要一个一个地处理,其滚动处理的手段是发送事件,也就是说,在一个receiver处理完毕后,会利用广播队列(BroadcastQueue)的mHandler,发送一个BROADCAST_INTENT_MSG事件,从而执行下一次的processNextBroadcast()。
processNextBroadcast()的代码逻辑大体是这样的:先尝试处理BroadcastQueue中的“平行广播”部分。这需要遍历并行列表(mParallelBroadcasts)的每一个BroadcastRecord以及其中的receivers列表。对于平行广播而言,receivers列表中的每个子节点是个BroadcastFilter。我们直接将广播递送出去即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
while (mParallelBroadcasts.size() > 0 ) { r = mParallelBroadcasts.remove( 0 ); r.dispatchTime = SystemClock.uptimeMillis(); r.dispatchClockTime = System.currentTimeMillis(); final int N = r.receivers.size(); . . . . . . for ( int i= 0 ; i<N; i++) { Object target = r.receivers.get(i); . . . . . . deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false ); } . . . . . . } |
3.2.1 用deliverToRegisteredReceiverLocked()递送到平行动态receiver
deliverToRegisteredReceiverLocked()的代码截选如下:
【frameworks/base/services/java/com/android/server/am/BroadcastQueue.java】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
private final void deliverToRegisteredReceiverLocked(BroadcastRecord r, BroadcastFilter filter, boolean ordered) { . . . . . . . . . . . . if (!skip) { if (ordered) { r.receiver = filter.receiverList.receiver.asBinder(); r.curFilter = filter; filter.receiverList.curBroadcast = r; r.state = BroadcastRecord.CALL_IN_RECEIVE; if (filter.receiverList.app != null ) { r.curApp = filter.receiverList.app; filter.receiverList.app.curReceiver = r; mService.updateOomAdjLocked(); } } . . . . . . performReceiveLocked(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, r.ordered, r.initialSticky); if (ordered) { r.state = BroadcastRecord.CALL_DONE_RECEIVE; } . . . . . . } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
private 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 { receiver.performReceive(intent, resultCode, data, extras, ordered, sticky); } } |
终于通过app.thread向用户进程传递语义了。注意scheduleRegisteredReceiver()的receiver参数,它对应的就是前文所说的ReceiverDispatcher的Binder实体——InnerReceiver了。
总之,当语义传递到用户进程的ApplicationThread以后,走到:
1
2
3
4
5
6
7
8
9
|
// 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); } |
终于走到ReceiverDispatcher的InnerReceiver了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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); } . . . . . . } } . . . . . . public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) { . . . . . . Args args = new Args(intent, resultCode, data, extras, ordered, sticky); if (!mActivityThread.post(args)) // 请注意这一句! { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing sync broadcast to " + mReceiver); args.sendFinished(mgr); } } } } |
请注意mActivityThread.post(args)一句,这样,事件泵最终会回调Args参数的run()成员函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
final class Args extends BroadcastReceiver.PendingResult implements Runnable { . . . . . . . . . . . . public void run() { final BroadcastReceiver receiver = mReceiver; . . . . . . try { ClassLoader cl = mReceiver.getClass().getClassLoader(); intent.setExtrasClassLoader(cl); setExtrasClassLoader(cl); receiver.setPendingResult( this ); receiver.onReceive(mContext, intent); // 回调具体receiver的onReceive() } catch (Exception e) { . . . . . . } if (receiver.getPendingResult() != null ) { finish(); } . . . . . . } } |
其中的那句receiver.onReceive(this),正是回调我们具体receiver的onReceive()成员函数的地方。噢,终于看到应用程序员熟悉的onReceive()了。这部分的示意图如下:
3.2.2 静态receiver的递送
说完动态递送,我们再来看静态递送。对于静态receiver,情况会复杂很多,因为静态receiver所从属的进程有可能还没有运行起来呢。此时BroadcastRecord节点中记录的子列表的节点是ResolveInfo对象。
1
2
3
4
|
ResolveInfo info = (ResolveInfo)nextReceiver; . . . . . . r.state = BroadcastRecord.APP_RECEIVE; String targetProcess = info.activityInfo.processName; |
那么我们要先找到receiver所从属的进程的进程名。
processNextBroadcast()中启动进程的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
ProcessRecord app = mService.getProcessRecordLocked(targetProcess, info.activityInfo.applicationInfo.uid); . . . . . . if (app != null && app.thread != null ) { . . . . . . app.addPackage(info.activityInfo.packageName); processCurBroadcastLocked(r, app); return ; . . . . . . } r.curApp = mService.startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true , r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, "broadcast" , r.curComponent, (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0 , false ) . . . . . . mPendingBroadcast = r; mPendingBroadcastRecvIndex = recIdx; |
如果目标进程已经存在了,那么app.thread肯定不为null,直接调用processCurBroadcastLocked()即可,否则就需要启动新进程了。启动的过程是异步的,可能很耗时,所以要把BroadcastRecord节点记入mPendingBroadcast。
3.2.2.1 processCurBroadcastLocked()
我们先说processCurBroadcastLocked()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
private final void processCurBroadcastLocked(BroadcastRecord r, ProcessRecord app) throws RemoteException { . . . . . . r.receiver = app.thread.asBinder(); r.curApp = app; app.curReceiver = r; . . . . . . . . . . . . app.thread.scheduleReceiver( new Intent(r.intent), r.curReceiver, mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo), r.resultCode, r.resultData, r.resultExtras, r.ordered); . . . . . . started = true ; . . . . . . } |
其中最重要的是调用app.thread.scheduleReceiver()的那句。在IApplicationThread接口中,是这样定义scheduleReceiver()函数原型的:
1
2
3
4
5
|
void scheduleReceiver(Intent intent, ActivityInfo info, CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras, boolean sync) throws RemoteException; |
其中ActivityInfo info参数,记录着目标receiver的信息。可以看到,递送静态receiver时,是不会用到RecevierDispatcher的。
用户进程里handleMessage()
1
2
3
4
5
6
|
case RECEIVER: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp" ); handleReceiver((ReceiverData)msg.obj); maybeSnapshot(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break ; |
ActivityThread中,会运用反射机制,创建出BroadcastReceiver对象,而后回调该对象的onReceive()成员函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
private void handleReceiver(ReceiverData data) { . . . . . . IActivityManager mgr = ActivityManagerNative.getDefault(); BroadcastReceiver receiver; try { java.lang.ClassLoader cl = packageInfo.getClassLoader(); data.intent.setExtrasClassLoader(cl); data.setExtrasClassLoader(cl); receiver = (BroadcastReceiver)cl.loadClass(component).newInstance(); } catch (Exception e) { . . . . . . } try { . . . . . . receiver.setPendingResult(data); receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); } catch (Exception e) { . . . . . . } finally { sCurrentBroadcastIntent.set( null ); } if (receiver.getPendingResult() != null ) { data.finish(); } } |
3.2.2.2 必要时启动新进程
现在我们回过头来看,在目标进程尚未启动的情况下,是如何完成递送的。刚刚我们已经看到调用startProcessLocked()的句子了,只要不出问题,目标进程成功启动后就会调用AMS的attachApplication()。
有关attachApplication()的详情,请参考其他关于AMS的文档,此处我们只需知道它里面又会调用attachApplicationLocked()函数。
1
|
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) |
attachApplicationLocked()内有这么几句:
1
2
3
4
5
6
7
8
9
|
// Check if a next-broadcast receiver is in this process... if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { didSomething = sendPendingBroadcastsLocked(app); } catch (Exception e) { // If the app died trying to launch the receiver we declare it 'bad' badApp = true ; } } |
它们的意思是,如果新启动的进程就是刚刚mPendingBroadcast所记录的进程的话,此时AMS就会执行sendPendingBroadcastsLocked(app)一句。
1
2
3
4
5
6
7
8
|
// The app just attached; send any pending broadcasts that it should receive boolean sendPendingBroadcastsLocked(ProcessRecord app) { boolean didSomething = false ; for (BroadcastQueue queue : mBroadcastQueues) { didSomething |= queue.sendPendingBroadcastsLocked(app); } return didSomething; } |
BroadcastQueue的sendPendingBroadcastsLocked()函数如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public boolean sendPendingBroadcastsLocked(ProcessRecord app) { boolean didSomething = false ; final BroadcastRecord br = mPendingBroadcast; if (br != null && br.curApp.pid == app.pid) { try { mPendingBroadcast = null ; processCurBroadcastLocked(br, app); didSomething = true ; } catch (Exception e) { . . . . . . } } return didSomething; } |
可以看到,既然目标进程已经成功启动了,那么mPendingBroadcast就可以赋值为null了。接着,sendPendingBroadcastsLocked()会调用前文刚刚阐述的processCurBroadcastLocked(),其内再通过app.thread.scheduleReceiver(),将语义发送到用户进程,完成真正的广播递送。这部分在上一小节已有阐述,这里就不多说了。
3.2.3 说说有序广播是如何循环起来的?
我们知道,平行广播的循环很简单,只是在一个while循环里对每个动态receiver执行deliverToRegisteredReceiverLocked()即可。而对有序广播来说,原则上每次processNextBroadcast()只会处理一个BroadcastRecord的一个receiver而已。当然,此时摘下的receiver既有可能是动态注册的,也有可能是静态的。
对于动态注册的receiver,目标进程处理完广播之后,会间接调用am.finishReceiver()向AMS发出反馈,关于这一步,其实在前面罗列ReceiverDispatcher的performReceive()时已经出现过了,我们再列一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) { . . . . . . Args args = new Args(intent, resultCode, data, extras, ordered, sticky); if (!mActivityThread.post(args)) { if (mRegistered && ordered) { IActivityManager mgr = ActivityManagerNative.getDefault(); . . . . . . args.sendFinished(mgr); // 请注意这一句! } } } |
Args继承于BroadcastReceiver.PendingResult,它调用的sendFinished()就是PendingResult的sendFinished():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public void sendFinished(IActivityManager am) { synchronized ( this ) { if (mFinished) { throw new IllegalStateException( "Broadcast already finished" ); } mFinished = true ; try { if (mResultExtras != null ) { mResultExtras.setAllowFds( false ); } if (mOrderedHint) { am.finishReceiver(mToken, mResultCode, mResultData, mResultExtras, mAbortBroadcast); } else { // This broadcast was sent to a component; it is not ordered, // but we still need to tell the activity manager we are done. am.finishReceiver(mToken, 0 , null , null , false ); } } catch (RemoteException ex) { } } } |
代码中的am.finishReceiver()会通知AMS,表示用户侧receiver已经处理好了,或者至少告一段落了,请AMS进行下一步动作。
而对于动态注册的receiver,情况是类似的,最终也是调用am.finishReceiver()向AMS发出回馈的,只不过发起的动作是在ActivityThread的handleReceiver()动作中。前文已经列过这个函数了,大家注意下面的句子即可:
1
2
3
4
5
6
7
8
9
10
11
|
private void handleReceiver(ReceiverData data) { . . . . . . receiver.setPendingResult(data); receiver.onReceive(context.getReceiverRestrictedContext(), data.intent); . . . . . . if (receiver.getPendingResult() != null ) { data.finish(); } } |
ReceiverData也是继承于BroadcastReceiver.PendingResult的,它调用的finish()是PendingResult的finish():
1
2
3
4
5
6
7
8
9
10
11
|
public final void finish() { if (mType == TYPE_COMPONENT) { . . . . . . } else if (mOrderedHint && mType != TYPE_UNREGISTERED) { if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, "Finishing broadcast to " + mToken); final IActivityManager mgr = ActivityManagerNative.getDefault(); sendFinished(mgr); } } |
此处的sendFinished()内部最终也会调用到am.finishReceiver(),向AMS通告receiver已经处理好了。
AMS侧在收到finishReceiver语义后,执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public void finishReceiver(IBinder who, int resultCode, String resultData, Bundle resultExtras, boolean resultAbort) { . . . . . . try { boolean doNext = false ; BroadcastRecord r = null ; synchronized ( this ) { r = broadcastRecordForReceiverLocked(who); if (r != null ) { doNext = r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, true ); } } if (doNext) { r.queue.processNextBroadcast( false ); } trimApplications(); } finally { Binder.restoreCallingIdentity(origId); } } |
可以看到,如有必要,会继续调用processNextBroadcast(),从而完成有序广播的循环处理。
3.2.4 说说有序广播的timeout处理
因为AMS很难知道一次广播究竟能不能完全成功递送出去,所以它必须实现一种“时限机制”。前文在阐述broadcastIntentLocked()时,提到过new一个BroadcastRecord节点,并插入一个BroadcastQueue里的“平行列表”或者“有序列表”。不过当时我们没有太细说那个BroadcastQueue,现在我们多加一点儿说明。
实际上系统中有两个BroadcastQueue,一个叫做“前台广播队列”,另一个叫“后台广播队列”,在AMS里是这样定义的:
1
2
|
BroadcastQueue mFgBroadcastQueue; BroadcastQueue mBgBroadcastQueue; |
为什么要搞出两个队列呢?我认为这是因为系统对“广播时限”的要求不同导致的。对于前台广播队列而言,它里面的每个广播必须在10秒之内把广播递送给receiver,而后台广播队列的时限比较宽,只需60秒之内递送到就可以了。具体时限值请看BroadcastQueue的mTimeoutPeriod域。注意,这个10秒或60秒限制是针对一个receiver而言的。比方说“前台广播队列”的某个BroadcastRecord节点对应了3个receiver,那么在处理这个广播节点时,只要能在30秒(3 x 10)之内搞定就可以了。事实上,AMS系统考虑了更多东西,所以它给一个BroadcastRecord的总时限是其所有receiver时限之和的2倍,在此例中就是60秒(2 x 3 x 10)。
对于平行receiver而言,时限的作用小一点儿,因为动态receiver是直接递送到目标进程的,它不考虑目标端是什么时候处理完这个广播的。
然而对于有序receiver来说,时限就比较重要了。因为receiver之间必须是串行处理的,也就是说上一个receiver在没处理完时,系统是不会让下一个receiver进行处理的。从processNextBroadcast()的代码来看,在处理有序receiver时,BroadcastRecord里的nextReceiver域会记录“下一个应该处理的receiver”的标号。只有在BroadcastRecord的所有receiver都处理完后,或者BroadcastRecord的处理时间超过了总时限的情况下,系统才会把这个BroadcastRecord节点从队列里删除。因此我们在processNextBroadcast()里看到的获取当前BroadcastRecord的句子是写死为r = mOrderedBroadcasts.get(0)的。
在拿到当前BroadcastRecord之后,利用nextReceiver值拿到当前该处理的receiver信息:
1
2
3
|
int recIdx = r.nextReceiver++; . . . . . . Object nextReceiver = r.receivers.get(recIdx); |
当然,一开始,nextReceiver的值只会是0,表示第一个receiver有待处理,此时会给BroadcastRecord的dispatchTime域赋值。
1
2
3
4
5
6
|
int recIdx = r.nextReceiver++; r.receiverTime = SystemClock.uptimeMillis(); if (recIdx == 0 ) { r.dispatchTime = r.receiverTime; r.dispatchClockTime = System.currentTimeMillis(); . . . . . . } |
也就是说,dispatchTime的意义是标记实际处理BroadcastRecord的起始时间,那么这个BroadcastRecord所能允许的最大时限值就是:
dispatchTime + 2 * mTimeoutPeriod * 其receiver总数
一旦超过这个时限,而BroadcastRecord又没有处理完,那么就强制结束这个BroadcastRecord节点:
1
2
3
4
5
6
7
8
|
if ((numReceivers > 0 ) && (now > r.dispatchTime + ( 2 *mTimeoutPeriod*numReceivers))) { . . . . . . broadcastTimeoutLocked( false ); // forcibly finish this broadcast forceReceive = true ; r.state = BroadcastRecord.IDLE; } |
此处调用的broadcastTimeoutLocked()的参数是boolean fromMsg,表示这个函数是否是在处理“时限消息”的地方调用的,因为当前是在processNextBroadcast()函数里调用broadcastTimeoutLocked()的,所以这个参数为false。从这个参数也可以看出,另一处判断“处理已经超时”的地方是在消息处理机制里,在那个地方,fromMsg参数应该设为true。
大体上说,每当processNextBroadcast()准备递送receiver时,会调用setBroadcastTimeoutLocked()设置一个延迟消息:
1
2
3
|
long timeoutTime = r.receiverTime + mTimeoutPeriod; . . . . . . setBroadcastTimeoutLocked(timeoutTime); |
setBroadcastTimeoutLocked()的代码如下:
1
2
3
4
5
6
7
8
|
final void setBroadcastTimeoutLocked( long timeoutTime) { if (! mPendingBroadcastTimeoutMessage) { Message msg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG, this ); mHandler.sendMessageAtTime(msg, timeoutTime); mPendingBroadcastTimeoutMessage = true ; } } |
只要我们的receiver能及时处理广播,系统就会cancel上面的延迟消息。这也就是说,但凡事件泵的handleMessage()开始处理这个消息,就说明receiver处理超时了。此时,系统会放弃处理这个receiver,并接着尝试处理下一个receiver。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
final Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { . . . . . . case BROADCAST_TIMEOUT_MSG: { synchronized (mService) { broadcastTimeoutLocked( true ); } } break ; } } }; |
broadcastTimeoutLocked()的代码截选如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
final void broadcastTimeoutLocked( boolean fromMsg) { if (fromMsg) { mPendingBroadcastTimeoutMessage = false ; } if (mOrderedBroadcasts.size() == 0 ) { return ; } long now = SystemClock.uptimeMillis(); BroadcastRecord r = mOrderedBroadcasts.get( 0 ); . . . . . . . . . . . . finishReceiverLocked(r, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, true ); scheduleBroadcastsLocked(); . . . . . . } |
可以看到,当一个receiver超时后,系统会放弃继续处理它,并再次调用scheduleBroadcastsLocked(),尝试处理下一个receiver。
4 尾声
有关Android的广播机制,我们就先说这么多吧。品一杯红茶,说一段代码,管他云山雾罩,任那琐碎冗长,我自冷眼看安卓,瞧他修短随化。
转自http://blog.csdn.net/codefly/article/details/42323235
品茗论道说广播(Broadcast内部机制讲解)(下)的更多相关文章
- 品茗论道说广播(Broadcast内部机制讲解)(上)
1 概述 我们在编写Android程序时,常常会用到广播(Broadcast)机制.从易用性的角度来说,使用广播是非常简单的.不过,这个不是本文关心的重点,我们希望探索得再深入一点儿.我想,许多人也不 ...
- [源码分析] 从实例和源码入手看 Flink 之广播 Broadcast
[源码分析] 从实例和源码入手看 Flink 之广播 Broadcast 0x00 摘要 本文将通过源码分析和实例讲解,带领大家熟悉Flink的广播变量机制. 0x01 业务需求 1. 场景需求 对黑 ...
- zookeeper 内部机制学习
zookeeper 内部机制学习 1. zk的设计目标 最终一致性:client不论连接到那个Server,展示给它的都是同一个视图. 可靠性:具有简单.健壮.良好的性能.如果消息m被到一台服务器接收 ...
- Android中的广播Broadcast详解
今天来看一下Android中的广播机制,我们知道广播Broadcast是Android中的四大组件之一,可见他的重要性了,当然它的用途也很大的,比如一些系统的广播:电量低.开机.锁屏等一些操作都会发送 ...
- 万字综述,核心开发者全面解读PyTorch内部机制
斯坦福大学博士生与 Facebook 人工智能研究所研究工程师 Edward Z. Yang 是 PyTorch 开源项目的核心开发者之一.他在 5 月 14 日的 PyTorch 纽约聚会上做了一个 ...
- [Spark內核] 第42课:Spark Broadcast内幕解密:Broadcast运行机制彻底解密、Broadcast源码解析、Broadcast最佳实践
本课主题 Broadcast 运行原理图 Broadcast 源码解析 Broadcast 运行原理图 Broadcast 就是将数据从一个节点发送到其他的节点上; 例如 Driver 上有一张表,而 ...
- NumPy 广播(Broadcast)
NumPy 广播(Broadcast) 广播(Broadcast)是 numpy 对不同形状(shape)的数组进行数值计算的方式, 对数组的算术运算通常在相应的元素上进行. 如果两个数组 a 和 b ...
- 广播 (Broadcast)
广播 :在Android中,Broadcast是一种广泛运用的在应用程序之间传输信息的机制.我们拿广播电台来做个比方.我们平常使用收音机收音是这样的:许许多多不同的广播电台通过特定的频率来发送他们的内 ...
- Numpy | 10 广播(Broadcast)
广播(Broadcast)是 numpy 对不同形状(shape)的数组进行数值计算的方式, 对数组的算术运算通常在相应的元素上进行. 下面的图片展示了数组 b 如何通过广播来与数组 a 兼容. 4x ...
随机推荐
- 学会MySQL索引
原文:https://mp.weixin.qq.com/s/UzWxJ_pVPjU5ip0Z-Y9TdA 什么是索引? 百度百科是这样描述的: 索引是为来加速对表中数据行中的检索而创建的一种分散的数据 ...
- nodejs第一个练习:用Node.js建HTTP服务器
这是官方的一个例子, 在F盘建立一个tinyphp.js文件,内容: var http = require('http'); http.createServer(function (req, res) ...
- Struct2_定义拦截器并使用注解方式作用在Action的方法中
一.目的:通过在方法上加注解控制哪些方法需要登陆后才能访问 二.方式:利用拦截器判断用户是否登陆 三.实现步骤 定义配置文件struts.xml添加节点 1 2 3 4 5 6 7 8 9 1 ...
- iOS 捕获程序崩溃日志
iOS开发中遇到程序崩溃是很正常的事情,如何在程序崩溃时捕获到异常信息并通知开发者? 下面就介绍如何在iOS中实现: 1. 在程序启动时加上一个异常捕获监听,用来处理程序崩溃时的回调动作 NSSetU ...
- docker部署golang+redis聊天室
博客地址:http://www.niu12.com/article/7#####1.项目源码: https://github.com/ZQCard/webchat#####2.项目构成 websock ...
- ylbtech-LanguageSamples-Nullable(可以为 null 的类型)
ylbtech-Microsoft-CSharpSamples:ylbtech-LanguageSamples-Nullable(可以为 null 的类型) 1.A,示例(Sample) 返回顶部 “ ...
- find . -mtime +1 查找文件
N * 24+1 内表示 1 * 24 +24小时以外..+0 才表示 0 * 24 +24小时以外1 表示 1*24 + 24 到 24 之间..0 表示 0*24 + 24 到 0 之间..-1 ...
- 机器学习-Confusion Matrix混淆矩阵、ROC、AUC
本文整理了关于机器学习分类问题的评价指标——Confusion Matrix.ROC.AUC的概念以及理解. 混淆矩阵 在机器学习领域中,混淆矩阵(confusion matrix)是一种评价分类模型 ...
- Java笔记16:多线程共享数据
一.Thread实现 public class ThreadDemo4 { publicstaticvoid main(String[] args) { new ThreadTest4().start ...
- Redis中为什么使用跳表---------转自http://blog.csdn.net/u010412301/article/details/64923131
最近在研究数据库的一些底层实现,百度的面试官问到了跳表,当时没有回答上来,在csdn上看到了这篇文章,感觉写的比较好,希望大家可以多多交流. Redis里面使用skiplist是为了实现sorted ...