上一篇已经对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中,菜单项控件的实现类为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的菜单动作有四种触发的方式,分别为:
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; }
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/action_groupchat" android:icon="@drawable/ofm_group_chat_icon" android:showAsAction="ifRoom|withText" android:title="@string/action_groupchat" android:onClick="groupchatClick"/> </menu>然后在Activity中写方法如下:
public boolean groupchatClick(MenuItem item){ System.out.println("click group chat item"); return true; }注意这里的返回值,如果设置为true,则表示拦截菜单项的点击事件,此时不会再执行Activity.onOptionsItemSelected()方法;如果设置为false,则会继续执行Activity.onOptionsItemSelected()方法。
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; } }
public boolean onOptionsItemSelected(MenuItem item) { if (mParent != null) { return mParent.onOptionsItemSelected(item); } return false; }可以看到,Activity.onOptionItemSelected()方法默认返回值为false,也就是说如果我们在Activity中没有重写该方法,它是会一直执行后面的方法的。
@Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.normal, menu); // 找到对应的菜单项,并设置Intent MenuItem item = menu.findItem(R.id.action_groupchat); item.setIntent(new Intent(this, TestActivity.class)); return true; }在这个方法中找到对应的菜单项,并调用setIntent()方法传入Intent即可;
public class MyActionProvider extends ActionProvider { public MyActionProvider(Context context) { super(context); } @Override public View onCreateActionView() { return null; } @Override public boolean onPerformDefaultAction() { System.out.println("execute Action provider method"); return super.onPerformDefaultAction(); } }然后在menu的xml中配置android:actionProviderClass属性
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/action_addfriend" android:icon="@drawable/ofm_add_icon" android:showAsAction="ifRoom" android:title="@string/action_addfriend" android:numericShortcut="2" android:actionProviderClass="com.example.actionbardemo.MyActionProvider"/> </menu>
public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.normal, menu); // 找到对应的菜单项,并设置ActionProvider MenuItem item = menu.findItem(R.id.action_groupchat); item.setActionProvider(new ActionProvider(this) { @Override public View onCreateActionView() { return null; } @Override public boolean onPerformDefaultAction() { System.out.println("execute Action provider method"); return super.onPerformDefaultAction(); } }); return true; }