X

リフレクションを使ってメソッドを呼び出す


Javaにはreflectionと呼ばれる機能があります。クラスからフィールド、メソッド情報を取得するためのインターフェイスです。

取得したメソッドは実行でき、Androidではhideメソッドを使う際に応用出来ます。当然ながら、Android SDKのAPIとして公開されていないhideメソッドを使うことはリスクを伴います。
引数が変わったり、急に無くなってりするかもしれません(サポート対象外なので当然ですね)。
また、リフレクションを使って属性を変更し、Privateメソッド/フィールドにアクセスすることもできるため、こちらはUnitTestのときに威力を発揮します。

リフレクションを使う

reflectionはjava.lang.reflectパッケージとしてサポートされており、動的に実行するメソッドを変えることも出来るので大変便利な機能です。
ただし、本記事の内容を使ってhideAPIを使う場合、間違いなくサポートされない挙動となります。
Androidアプリの機能として提供してよいかリスクについて十分、考えてください。

今回紹介するサンプルでは、TelephonyManagerを例にreflectionの使い方を解説します。
TelephonyManagerそのものについてはTelephonyManagerを使って端末の情報を取得するなど過去の記事を参照してください(右の画像はTelephonyManagerのメソッド一覧です)。

ソースコードはつづきから

メソッドの取得

サンプルとしてクラスからメソッド一覧を取得してみましょう。
■src/reflectionActivity.java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TelephonyManager tm = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);

        Method[] methods = tm.getClass().getDeclaredMethods();
        for (Method method : methods) {
        	Log.d("Reflection",method.getName());
                ...(省略)...
        }
    }

8行目:getClassメソッドで基底となるクラスにアクセスし、method情報が入った配列を受け取ります。
10行目:メソッド名をログに出力します。

このサンプルで取得できるメソッド一覧はhideアノテーションにより隠されたメソッドも含んでいます。
(クラスとして保持するメソッドを抽出できます)

メソッドの実行

先ほどのサンプルに改良を加えて、特定のメソッドを見つけたら実行するように変更しています。
例としてTelephonyManagerクラスのgetNetworkOperatorNameメソッドを実行してみましょう。
■src/reflectionActivity.java

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        TelephonyManager tm = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);

        Method[] methods = tm.getClass().getDeclaredMethods();
        for (Method method : methods) {
        	Log.d("Reflection",method.getName());
            if (method.getName().equals("getNetworkOperatorName")) {
                Log.d("Reflection", "method.getName");
                try {
                	Log.d("Reflection", "invokeMethod");
                    Object obj = method.invoke(tm);
                    Log.d("Reflection", "invoke: " + obj.toString());
                } catch (IllegalArgumentException e) {
                	//呼び出し:引数が異なる
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                	//呼び出し:アクセス違反、保護されている
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                	//ターゲットとなるメソッド自身の例外処理
					e.printStackTrace();
				}
            }
        }
    }

11行目:取得したメソッド一覧から目当てのgetNetworkOperatorNameメソッドを探します。
すべてのメソッドでメソッド名を取得し、一致を判定していきます。
15,16行目:一致したメソッドがあれば(サンプルではgetNetworkOperatorNameメソッド)、
methodクラスのinvokeメソッドを使って実行します。返り値はObjectとして受け取り、利用時にキャストします。
(サンプルではtoStringメソッドで表示するだけなのでキャストは省略しています)

リフレクションを使うときの注意

リフレクションでは引数エラーなどのコンパイル時チェックが入りません。そのため、呼び出したいAPIの仕様をリファレンス、ソースコード等で確認して、よく把握しておく必要があります。
エラー処理においては、実行時に行われるため、引数チェック、関数アクセス権限の確認など複雑になりがちです。
またメソッドがない場合なども考慮し、エラー処理をきちんと実装してください(エラー処理の重要さはリフレクションだけではありませんね)。

以上、おつかれさまでした!

mhidaka: Software Engineerだよ。DroidKaigi Organizer / Androidと組込とRe:VIEW。techbooster主宰。mhidaka's writings http://booklog.jp/users/mhidaka 技術書典! http://techbookfest.org