Android查缺补漏(View篇)--事件分发机制源码分析
在上一篇博文中分析了事件分发的流程及规则,本篇会从源码的角度更进一步理解事件分发机制的原理,如果对事件分发规则还不太清楚的童鞋,建议先看一下上一篇博文 《Android查缺补漏(View篇)--事件分发机制》 ,先来看一下本篇的分析思路,一会儿会按照事件传递的顺序,针对以下几点进行源码分析:
- Activity对点击事件的分发过程
- PhoneWindow是如何处理点击事件的
- 顶级View对点击事件的分发过程
- View对点击事件的处理过程
Activity对点击事件的分发过程
通过上一篇博文中我们就知道了当一个点击事件发生后,最先传递给当前的Activity,并有Activity的dispatchTouchEvent方法来分发,其具体的分发工作时由Activity内部的Window处理的。
Activity的dispatchTouchEvent方法源码如下:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction(); // 这是一个空方法,不用理会
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
在dispatchTouchEvent内部我们看到通过getWindow获取了Window,然后将事件分发的处理操作交给了Window,通过上述源码我们即可看出从大的流程上Window将分发给顶级View,如果在getWindow().superDispatchTouchEvent(ev)这一步返回了true,那么事件循环就此结束,返回false意味着各界的View的onTouchEvent都返回了false,最终会去调用当前Activity的onTouchEvent。
PhoneWindow是如何处理点击事件的
接着详细看一下在getWindow().superDispatchTouchEvent(ev)中都干了啥,我们知道Window是一个抽象类,我们必须找出他们实现类才可以进行下一步分析,很简单,通过在Activity中搜索mWindow变量,很快我们就在Activity的attch方法中找到了它的赋值过程:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
...// 省略其他代码
}
没错,Window的实现类就是PhoneWindow,接下来直接去看PhoneWindow的superDispatchTouchEvent方法:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
PhoneWindow什么都没做,直接把DecorView,DecorView就是Activity的顶级View,在Activity中可以通过getWindow().getDecorView()获取。
顶级View(DecorView)对点击事件的分发过程
DecorView的superDispatchTouchEvent源码如下:
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
其实DecorView继承了FrameLayout,当然也是一个ViewGroup,所以这里我们再跳转到ViewGroup的dispatchTouchEvent方法中进一步分析,在上一篇博文《Android查缺补漏(View篇)--事件分发机制》 中已经详细介绍了ViewGroup的dispatchTouchEvent对点击事件分发的流程,这里再通过源代码了解一下它的实现过程。
这个方法非常长,但我们一步步分析还是能找到一些头绪的,请着重看代码中注释。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
// 对事件进行安全过滤
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 初始化事件流的状态,当有down类型的touch事件传来时说明是一个新的事件流,此时需要重置事件状态。
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查是否拦截事件,当事件交由ViewGroup的子元素处理时,就会将mFirstTouchTarget赋值并指向子元素。
// 更详细来说就是,当ViewGroup拦截事件时,mFirstTouchTarget为null,这时候除了down以外的事件类型到来时将不再调用onInterceptTouchEvent,同一事件流中的其他时间类型都会讲给ViewGroup处理。当ViewGroup不拦截事件时,mFirstTouchTarget将会被赋值指向为子元素。
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 这里包含了一种特殊情况,那就是在子View中可以通过requesetDisallowInterceptTouchEvent方法来干预ViewGroup对除了down以外的事件的分发过程,
// 这里的FLAG_DISALLOW_INTERCEPT标记为就是通过在requestDisallowInterceptTouchEvent方法来设置的,FLAG_DISALLOW_INTERCEPT被设置后,ViewGroup就不在对事件拦截。
// 当down事件传来后,在前面的resetTouchState方法中会对FLAG_DISALLOW_INTERCEPT重置,所以说子View无法影响ViewGroup对down事件的处理。
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessiiblity focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
// 当ViewGroup不拦截事件的情况下,事件就会交给子View处理
// 首先遍历ViewGroup的所有子View,判断子View是否能够接收到点击事件:
// 判断的依据是:子元素是否在播放动画和点击事件的坐标是否落在子元素的范围内。
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
/*
这里的dispatchTransformedTouchEvent方法实际调用的就是子元素的dispatchTouchEvent方法。
这个方法内部有一段逻辑是这样:
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
由于这里传入的child非null,所以此时事件就交给了子view处理。
如果子View的dispatchTouchEvent方法返回了true,那么mFirstTouchTarget就会被赋值(在addTouchTarget()中被赋值),同时跳出for循环。
如果子View返回了false,那就继续循环,按同样的套路将调用下一个子View的dispatchTouchEvent方法。(例如:多个View重叠在一起时,这样在该区域就有多个满足接收点击事件的条件的控件)
*/
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
/*
mFirstTouchTarget为null时,ViewGroup就会拦截接下来的同一序列中的所有事件。
如果遍历了所有的子元素事件都没有被合适的处理(可能是因为该ViewGroup没有子View,或者在子View的dispatchTouchEvent方法返回了false),ViewGroup就会自己处理事件。
这里的第三个参数传入了null,在dispatchTransformedTouchEvent中通过以下逻辑即可知道会调用super.dispatchTouchEvent(event);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
*/
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
...
}
上面代码是对顶级View(其实也是ViewGroup)事件分发的分析,接下来再看看当事件传递给View时,View对事件的处理代码。
View对点击事件的处理过程
从上面的分析我们可知,ViewGroup是通过调用View的dispatchTouchEvent方法将事件传递给View的,那么就来看一下View的dispatchTouchEvent方法源码:
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
在上面的源码中,首先会获取View的ListenerInfo判断有没有设置OnTouchListener,如果设置了并且在OnTouchListener中的onTouch方法返回了true,那么就不再调用View自身的onTouchEvent方法,至此我们也找到了OnTouchListener监听器中的onTouch方法优先于onTouchEvent方法的原因。
接着再看onTouchEvent方法的源码:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
// 从这段代码可知,即使不可用状态的控件也会消费事件。
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
// mTouchDelegate是View的代理,有代理的情况下回调用代理的onTouchEvent方法。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// 从上面代码为clickable赋值时我们可以知道,不管View是不是disable状态,只要它的CLICKABLE和LONG_CLICKABLE有一个为true时,它就会消耗这个事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// action_up事件会触发performClick方法,在performClick方法中调用了View的onClickListener监听器的onClick方法。
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
当action_up事件到来时会触发performClick方法,在performClick方法中调用了View的onClickListener监听器的onClick方法,所以至此我们应该知道onClick方法是在up事件发生后调用的,优先级最低。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
小结
到这里整个事件分发的源码基本分析完毕,大体总结一下:
- 当点击事件产生后,会首先触发Activity的dispatchTouchEvent方法,在此方法中会将事件传递个PhoneWindow处理;
- 在PhoneWindow的dispatch方法中又将事件传递个了DecorView的dispatchTouchEvent;
- 接着DecorView又调用了父类ViewGroup的dispatchTouchEvent方法对事件进行了分发;
- 在ViewGroup的dispatchTouchEvent方法中,首先先判断了自身是否拦截事件,如果拦截那么事件不再向其子View传递,如果不拦截就会遍历其所有的子View找到可以适合接收事件的子View并调用子View的dispatchTouchEvent方法。
- 在子View的dispatchTouchEvent方法中对事件做相应处理。
最后想说的是,本系列文章为博主对Android知识进行再次梳理,查缺补漏的学习过程,一方面是对自己遗忘的东西加以复习重新掌握,另一方面相信在重新学习的过程中定会有巨大的新收获,如果你也有跟我同样的想法,不妨关注我一起学习,互相探讨,共同进步!
参考文献:
- 《Android开发艺术探索》
Android查缺补漏(View篇)--事件分发机制源码分析的更多相关文章
- Android事件分发机制源码分析
Android事件分发机制源码分析 Android事件分发机制源码分析 Part1事件来源以及传递顺序 Activity分发事件源码 PhoneWindow分发事件源码 小结 Part2ViewGro ...
- Qt事件分发机制源码分析之QApplication对象构建过程
我们在新建一个Qt GUI项目时,main函数里会生成类似下面的代码: int main(int argc, char *argv[]) { QApplication application(argc ...
- Android View事件分发-从源码分析
View事件分发-从源码分析 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/details/78555397?lo ...
- Android View 事件分发机制 源码解析 (上)
一直想写事件分发机制的文章,不管咋样,也得自己研究下事件分发的源码,写出心得~ 首先我们先写个简单的例子来测试View的事件转发的流程~ 1.案例 为了更好的研究View的事件转发,我们自定以一个My ...
- (转)spring boot实战(第三篇)事件监听源码分析
原文:http://blog.csdn.net/liaokailin/article/details/48194777 监听源码分析 首先是我们自定义的main方法: package com.lkl. ...
- Android开发学习之路-Handler消息派发机制源码分析
注:这里只是说一下sendmessage的一个过程,post就类似的 如果我们需要发送消息,会调用sendMessage方法 public final boolean sendMessage(Mess ...
- View的事件分发机制解析
引言 Android事件构成 在Android中,事件主要包含点按.长按.拖拽.滑动等,点按又包含单击和双击,另外还包含单指操作和多指操作.全部这些都构成了Android中的事件响应.总的来说.全部的 ...
- Android查缺补漏(View篇)--自定义 View 的基本流程
View是Android很重要的一部分,常用的View有Button.TextView.EditView.ListView.GridView.各种layout等等,开发者通过对这些View的各种组合以 ...
- Android查缺补漏(IPC篇)-- Bundle、文件共享、ContentProvider、Messenger四种进程间通讯介绍
本文作者:CodingBlock 文章链接:http://www.cnblogs.com/codingblock/p/8387752.html 进程间通讯篇系列文章目录: Android查缺补漏(IP ...
随机推荐
- WPF的消息机制(二)- WPF内部的5个窗口之隐藏消息窗口
目录 WPF的消息机制(一)-让应用程序动起来 WPF的消息机制(二)-WPF内部的5个窗口 (1)隐藏消息窗口 (2)处理激活和关闭的消息的窗口和系统资源通知窗口 (3)用于用户交互的可见窗口 (4 ...
- RAID常用级别的比较
[转]RAID常用级别的比较 特点 硬盘及容量 性能及安全 典型应用 raid 0 用于平行存储,即条带.其原理是把连续的数据分成几份,然后分散存储到阵列中的各个硬盘上.任何一个磁盘故障,都将导致数据 ...
- Web App、Hybrid App与Native App
在这个App的时代,转战了前端,一直接触的都是pc, 离out不远了. 那么接下来,app是我接下来半年的重点,为什么是半年,因为时间不多了. 因为是前端,那么我的重心肯定是 Web App, Hyb ...
- 更新Appium中的WebDriverAgent
到WebDriverAgent下载最新版本的WebDriverAgent 进入下载后的WebDriverAgent文件 执行 ./Scripts/bootstrap.sh 直接用Xcode打开WebD ...
- Python模块学习---Web
import urlparse url = urlparse.urlparse("http://www.python.org/doc/FAQ.html") print url pr ...
- java中碰到无法解决的问题:无法访问类的getter访问器
大牛们来看看,俺这是咋了?因博问不让发图,发到这里求助: 以上两个方法都是从mysql中select数据,为嘛第二个出现辣鸡报错? 请注意: reslist.size() = 289 第二种方法已经获 ...
- 表单验证控件Verify.js
自己工作常用到表单录入验证,就顺手写了一个验证控件,刚开始写得很烂.多年后翻出来,又优化了一下,增加了一些功能.拿出来分享分享. 主要功能就是表单的录入验证. * 1.当录入框必填时,在控件后生成红色 ...
- The `XXXX` target overrides the `HEADER_SEARCH_PATHS` build setting defined in `Pods/Target Support Files/Pods-game-desktop/Pods-game-desktop.release.xcconfig'. This can lead to prob
The `game-desktop [Release]` target overrides the `HEADER_SEARCH_PATHS` build setting defined in `Po ...
- Talking appsettings.json in Asp.Net Core
在ASP.NET Core中,默认提供了三个运行时环境变量,通过查看Hosting源代码我们可以看到,分别是Development.Staging.Production public static c ...
- mongodb 创建用户
1.创建管理员 2.授权认证 3.给使用的数据库添加用户 普通连接(非授权连接)数据库 mongod -f /usr/local/etc/mongod.conf 授权连接数据库 mongod -f / ...