【Broadcast】sendBroadcast流程(二)

2024-06-06 19:18

本文主要是介绍【Broadcast】sendBroadcast流程(二),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上节:sendBroadcat流程(一)中分析了AMS.broadcastIntentLocked中的处理。

接下来分析广播的派发过程,即分析BroadcastQueue.scheduleBroadcastsLocked函数。

public void scheduleBroadcastsLocked() {if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Schedule broadcasts ["+ mQueueName + "]: current="+ mBroadcastsScheduled);if (mBroadcastsScheduled) {return;}mHandler.sendMessage(mHandler.obtainMessage(BROADCAST_INTENT_MSG, this));mBroadcastsScheduled = true;
}
private final class BroadcastHandler extends Handler {public BroadcastHandler(Looper looper) {super(looper, null, true);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case BROADCAST_INTENT_MSG: {processNextBroadcast(true);} break;case BROADCAST_TIMEOUT_MSG: {synchronized (mService) {broadcastTimeoutLocked(true);}} break;}}
}

接下来看processNextBroadcast函数

第一阶段,处理非串行广播:

final void processNextBroadcast(boolean fromMsg) {synchronized(mService) {BroadcastRecord r;mService.updateCpuStats();if (fromMsg) {mBroadcastsScheduled = false;}// First, deliver any non-serialized broadcasts right away.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);【1.1】deliverToRegisteredReceiverLocked(r, (BroadcastFilter)target, false, i);}addBroadcastToHistoryLocked(r);}

【1.1】deliverToRegisteredReceiverLocked

private final void deliverToRegisteredReceiverLocked(BroadcastRecord r,BroadcastFilter filter, booleanordered) {booleanskip = false;//检查发送进程是否有filter要求的权限if(filter.requiredPermission != null) {intperm = checkComponentPermission(filter.requiredPermission,r.callingPid, r.callingUid, -1, true);if(perm != PackageManager.PERMISSION_GRANTED) skip = true;}//检查接收者是否有发送者要求的权限if(r.requiredPermission != null) {intperm = checkComponentPermission(r.requiredPermission,filter.receiverList.pid, filter.receiverList.uid, -1, true);if(perm != PackageManager.PERMISSION_GRANTED) skip = true;}if(!skip) {if(ordered) {......//设置一些状态,成员变量等信息,不涉及广播发送}try {//发送广播performReceiveLocked(filter.receiverList.app,   filter.receiverList.receiver,new Intent(r.intent), r.resultCode,r.resultData, r.resultExtras, r.ordered, r.initialSticky);if(ordered) r.state = BroadcastRecord.CALL_DONE_RECEIVE;}......}}}

再看performReceiveLocked函数

void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver,Intent intent, int resultCode, String data, Bundle extras,boolean ordered, boolean sticky, int sendingUser) throws RemoteException {// Send the intent to the receiver asynchronously using one-way binder calls.if (app != null) {if (app.thread != null) {//如果app及app.thread不为null,则调度scheduleRegisteredReceiver// If we have an app thread, do the call through that so it is// correctly ordered with other one-way calls.try {//scheduleRegisteredReceiver仅针对动态receiverapp.thread.scheduleRegisteredReceiver(receiver, intent, resultCode,data, extras, ordered, sticky, sendingUser, app.repProcState);// TODO: Uncomment this when (b/28322359) is fixed and we aren't getting// DeadObjectException when the process isn't actually dead.//} catch (DeadObjectException ex) {// Failed to call into the process.  It's dying so just let it die and /            //move on.//    throw ex;} catch (RemoteException ex) {// Failed to call into the process. It's either dying or wedged. Kill it             //gently.synchronized (mService) {Slog.w(TAG, "Can't deliver broadcast to " + app.processName+ " (pid " + app.pid + "). Crashing it.");app.scheduleCrash("can't deliver broadcast");}throw ex;}} else {// Application has died. Receiver doesn't exist.throw new RemoteException("app.thread must not be null");}} else {//否则调用IIntentReceiver的performReceive函数receiver.performReceive(intent, resultCode, data, extras, ordered,sticky, sendingUser);}
}

对于动态receiver而言,大部分情况下会执行if分支,所以应用进程ApplicationThread的scheduleRegisteredReceiver函数将被调用。

scheduleRegisteredReceiver函数

// 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, int sendingUser, int processState) throws RemoteException {updateProcessState(processState, false);receiver.performReceive(intent, resultCode, dataStr, extras, ordered,sticky, sendingUser);
}

此处的reciever是registerReceiver时传入的,实际类型是LoadedApk.ReceiverDispatcher.InnerReceiver,来看它的performReceive函数:

@Override
public void performReceive(Intent intent, int resultCode, String data,Bundle extras, boolean ordered, boolean sticky, int sendingUser) {final LoadedApk.ReceiverDispatcher rd;if (intent == null) {Log.wtf(TAG, "Null intent received");rd = null;} else {rd = mDispatcher.get();}if (ActivityThread.DEBUG_BROADCAST) {int seq = intent.getIntExtra("seq", -1);}if (rd != null) {rd.performReceive(intent, resultCode, data, extras,ordered, sticky, sendingUser);} else {// The activity manager dispatched a broadcast to a registered// receiver in this process, but before it could be delivered the// receiver was unregistered.  Acknowledge the broadcast on its// behalf so that the system's broadcast sequence can continue.IActivityManager mgr = ActivityManager.getService();try {if (extras != null) {extras.setAllowFds(false);}mgr.finishReceiver(this, resultCode, data, extras, false, intent.getFlags());} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}
}

又调用了LoadedApk.ReceiverDispatcher的performReceive函数:

public void performReceive(Intent intent, int resultCode, String data,Bundle extras, boolean ordered, boolean sticky, int sendingUser) {final Args args = new Args(intent, resultCode, data, extras, ordered,sticky, sendingUser);if (intent == null) {Log.wtf(TAG, "Null intent received");}else {//log 信息}//mActivityThread是一个Handler,SDK中的两个同名registerReceiver,//没有传递Handler,则使用主线程的Handlerif (intent == null || !mActivityThread.post(args.getRunnable())) {if (mRegistered && ordered) {IActivityManager mgr = ActivityManager.getService();args.sendFinished(mgr);}}
}

scheduleRegisteredReceiver最终向主线程的Handler投递了一个Runnable对象,该Runnable中会在主线程中运行:

public final Runnable getRunnable() {return () -> {final BroadcastReceiver receiver = mReceiver;final boolean ordered = mOrdered;final IActivityManager mgr = ActivityManager.getService();final Intent intent = mCurIntent;if (intent == null) {Log.wtf(TAG, "Null intent being dispatched, mDispatched=" + mDispatched+ ": run() previously called at "+ Log.getStackTraceString(mPreviousRunStacktrace));}mCurIntent = null;mDispatched = true;mPreviousRunStacktrace = new Throwable("Previous stacktrace");if (receiver == null || intent == null || mForgotten) {if (mRegistered && ordered) {sendFinished(mgr);}return;}Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveReg");try {ClassLoader cl = mReceiver.getClass().getClassLoader();intent.setExtrasClassLoader(cl);intent.prepareToEnterProcess();setExtrasClassLoader(cl);receiver.setPendingResult(this);//调用动态receiver的onReceive函数receiver.onReceive(mContext, intent);} catch (Exception e) {if (mRegistered && ordered) {sendFinished(mgr);}if (mInstrumentation == null ||!mInstrumentation.onException(mReceiver, e)) {Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);throw new RuntimeException("Error receiving broadcast " + intent+ " in " + mReceiver, e);}}if (receiver.getPendingResult() != null) {//调用BroadcastReceiver的finish完成工作,通知AMSfinish();}Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);};
}
/**
* Finish the broadcast.  The current result will be sent and the
* next broadcast will proceed.
*/
public final void finish() {if (mType == TYPE_COMPONENT) {final IActivityManager mgr = ActivityManager.getService();if (QueuedWork.hasPendingWork()) {// If this is a broadcast component, we need to make sure any// queued work is complete before telling AM we are done, so// we don't have our process killed before that.  We now know// there is pending work; put another piece of work at the end// of the list to finish the broadcast, so we don't block this// thread (which may be the main thread) to have it finished.//// Note that we don't need to use QueuedWork.addFinisher() with the// runnable, since we know the AM is waiting for us until the// executor gets to it.QueuedWork.queue(new Runnable() {@Override public void run() {sendFinished(mgr);}}, false);} else {sendFinished(mgr);}} else if (mOrderedHint && mType != TYPE_UNREGISTERED) {final IActivityManager mgr = ActivityManager.getService();sendFinished(mgr);}
}
/** @hide */
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, mFlags);} 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, mFlags);}} catch (RemoteException ex) {}}
}

会调用到ActivityManagerService的finishReceiver函数:

public void finishReceiver(IBinder who, int resultCode, String resultData,Bundle resultExtras, boolean resultAbort, int flags) {// Refuse possible leaked file descriptorsif (resultExtras != null && resultExtras.hasFileDescriptors()) {throw new IllegalArgumentException("File descriptors passed in Bundle");}final long origId = Binder.clearCallingIdentity();try {boolean doNext = false;BroadcastRecord r;synchronized(this) {BroadcastQueue queue = (flags & Intent.FLAG_RECEIVER_FOREGROUND) != 0? mFgBroadcastQueue : mBgBroadcastQueue;r = queue.getMatchingOrderedReceiver(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第二阶段,处理串行广播:

final void processNextBroadcast(boolean fromMsg) {synchronized(mService) {BroadcastRecord r;...//第一阶段处理串行广播//第二阶段// Now take care of the next serialized one...// If we are waiting for a process to come up to handle the next// broadcast, then do nothing at this point.  Just in case, we// check that the process we're waiting for still exists./*接下来处理mOrderedBroadcasts中的成员,如果接收者所在的进程还未启动,则需等待。mPendingBrodcast用于标识因为应用进程还未启动而处于等待状态的BrodcastRecord。*/if (mPendingBroadcast != null) {boolean isDead;synchronized (mService.mPidsSelfLocked) {ProcessRecord proc =         mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid);isDead = proc == null || proc.crashing;}/*重要说明判断要等待的进程是否为dead进程,如果没有dead进程,则继续等待。仔细思考,此处直接返回会有什么问题。问题不小!假设有两个ordered广播A和B,有两个接收者,AR和BR,并且BR所在进程已经启动并完成初始化Android运行环境。如果processNextBroadcast先处理A,再处理B,那么此处B的处理将因为mPendingBroadcast不为空而被延后。虽然B和A之间没有任何关系(例如完全是两个不同的广播消息),但是事实上,大多数开发人员理解的order是针对单个广播的,例如A有5个接收者,那么对这5个接收者的广播的处理是串行的。通过此处的代码发现,系统竟然串行处理A和B广播,即B广播要待到A的5个接收者都处理完了才能处理。*/if (!isDead) {// It's still alive, so keep waitingreturn;} else {mPendingBroadcast.state = BroadcastRecord.IDLE;mPendingBroadcast.nextReceiver = mPendingBroadcastRecvIndex;mPendingBroadcast = null;}}boolean looped = false;do {//mOrderedBroadcasts处理完毕if (mOrderedBroadcasts.size() == 0) {// No more broadcasts pending, so all done!mService.scheduleAppGcsLocked();if (looped) {// If we had finished the last ordered broadcast, then// make sure all processes have correct oom and sched// adjustments.mService.updateOomAdjLocked();}return;}r = mOrderedBroadcasts.get(0);boolean forceReceive = false;//判断此条广播是否处理时间过长//先得到该条广播的所有接收者int numReceivers = (r.receivers != null) ? r.receivers.size() : 0;if (mService.mProcessesReady && r.dispatchTime > 0) {long now = SystemClock.uptimeMillis();///如果总耗时超过2倍的接收者个数*每个接收者最长处理时间(10秒),则//强制结束这条广播的处理if ((numReceivers > 0) &&(now > r.dispatchTime + (2*mTimeoutPeriod*numReceivers))) {broadcastTimeoutLocked(false); // forcibly finish this broadcastforceReceive = true;r.state = BroadcastRecord.IDLE;}}if (r.state != BroadcastRecord.IDLE) {return;}//如果下面这个if条件满足,则表示该条广播要么已经全部被处理,要么被中途取消if (r.receivers == null || r.nextReceiver >= numReceivers|| r.resultAbort || forceReceive) { //没有接收此广播的receiver了if (r.resultTo != null) {try {//将该广播的处理结果传给设置了resultTo的接收者performReceiveLocked(r.callerApp, r.resultTo,new Intent(r.intent), r.resutCode,r.resultData, r.resultExtras, false, false, r.userId);catch (RemoteException e) {r.resultTo = null;}}cancelBroadcastTimeoutLocked();addBroadcastToHistoryLocked(r);if (r.intent.getComponent() == null && r.intent.getPackage() == null&& (r.intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) == 0) {// This was an implicit broadcast... let's record it for posterity.mService.addBroadcastStatLocked(r.intent.getAction(), r.callerPackage,r.manifestCount, r.manifestSkipCount, r.finishTime-r.dispatchTime);}mOrderedBroadcasts.remove(0);r = null;looped = true;continue;} while (r == null);....} //end of synchronized(mService)
}       

第二阶段的工作总结:

  1. 首先根据是否处于pending状态进行相关操作
  2. 处理超时的广播记录。超时时间是2*BROADCAST_TIMEOUT*numReceivers,BROADCAST_TIMEOUT默认为10秒。由于涉及到创建进程,初始化Android运行环境等操作,所以此处的超时时间还乘以一个固定倍数2。

第三阶段

        // Get the next receiver...int recIdx = r.nextReceiver++;// Keep track of when this receiver started, and make sure there// is a timeout message pending to kill it if need be.r.receiverTime = SystemClock.uptimeMillis();if (recIdx == 0) {r.dispatchTime = r.receiverTime;//记录本广播第一次处理开始的时间r.dispatchClockTime = System.currentTimeMillis();}//设置广播处理超时时间,BROADCAST_TIMEOUT为10秒if (!mPendingBroadcastTimeoutMessage) {long timeoutTime = r.receiverTime + mTimeoutPeriod;setBroadcastTimeoutLocked(timeoutTime);}final BroadcastOptions brOptions = r.options;//取该条广播的下一个接收者final Object nextReceiver = r.receivers.get(recIdx);if (nextReceiver instanceof BroadcastFilter) {// Simple case: this is a registered receiver who gets// a direct call.//如果是动态接收者,则直接调用deliverToRegisteredReceiverLocked处理BroadcastFilter filter = (BroadcastFilter)nextReceiver;deliverToRegisteredReceiverLocked(r, filter, r.ordered, recIdx);if (r.receiver == null || !r.ordered) {// The receiver has already finished, so schedule to// process the next one.r.state = BroadcastRecord.IDLE;scheduleBroadcastsLocked();} else {if (brOptions!=null&&brOptions.getTemporaryAppWhitelistDuration()> 0) {scheduleTempWhitelistLocked(filter.owningUid,brOptions.getTemporaryAppWhitelistDuration(), r);}}return;//已经通知一个接收者去处理该广播,需要等它的处理结果,所以此处直接返回}// Hard case: need to instantiate the receiver, possibly// starting its application process to host it.ResolveInfo info =(ResolveInfo)nextReceiver;ComponentName component = new ComponentName(info.activityInfo.applicationInfo.packageName,info.activityInfo.name);......//检测是否为skip的packageString targetProcess = info.activityInfo.processName;ProcessRecord app = mService.getProcessRecordLocked(targetProcess,info.activityInfo.applicationInfo.uid, false);.....r.delivery[recIdx] = BroadcastRecord.DELIVERY_DELIVERED;r.state = BroadcastRecord.APP_RECEIVE;r.curComponent = component;r.curReceiver = info.activityInfo;.....//正在处理广播,package不能被stop// Is this receiver's application already running?//如果接收者所在的进程已经启动if (app != null && app.thread != null && !app.killed) {try {app.addPackage(info.activityInfo.packageName,                                 info.activityInfo.applicationInfo.versionCode, mService.mProcessStats);processCurBroadcastLocked(r, app);return;//已经触发接收者处理本广播,需要等待处理结果} catch (RemoteException e) {} catch (RuntimeException e) {                    Slog.wtf(TAG, "Failed sending broadcast to "+ r.curComponent + " with " + r.intent, e);// If some unexpected exception happened, just skip// this broadcast.  At this point we are not in the call// from a client, so throwing an exception out from here// will crash the entire system instead of just whoever// sent the broadcast.logBroadcastReceiverDiscardLocked(r);finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);scheduleBroadcastsLocked();// We need to reset the state if we failed to start the receiver.r.state = BroadcastRecord.IDLE;return;}// If a dead object exception was thrown -- fall through to// restart the application.}// Not running -- get it started, to be executed when the app comes up.//如果进程没启动,则要先启动进程if ((r.curApp=mService.startProcessLocked(targetProcess,info.activityInfo.applicationInfo, true,r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,"broadcast", r.curComponent,(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0,         false, false))== null) {//进程启动失败的处理// Ah, this recipient is unavailable.  Finish it if necessary,// and mark the broadcast record as ready for the next.Slog.w(TAG, "Unable to launch app "+ info.activityInfo.applicationInfo.packageName + "/"+ info.activityInfo.applicationInfo.uid + " for broadcast "+ r.intent + ": process is bad");logBroadcastReceiverDiscardLocked(r);finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);scheduleBroadcastsLocked();r.state = BroadcastRecord.IDLE;return;}mPendingBroadcast = r; //设置mPendingBroadcastmPendingBroadcastRecvIndex = recIdx;

第三阶段的处理总结:

  1. 如果广播接收者为动态注册对象,则直接调用deliverToRegisteredReceiverLocked处理它;
  2. 如果广播接收者为静态注册对象,并且该对象对应的进程已经存在,则调用processCurBroadcastLocked处理它;
  3. 如果广播接收者为静态注册对象,并且该对象对应的进程还不存在,则需要创建该进程,这是最糟糕的情况。

sendBroadcast流程图:

这篇关于【Broadcast】sendBroadcast流程(二)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1036998

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除

火语言RPA流程组件介绍--浏览网页

🚩【组件功能】:浏览器打开指定网址或本地html文件 配置预览 配置说明 网址URL 支持T或# 默认FLOW输入项 输入需要打开的网址URL 超时时间 支持T或# 打开网页超时时间 执行后后等待时间(ms) 支持T或# 当前组件执行完成后继续等待的时间 UserAgent 支持T或# User Agent中文名为用户代理,简称 UA,它是一个特殊字符串头,使得服务器

UMI复现代码运行逻辑全流程(一)——eval_real.py(尚在更新)

一、文件夹功能解析 全文件夹如下 其中,核心文件作用为: diffusion_policy:扩散策略核心文件夹,包含了众多模型及基础库 example:标定及配置文件 scripts/scripts_real:测试脚本文件,区别在于前者倾向于单体运行,后者为整体运行 scripts_slam_pipeline:orb_slam3运行全部文件 umi:核心交互文件夹,作用在于构建真

C++/《C/C++程序编译流程》

程序的基本流程如图:   1.预处理        预处理相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。 读取C/C++源程序,对其中的伪指令(以#开头的指令)进行处理将所有的“#define”删除,并且展开所有的宏定义处理所有的条件编译指令,如:“#if”、“

笔记本电脑的具体选购步骤流程

2.1 笔记本电脑的具体选购步骤流程   关于笔记本电脑的选购,一直是热点话题。不管是新手还是老前辈,选购前,总是要先上网查一查,汇总一些信息或经验。因为选购一台笔记本电脑,从它的配置、外观到做工等很多方面都需要考量,所以挑一台自己喜欢的、适合自己的笔记本电脑也是一件很费脑筋的事情。本节将一些选购笔记本电脑的经验进行了总结,供广大读者选购笔记本电脑时参考。   笔记本电脑选购流程如下

基于微信小程序与嵌入式系统的智能小车开发(详细流程)

一、项目概述 本项目旨在开发一款智能小车,结合微信小程序与嵌入式系统,提供实时图像处理与控制功能。用户可以通过微信小程序远程操控小车,并实时接收摄像头采集的图像。该项目解决了传统遥控小车在图像反馈和控制延迟方面的问题,提升了小车的智能化水平,适用于教育、科研和娱乐等多个领域。 二、系统架构 1. 系统架构设计 本项目的系统架构主要分为以下几个部分: 微信小程序:负责用户界面、控制指令的

MySQL B+树查询数据全流程

MySQL B+树查询数据全流程 一、引言 在 MySQL 数据库中,B+树是一种常用的数据结构,用于高效地存储和查询数据。了解 B+树中查询数据的全过程,对于深入理解 MySQL 的工作原理和优化查询性能至关重要。 二、B+树的结构特点 B+树是一种平衡的多路查找树,具有以下结构特点: 每个节点可以存储多个关键字和对应的指针。非叶子节点的关键字起到索引的作用,用于引导查询到相应的子节点