2011年6月19日日曜日

レイアウトのXMLに配置したButtonのOnClickがDialogだと使えない?

最近、ButtonのOnClickListenerを実装するのが面倒なので、レイアウトXMLのonClickを利用しているのですが、Dialogでも同じように使おうとしたところ、
以下のメッセージと共に、java.lang.IllegalStateException例外が発生しました。

「Could not find a method buttonClick(View) in the activity」

その時はOnClickListenerを実装した方が早かったので、深く調べなかったのですが、
たまたま思い出したので調べてみました。
※今回はAndroid2.2のソースコードを追いかけました。1.6でも同様の現象に
ぶち当たるのではないかと思います。

実際、レイアウトXMLのOnClickが設定される場所は、Viewクラスにあります。

case R.styleable.View_onClick:
                    if (context.isRestricted()) {
                        throw new IllegalStateException("The android:onClick attribute cannot " 
                                + "be used within a restricted context");
                    }

                    final String handlerName = a.getString(attr);
                    if (handlerName != null) {
                        setOnClickListener(new OnClickListener() {
                            private Method mHandler;

                            public void onClick(View v) {
                                if (mHandler == null) {
                                    try {
                                        mHandler = getContext().getClass().getMethod(handlerName,
                                                View.class);
                                    } catch (NoSuchMethodException e) {
                                        int id = getId();
                                        String idText = id == NO_ID ? "" : " with id '"
                                                + getContext().getResources().getResourceEntryName(
                                                    id) + "'";
                                        throw new IllegalStateException("Could not find a method " +
                                                handlerName + "(View) in the activity "
                                                + getContext().getClass() + " for onClick handler"
                                                + " on view " + View.this.getClass() + idText, e);
                                    }
                                }

                                try {
                                    mHandler.invoke(getContext(), View.this);
                                } catch (IllegalAccessException e) {
                                    throw new IllegalStateException("Could not execute non "
                                            + "public method of the activity", e);
                                } catch (InvocationTargetException e) {
                                    throw new IllegalStateException("Could not execute "
                                            + "method of the activity", e);
                                }
                            }
                        });
                    }
                    break;

handlerName変数には呼び出されるはずのメソッド名が設定されています。

ポイントは、以下のコードになります。

mHandler = getContext().getClass().getMethod(handlerName,
                                                View.class);

この部分で、クラス内にOnClickで設定したメソッドがあるかどうかを確認しています。
さて、今回の現象はActivityだとメソッドが発見できて、Dialogだと発見出来ないわけですから、getContext().getClass()で返されるクラスのインスタンスが違ってそうな気がします。
ということで、Dialog#onCreate(setContentView()を呼び出した直後)で

Log.d("TEST", "ClassName = " + getContext().getClass().getName());

なんてことをしてみました。

すると、出力されたログをみると、

ClassName = android.view.ContextThemeWrapper

と出力されました。

Activityだと同じ事ができないのですが、恐らく、getContext()がActivityのインスタンスを返すのだろうと思われます。

Activityのインスタンスであれば、自分で実装してあれば、メソッドが発見できますが、DialogでgetContext()するとContextThemeWrapperが返ってくるため、
OnClickのメソッドが発見出来ないわけです。

ちなみに、Dialog#getContext()が返す、Contextは、以下の通りです。
mContext = new ContextThemeWrapper(
            context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme);

    public final Context getContext() {
        return mContext;
    }

ということで、結論は、「DialogではレイアウトXMLのOnClickは使えない」と言うことがわかりました。

DialogではおとなしくOnClickListenerを実装しましょう。