上一篇已经对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;
}
然后在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属性
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;
}