android 使用反射獲取MediaPlayer的Invoke方法

最近有需求需要使用MediaPlayer的invoke接口去實現某些功能, 但是invoke接口是隱藏的, 沒有在sdk中開放出來. 所以使用反射的方法來獲取invoke接口, 但在實現的過程中出現一些問題, 在這裏記錄一下.

1.使用反射的方式獲取隱藏的接口

    if (mMediaPlayer != null) {
        Parcel request = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            request.writeInt(200);

            Class<?> cls = mMediaPlayer.getClass();
            Method method = cls.getDeclaredMethod("invoke", Parcel.class, Parcel.class);
            method.setAccessible(true); //如果隱藏接口是public的, 這句可以不要
            method.invoke(mMediaPlayer, request, reply); 

            int result = reply.readInt();
            if (0 == result) {
              return false;
          } else if (1 == result) {
              return true;
          }
        }  catch (Exception e) {
                e.getCause().printStackTrace();
        } finally {
            request.recycle();
            reply.recycle();
        }
    }

反射的調用步驟:
(1) 獲取相關類的class
Class cls = mMediaPlayer.getClass();
或者通過包名獲取:
Class cls = Class.forName (“android.media.MediaPlayer”);

(2) 通過方法名獲得方法接口, 如果方法有參數, 需要將參數的class傳入
Method method = cls.getDeclaredMethod(“invoke”, Parcel.class, Parcel.class);

(3) 通過Method的invoke接口來實現方法的調用, 這時候需要傳參, 將參數傳入:
method.invoke(mMediaPlayer, request, reply);

2.出現的問題
當通過這種方式調用後會發現無效, 並且會打印下面的error信息:

01-01 08:31:42.270 W/System.err( 1475): java.lang.reflect.InvocationTargetException
01-01 08:31:42.270 W/System.err( 1475):  at java.lang.reflect.Method.invokeNative(Native Method)
01-01 08:31:42.270 W/System.err( 1475):  at java.lang.reflect.Method.invoke(Method.java:511)

這個問題搞了好久, 反射調用的方法應該沒什麼問題, 錯誤應該是其他的, 仔細看log發現有下列信息:

01-01 08:31:42.269 E/Parcel  ( 3212): Reading a NULL string not supported here.

我沒有傳入任何string下去, 卻報出這個error, 應該是需要再出入一個string下去.

這個時候查看MediaPlayer的源碼, 看到selectOrDeselectTrack等方法內部也是調用的invoke接口:

    private void selectOrDeselectTrack(int index, boolean select)
            throws IllegalStateException {
        Parcel request = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        try {
            request.writeInterfaceToken(IMEDIA_PLAYER);
            request.writeInt(select ? INVOKE_ID_SELECT_TRACK
                    : INVOKE_ID_DESELECT_TRACK);
            request.writeInt(index);
            invoke(request, reply);
        } finally {
            request.recycle();
            reply.recycle();
        }
    }

這裏面與我寫的調用invoke接口多了一句:

request.writeInterfaceToken(IMEDIA_PLAYER);

IMEDIA_PLAYER的值爲:

  private final static String IMEDIA_PLAYER = "android.media.IMediaPlayer";

當我將這句加上測試後, 果然沒問題了, 調用成功. 這句代碼的意思是標識遠程服務的名稱. 不然不知道去啓動哪個服務來操作.

其實MediaPlayer中有提供方法來獲取Parcel對象, 但是此方法也是隱藏的:

    public Parcel newRequest() {
        Parcel parcel = Parcel.obtain();
        parcel.writeInterfaceToken(IMEDIA_PLAYER);
        return parcel;
    }

這個方法的內部也是調用了writeInterfaceToken接口.

3.其他
如果可以查看源碼, 其實每個方法的使用源碼中都有對應的test例子, 例如現在說的invoke方法, 在源碼中有 MediaPlayerInvokeTest.java, 這裏面介紹瞭如何使用invoke接口, 下面是這個類的內容:

    // Tests for the invoke method in the MediaPlayer.
    public class MediaPlayerInvokeTest extends
            ActivityInstrumentationTestCase2<MediaFrameworkTest> {
        private static final String TAG = "MediaPlayerInvokeTest";
        private MediaPlayer mPlayer;
        private Random rnd;

        public MediaPlayerInvokeTest() {
            super("com.android.mediaframeworktest", MediaFrameworkTest.class);
            rnd = new Random(Calendar.getInstance().getTimeInMillis());
        }

        @Override
        protected void setUp() throws Exception {
            super.setUp();
            mPlayer = new MediaPlayer();
        }

        @Override
        protected void tearDown() throws Exception {
            mPlayer.release();
            super.tearDown();
        }

        // Generate a random number, sends it to the ping test player.
        @Suppress
        @MediumTest
        public void testPing() throws Exception {
            mPlayer.setDataSource("test:invoke_mock_media_player.so?url=ping");

            Parcel request = mPlayer.newRequest();
            Parcel reply = Parcel.obtain();

            int val = rnd.nextInt();
            request.writeInt(val);
            mPlayer.invoke(request, reply);
            assertEquals(val, reply.readInt());
        }
    }

這次的收穫就是要學會善於查看android源碼.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章