WebViewのActionModeメニューのカスタマイズ
WebViewの文字を選択する時に表示されるActionModeメニューをカスタマイズするプログラムはAndroid7でクラッシュになったため、発生原因と対応策をメモします。
Action Modeメニュー表示場所
Android6以前は、アプリ画面の一上のActionBarにActionModeのメニューが表示されますが、Android6からiOSのようにFloating Action Modeになり、浮かんでいるコンテキストメニューが表示されるようになりました。
独自のActionModeメニューの定義
ActionModeが表示された時に、OSにより表示されるメニューをクリアして、独自なメニューに入れ替える。
- メニュー定義
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_menu_item_search"
android:icon="@android:drawable/ic_menu_search"
android:onClick="onActionItemClicked"
android:orderInCategory="1"
app:showAsAction="always"
android:title="@string/action_menu_item_search">
</item>
<item
android:id="@+id/action_menu_item_copy"
android:icon="@drawable/ic_menu_copy"
android:onClick="onActionItemClicked"
android:orderInCategory="2"
app:showAsAction="always"
android:title="@string/action_menu_item_copy">
</item>
</menu>
- ActionMode起動メソッド
@Override
public void onActionModeStarted(ActionMode mode) {
if (actionMode == null) {
actionMode = mode;
Menu menu = mode.getMenu();
menu.clear();
// Inflate your own menu items
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.mymenu, menu);
}
super.onActionModeStarted(mode);
}
Android7 以降の場合InflateExceptionが発生
Android6までは問題なく動きましたが、Android7.0とAndroid7.1.1でInflateExceptionが発生しました。
W/System.err: android.view.InflateException: Couldn't resolve menu item onClick handler onActionItemClicked in class android.app.ContextImpl
W/System.err: at android.view.MenuInflater$InflatedOnMenuItemClickListener.<init>(MenuInflater.java:242)
W/System.err: at android.view.MenuInflater$MenuState.setItem(MenuInflater.java:460)
W/System.err: at android.view.MenuInflater$MenuState.addItem(MenuInflater.java:494)
W/System.err: at android.view.MenuInflater.parseMenu(MenuInflater.java:190)
W/System.err: at android.view.MenuInflater.inflate(MenuInflater.java:111)
W/System.err: at com.myapp.joke.MyActivity.onActionModeStarted(MyActivity.java:349)
W/System.err: at android.support.v7.view.WindowCallbackWrapper.onActionModeStarted(WindowCallbackWrapper.java:158)
W/System.err: at com.android.internal.policy.DecorView.startActionMode(DecorView.java:868)
W/System.err: at com.android.internal.policy.DecorView.startActionModeForChild(DecorView.java:816)
W/System.err: at android.view.ViewGroup.startActionModeForChild(ViewGroup.java:828)
W/System.err: at android.view.ViewGroup.startActionModeForChild(ViewGroup.java:828)
W/System.err: at android.view.ViewGroup.startActionModeForChild(ViewGroup.java:828)
W/System.err: at android.view.ViewGroup.startActionModeForChild(ViewGroup.java:828)
W/System.err: at android.view.ViewGroup.startActionModeForChild(ViewGroup.java:828)
W/System.err: at android.view.ViewGroup.startActionModeForChild(ViewGroup.java:828)
W/System.err: at android.view.ViewGroup.startActionModeForChild(ViewGroup.java:828)
W/System.err: at android.view.View.startActionMode(View.java:5828)
W/System.err: at org.chromium.content.browser.ContentViewCore.startFloatingActionMode(ContentViewCore.java:2253)
W/System.err: at org.chromium.content.browser.ContentViewCore.startActionMode(ContentViewCore.java:2238)
W/System.err: at org.chromium.content.browser.ContentViewCore.showSelectActionMode(ContentViewCore.java:2213)
W/System.err: at org.chromium.content.browser.ContentViewCore.onSelectionEvent(ContentViewCore.java:2319)
W/System.err: at org.chromium.android_webview.AwContents.nativeOnDraw(Native Method)
W/System.err: at org.chromium.android_webview.AwContents.access$4700(AwContents.java:98)
W/System.err: at org.chromium.android_webview.AwContents$AwViewMethodsImpl.onDraw(AwContents.java:2980)
W/System.err: at org.chromium.android_webview.AwContents.onDraw(AwContents.java:1307)
W/System.err: at com.android.webview.chromium.WebViewChromium.onDraw(WebViewChromium.java:1697)
W/System.err: at android.webkit.WebView.onDraw(WebView.java:2534)
W/System.err: at android.view.View.draw(View.java:17185)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16167)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:3711)
W/System.err: at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:3691)
W/System.err: at android.view.View.updateDisplayListIfDirty(View.java:16130)
W/System.err: at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:648)
W/System.err: at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:654)
W/System.err: at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:762)
W/System.err: at android.view.ViewRootImpl.draw(ViewRootImpl.java:2800)
W/System.err: at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2608)
W/System.err: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2215)
W/System.err: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1254)
W/System.err: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6337)
W/System.err: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:874)
W/System.err: at android.view.Choreographer.doCallbacks(Choreographer.java:686)
W/System.err: at android.view.Choreographer.doFrame(Choreographer.java:621)
W/System.err: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:860)
W/System.err: at android.os.Handler.handleCallback(Handler.java:751)
W/System.err: at android.os.Handler.dispatchMessage(Handler.java:95)
W/System.err: at android.os.Looper.loop(Looper.java:154)
W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6119)
W/System.err: at java.lang.reflect.Method.invoke(Native Method)
W/System.err: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
W/System.err: Caused by: java.lang.NoSuchMethodException: onActionItemClicked [interface android.view.MenuItem]
W/System.err: at java.lang.Class.getMethod(Class.java:1981)
W/System.err: at java.lang.Class.getMethod(Class.java:1637)
W/System.err: at android.view.MenuInflater$InflatedOnMenuItemClickListener.<init>(MenuInflater.java:240)
W/System.err: ... 71 more
例外の原因
WebViewから生成されるFloatingActionModeのコンテキストがMyActivityではなく、DecorViewになっています。DecorViewのベースコンテキストがApplicationであります。Menu定義ファイルに設定されたonclickメソッド「onActionItemClicked」がMyActivityに定義されていますが、DecorViewからベースコンテキストを探しても見つからないため、InflateExceptionがスローされます。
臨時対応策
他の良い方法があるかもしれませんが、臨時対策として、リフレクションでMenuInflaterのコンテキストをMyActivityに入れ替えます。
public void onActionModeStarted(ActionMode mode) {
if (actionMode == null) {
actionMode = mode;
Menu menu = mode.getMenu();
menu.clear();
// Inflate your own menu items
MenuInflater inflater = mode.getMenuInflater();
if(Build.VERSION.SDK_INT >= 24) {
try {
Field field = FieldUtils.getField(inflater.getClass(), "mContext", true);
field.set(inflater, this);
} catch (IllegalAccessException e) {
}
}
inflater.inflate(R.menu.mymenu, menu);
}
super.onActionModeStarted(mode);
}