上一篇已经对ActionBar菜单构造过程进行比较详细的分析,没有看过的朋友可以移步android中ActionBar的源代码分析(二)。本章接着对ActionBar菜单的执行过程进行分析。

在介绍ActionBar菜单的执行过程之前,首先我们需要了解android的消息处理机制,我们知道activity的activity等组件和view控件都是运行在主线程上的,这个主线程我们也称为UI线程,UI线程的管理类为ActivityThread,它由Zygote进程孵化应用程序进程过程中创建起来的,在ActivityThread创建过程中,会调用Looper.prepareMainLooper()创建一个主looper来维护整个应用程序UI方面的消息队列,并由ViewRootImpl.ViewRootHandler负责消息的分发处理;比如按钮或者菜单的点击事件,是由ViewRootHandler对该点击消息进行分发,执行点击消息的callback事件即View.performClick()进行处理,在View.performClick()方法中如果发现有注册了View.OnClickListener回调接口的对象,就调用该接口的onClick方法执行回调。

ActionBar菜单执行过程分析

在ActionBar中,菜单项控件的实现类为ActionMenuItemView,ActionMenuItemView继承于TextView,也就是说一个菜单项就是一个TextView。在ActionMenuItemView的构造函数中就注册了View.OnClickListener回调接口:

    public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);......        setOnClickListener(this);        setOnLongClickListener(this);        setTransformationMethod(new AllCapsTransformationMethod(context));        mSavedPaddingLeft = -1;    }
可以看到,在调用setOnClickListener()方法时, ActionMenuItemView把本身作为参数传了进去,也即是ActionMenuItemView是实现了View.OnClickListener接口的,查看一下ActionMenuItemView.onClick(View v)方法如下:

    @Override    public void onClick(View v) {        if (mItemInvoker != null) {            mItemInvoker.invokeItem(mItemData);        }    }
这里调用判断mItemInvoker是否为空,如果不为空则执行mItemInvoker.invokeItem方法,既然菜单能够进行点击,那mItemInvoker肯定不为空啊,那这个mItemInvoker是个什么东东呢?从上一章的ActionBar菜单的构造过程我们知道, ActionMenuItemView是由类ActionMenuPresenter创建并绑定MenuItemImpl的,我们看一下ActionMenuPresenter.bindItemView()方法的实现吧:

    @Override    public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) {        itemView.initialize(item, 0);        final ActionMenuView menuView = (ActionMenuView) mMenuView;        ActionMenuItemView actionItemView = (ActionMenuItemView) itemView;        actionItemView.setItemInvoker(menuView);    }
可以清晰的看到,在这个方法中首先调用了ActionMenuItemView.initialize()进行初始化操作,值得注意的是,这里把MenuItemImpl作为参数传入,也就是说在ActionMenuItemView中存在对MenuItemImpl的引用,在后面的点击事件中会用到这个MenuItemImpl,这里先不深入说明;然后调用了ActionMenuItemView.setItemInvoker()方法,并把menuView传入进去,这个menuView实现类为ActionMenuView,也就是说,在ActionMenuItemView.onClick()方法中调用的 mItemInvoker.invokeItem,实际就是调用ActionMenuView.invokeItem()方法,下面我们看一下ActionMenuView.invokeItem()的实现逻辑

    public boolean invokeItem(MenuItemImpl item) {        return mMenu.performItemAction(item, 0);    }
这个方法很简单,就是调用了mMenu.performItemAction(),这个mMenu就是MenuBuilder,下面是MenuBuilder.performItemAction()方法的实现逻辑:

    public boolean performItemAction(MenuItem item, int flags) {        MenuItemImpl itemImpl = (MenuItemImpl) item;                if (itemImpl == null || !itemImpl.isEnabled()) {            return false;        }        boolean invoked = itemImpl.invoke();        ......                return invoked;    }
这个方法就调用了itemImpl.invoke()方法进行处理,这里的itemImpl就是上面ActionMenuPresenter.bindItemView()方法中调用ActionMenuItemView.initialize()方法中传入的MenuItemImpl对象,下面是MenuItemImpl.invoke()方法的实现逻辑:

    public boolean invoke() {        if (mClickListener != null &&                mClickListener.onMenuItemClick(this)) {            return true;        }        if (mMenu.dispatchMenuItemSelected(mMenu.getRootMenu(), this)) {          return true;        }        if (mItemCallback != null) {            mItemCallback.run();            return true;        }        if (mIntent != null) {            try {                mMenu.getContext().startActivity(mIntent);                return true;            } catch (ActivityNotFoundException e) {                Log.e(TAG, "Can't find activity to handle intent; ignoring", e);            }        }        if (mActionProvider != null && mActionProvider.onPerformDefaultAction()) {            return true;        }        return false;    }

在这个方法中,首先判断mClickListener是否为空,如果不为空,则调用mClickListener.onMenuItemClick()方法,并且判断该方法返回值是否为true,如果为true,则退出并完成整个点击处理过程;否则就会调用MenuBuilder.dispatchMenuItemSelected()方法,当MenuBuilder.dispatchMenuItemSelected()方法返回值为true,则退出并完成整个点击处理过程;否则就判断mItemCallback是否为空,不为空就调用mItemCallback.run()方法,这个mItemCallback是通过MenuItemImpl.setCallback()传入进来的回调对象,而由于MenuItem并没有setCallback()这个方法,因此外部是无法调用的,可能考虑是android内部使用的方法,我们暂且不考虑;如果mItemCallback为空,则判断mIntent是否为空,不为空则根据Intent跳转到对应的Activity上;否则判断ActionProvider是否为空,不为空则调用ActionProvider.onPerformDefaultAction()方法;

根据invoke()方法的调用逻辑,我们对ActionBar的菜单动作有四种触发的方式,分别为:

  1. 通过对mClickListener来触发点击事件;
  2. 使用系统的菜单点击事件;
  3. 通过对mIntent赋值来触发跳转到相应的Activity;
  4. 通过使用ActionProvider;

通过对mClickListener来触发点击事件

我们先分析一下菜单的xml配置吧。我们知道类MenuInflater是负责解析menu的xml配置,通过MenuInflater.MenuState对菜单项属性进行赋值的, 其中MenuInflater.MenuState.readItem()负责解析xml的配置,MenuInflater.MenuState.setItem()负责对菜单项(MenuItemImpl)进行属性负责,下面看一下readItem()方法:
        public void readItem(AttributeSet attrs) {            TypedArray a = mContext.obtainStyledAttributes(attrs,                    com.android.internal.R.styleable.MenuItem);            // Inherit attributes from the group as default value            itemId = a.getResourceId(com.android.internal.R.styleable.MenuItem_id, defaultItemId);            final int category = a.getInt(com.android.internal.R.styleable.MenuItem_menuCategory, groupCategory);            final int order = a.getInt(com.android.internal.R.styleable.MenuItem_orderInCategory, groupOrder);            itemCategoryOrder = (category & Menu.CATEGORY_MASK) | (order & Menu.USER_MASK);            itemTitle = a.getText(com.android.internal.R.styleable.MenuItem_title);            itemTitleCondensed = a.getText(com.android.internal.R.styleable.MenuItem_titleCondensed);            itemIconResId = a.getResourceId(com.android.internal.R.styleable.MenuItem_icon, 0);            itemAlphabeticShortcut =                    getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_alphabeticShortcut));            itemNumericShortcut =                    getShortcut(a.getString(com.android.internal.R.styleable.MenuItem_numericShortcut));            if (a.hasValue(com.android.internal.R.styleable.MenuItem_checkable)) {                // Item has attribute checkable, use it                itemCheckable = a.getBoolean(com.android.internal.R.styleable.MenuItem_checkable, false) ? 1 : 0;            } else {                // Item does not have attribute, use the group's (group can have one more state                // for checkable that represents the exclusive checkable)                itemCheckable = groupCheckable;            }            itemChecked = a.getBoolean(com.android.internal.R.styleable.MenuItem_checked, defaultItemChecked);            itemVisible = a.getBoolean(com.android.internal.R.styleable.MenuItem_visible, groupVisible);            itemEnabled = a.getBoolean(com.android.internal.R.styleable.MenuItem_enabled, groupEnabled);            itemShowAsAction = a.getInt(com.android.internal.R.styleable.MenuItem_showAsAction, -1);            itemListenerMethodName = a.getString(com.android.internal.R.styleable.MenuItem_onClick);            itemActionViewLayout = a.getResourceId(com.android.internal.R.styleable.MenuItem_actionLayout, 0);            itemActionViewClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionViewClass);            itemActionProviderClassName = a.getString(com.android.internal.R.styleable.MenuItem_actionProviderClass);            final boolean hasActionProvider = itemActionProviderClassName != null;            if (hasActionProvider && itemActionViewLayout == 0 && itemActionViewClassName == null) {                itemActionProvider = newInstance(itemActionProviderClassName,                            ACTION_PROVIDER_CONSTRUCTOR_SIGNATURE,                            mActionProviderConstructorArguments);            } else {                if (hasActionProvider) {                    Log.w(LOG_TAG, "Ignoring attribute 'actionProviderClass'."                            + " Action view already specified.");                }                itemActionProvider = null;            }            a.recycle();            itemAdded = false;        }

从这段代码中,可以看出ActionBar的菜单支持配置的属性包括:
  • android:id 菜单的项ID,也是菜单项的唯一标识,不能重复;
  • android:menuCategory 同种菜单项的种类。该属性可取4个值:container、system、secondary和alternative。通过menuCategroy属性可以控制菜单项的位置。其排列顺序受类MenuBuilder的数组变量sCategoryToOrder的控制,其排列顺序从小到大为alternative、secondary、container、system;例如将属性设为system,表示该菜单项是系统菜单,应放在其他种类菜单项的后面;
  • android:orderInCategory 同种类菜单的排列顺序。该属性需要设置一个整数值。例如menuCategory属性值都为system的3个菜单项(item1、item2和item3)。将这3个菜单项的orderInCategory属性值设为3、2、1,那么item3会显示在最前面,而item1会显示在最后面
  • android:title 菜单项标题(菜单项显示的文本)
  • android:titleCondensed 菜单项的短标题。当菜单项标题太长时会显示该属性值
  • android:icon 菜单项图标资源ID
  • android:alphabeticShortcut 菜单项的字母快捷键
  • android:numericShortcut 菜单项的数字快捷键
  • android:checkable 表示菜单项是否带复选框。该属性可设计为true或false
  • android:checked 如果菜单项带复选框(checkable属性为true),该属性表示复选框默认状态是否被选中。可设置的值为true或false
  • android:visible 菜单项默认状态是否可见
  • android:enabled 菜单项默认状态是否被激活
  • android:onClick 菜单项点击时触发的事件方法
  • android:showAsAction 菜单项显示的方式,可设置值包括:never(在ActionBar上不显示,而是显示在Overflow菜单中)、ifRoom(当ActionBar有足够的空间时则显示在ActionBar上,否则显示在Overflow菜单中)、always(总是显示在ActionBar上)、withText(在ActionBar上显示文字)、collapseActionView(声明了这个操作视窗应该被折叠到一个按钮中,当用户选择这个按钮时,这个操作视窗展开)
  • android:actionLayout 通过布局文件设置菜单项ActionView,不能和android:actionViewClass、android:actionProviderClass共同使用,否则会覆盖android:actionViewClass和android:actionProviderClass的设置;
  • android:actionViewClass 通过代码类设置菜单项的ActionView,不能和android:actionProviderClass共同使用,否则会覆盖android:actionProviderClass的设置
  • android:actionProviderClass 通过代码类设置菜单项的ActionProvider;
从上面可知,我们只需要在menu的xml中配置android:onClick属性即可,如下例:
        
然后在Activity中写方法如下:
public boolean groupchatClick(MenuItem item){System.out.println("click group chat item");return true;}
注意这里的返回值,如果设置为true,则表示拦截菜单项的点击事件,此时不会再执行Activity.onOptionsItemSelected()方法;如果设置为false,则会继续执行Activity.onOptionsItemSelected()方法。

使用系统的菜单点击事件

从MenuItemImpl.invoke()方法我们可知,如果mClickListener为空,则会执行MenuBuilder.dispatchMenuItemSelected()方法如下:
    boolean dispatchMenuItemSelected(MenuBuilder menu, MenuItem item) {        return mCallback != null && mCallback.onMenuItemSelected(menu, item);    }
代码很简单,判断mCallback是否为空,如果不为空则执行mCallback.onMenuItemSelected()方法,这个mCallback是什么呢?从上一章的分析可知,类PhoneWindow在initializePanelMenu方法中创建MenuBuilder时,调用了MenuBuilder.setCallback()方法,把自己作为参数传入,因此这个mCallback就是PhoneWindow,我们看一下PhoneWindow.onMenuItemSelected()方法:
    public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {        final Callback cb = getCallback();        if (cb != null && !isDestroyed()) {            final PanelFeatureState panel = findMenuPanel(menu.getRootMenu());            if (panel != null) {                return cb.onMenuItemSelected(panel.featureId, item);            }        }        return false;    }
在这个方法中,获取PhoneWindow的回调对象,判断不为空则调用回调方法onMenuItemSelected(),从上一章分析可知,这个getCallback()返回值为Activity,下面是Activity.onMenuItemSelected()方法的代码:
    public boolean onMenuItemSelected(int featureId, MenuItem item) {        switch (featureId) {            case Window.FEATURE_OPTIONS_PANEL:                // Put event logging here so it gets called even if subclass                // doesn't call through to superclass's implmeentation of each                // of these methods below                EventLog.writeEvent(50000, 0, item.getTitleCondensed());                if (onOptionsItemSelected(item)) {                    return true;                }                if (mFragments.dispatchOptionsItemSelected(item)) {                    return true;                }                if (item.getItemId() == android.R.id.home && mActionBar != null &&                        (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {                    if (mParent == null) {                        return onNavigateUp();                    } else {                        return mParent.onNavigateUpFromChild(this);                    }                }                return false;                            case Window.FEATURE_CONTEXT_MENU:                EventLog.writeEvent(50000, 1, item.getTitleCondensed());                if (onContextItemSelected(item)) {                    return true;                }                return mFragments.dispatchContextItemSelected(item);            default:                return false;        }    }

在这个方法中,判断传入的featureId的值,从前面分析可知,这个featureId就是Window.FEATURE_OPTIONS_PANEL,因此就会执行onOptionsItemSelected()方法;如果onOptionsItemSelected()返回值为true,则退出处理,否则执行fragment的菜单分发事件;代码最后是判断item是否home键,并且ActionBar的选项设置是否为显示Home的返回键,如果是则调用onNavigateUp()方法返回到上一个Activity中。我们重点看一下Activity.onOptionsItemSelected()方法如下:
    public boolean onOptionsItemSelected(MenuItem item) {        if (mParent != null) {            return mParent.onOptionsItemSelected(item);        }        return false;    }
可以看到,Activity.onOptionItemSelected()方法默认返回值为false,也就是说如果我们在Activity中没有重写该方法,它是会一直执行后面的方法的。 由此,我们终于知道了重写Activity.onOptionItemSelected()方法的前因后果了,值得注意的是,在重写该方法后要return super.onOptionsItemSelected(item);避免事件被意外拦截而导致不可意料的问题出现。

通过对mIntent赋值来触发跳转到相应的Activity

通过对mIntent赋值来触发跳转到相应的Activity的前提有两个:
  1. 不能再xml中配置android:onClick属性的值;或者就算配置了,那配置的方法返回值必须为false;
  2. 确保Activity.onOptionsItemSelected()方法的返回值为false(默认情况返回值就是false);
举例如下: 首先重写Activity.onCreateOptionsMenu()方法:
@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.normal, menu);// 找到对应的菜单项,并设置IntentMenuItem item = menu.findItem(R.id.action_groupchat);item.setIntent(new Intent(this, TestActivity.class));return true;}
在这个方法中找到对应的菜单项,并调用setIntent()方法传入Intent即可;

通过使用ActionProvider

使用ActionProvider的前提有三个:
  1. 不能再xml中配置android:onClick属性的值;或者就算配置了,那配置的方法返回值必须为false;
  2. 确保Activity.onOptionsItemSelected()方法的返回值为false(默认情况返回值就是false);
  3. 不能调用MenuItem.setIntent()方法对Menu设置Intent
举例如下: 首先写一个类继承类ActionProvider:
public class MyActionProvider extends ActionProvider {public MyActionProvider(Context context) {super(context);}@Overridepublic View onCreateActionView() {return null;}@Overridepublic boolean onPerformDefaultAction() {System.out.println("execute Action provider method");return super.onPerformDefaultAction();}}
然后在menu的xml中配置android:actionProviderClass属性
        

或者通过重写Activity.onCreateOptionsMenu()方法也能达到同样的效果:
public boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.normal, menu);// 找到对应的菜单项,并设置ActionProviderMenuItem item = menu.findItem(R.id.action_groupchat);item.setActionProvider(new ActionProvider(this) {@Overridepublic View onCreateActionView() {return null;}@Overridepublic boolean onPerformDefaultAction() {System.out.println("execute Action provider method");return super.onPerformDefaultAction();}});return true;}

关于ActionBar的菜单执行过程分析到此为止,下一篇就ActionBar的OverflowMenu的运行机制进行分析,敬请期待!

更多相关文章

  1. Android 项目导入eclipse中报错但找不到错误地方的解决方法
  2. Android中_TextView属性的XML详解 包括单行显示等等。
  3. Android -- Layout布局文件里的android:layout_height等属性为什
  4. Linux ubuntu repo安装方法
  5. 为android开放类增加自定义成员方法
  6. Android测试方法总结汇总
  7. Android项目源码混淆问题解决方法
  8. (转)Android从服务器端获取数据的几种方法
  9. Android socket通信 readline方法阻塞

随机推荐

  1. 手机Root后如何拿取data/data目录下的文
  2. Android中的FrameBuffer
  3. Android自定义控件基本原理详解(一)
  4. Android数据存储(五) SQLite数据库在Androi
  5. android初学者必须掌握的Activity状态的
  6. android Bitmap、Drawable、byte[]相互转
  7. ffmpeg--把一个eclipse目录结构的Android
  8. 如何去写Android(安卓)init.rc (Android(
  9. Android的内存分配、管理、OOM这一篇文章
  10. Android(安卓)Studio 中使用github功能