有个场景就是一个页面里有多个VideoView播放视频,然后每个视频都有一个音量值,但是VideoView并不支持直接设置音量,而是要通过调节系统音量来实现,那么这样的话,就不能实现为每个视频独立调节音量了我们知道MediaPlayer+SurfaceView也能实现视频的播放,并且MediaPlayer是可以直接通过setVolume来调节视频音量的,但是因为这里已经用了VideoView实现了播放,再改的话就太浪费时间和人力了。想到VideoView是继承SurfaceView的,那会不会VideoView的视频播放控制是由MediaPlayer实现的呢,查看VideoView源码:


try {
    mMediaPlayer = new MediaPlayer();
    // TODO: create SubtitleController in MediaPlayer, but we need
    // a context for the subtitle renderers
    final Context context = getContext();
    final SubtitleController controller = new SubtitleController(
            context, mMediaPlayer.getMediaTimeProvider(), mMediaPlayer);
    controller.registerRenderer(new WebVttRenderer(context));
    controller.registerRenderer(new TtmlRenderer(context));
    controller.registerRenderer(new Cea708CaptionRenderer(context));
    controller.registerRenderer(new ClosedCaptionRenderer(context));
    mMediaPlayer.setSubtitleAnchor(controller, this);

    if (mAudioSession != 0) {
        mMediaPlayer.setAudioSessionId(mAudioSession);
    } else {
        mAudioSession = mMediaPlayer.getAudioSessionId();
    }
    mMediaPlayer.setOnPreparedListener(mPreparedListener);
    mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
    mMediaPlayer.setOnCompletionListener(mCompletionListener);
    mMediaPlayer.setOnErrorListener(mErrorListener);
    mMediaPlayer.setOnInfoListener(mInfoListener);
    mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
    mCurrentBufferPercentage = 0;
    mMediaPlayer.setDataSource(mContext, mUri, mHeaders);
    mMediaPlayer.setDisplay(mSurfaceHolder);
    mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mMediaPlayer.setScreenOnWhilePlaying(true);
    mMediaPlayer.prepareAsync();


果然如此,VideoView仅仅只是封装了MediaPlayer和SurfaceView。那么既然这样的话就好办了,只需要获取到VideoView里面的MediaPlayer就可以单独修改这个视频的声音大小了,但是发现MediaPlayer是私有的并且VideoView并没有留出方法给外层调用,那么只能通过反射了。


/**
 * @param volume 音量大小
 * @param object VideoView实例
 * */
public void setVolume(float volume,Object object) {
    try {
        Class<?> forName = Class.forName("android.widget.VideoView");
        Field field = forName.getDeclaredField("mMediaPlayer");
        field.setAccessible(true);
        MediaPlayer mMediaPlayer = (MediaPlayer) field.get(object);
        mMediaPlayer.setVolume(volume, volume);
    } catch (Exception e) {
    }
}


据此思想,当遇到系统Api限制的问题时,我们不妨另辟蹊径,通过反射解除限制,从而解决问题,比如下面这个例子:

某些情况下我们想在ViewPager滑动的时候才创建页面,并不希望ViewPager给我们缓存页面,所以我们先找到ViewPager里面设置缓存个数相关的参数和方法。


private static final int DEFAULT_OFFSCREEN_PAGES = 1;
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;
public void setOffscreenPageLimit(int limit) {
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to " +
                DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }
    if (limit != mOffscreenPageLimit) {
        mOffscreenPageLimit = limit;
        populate();
    }
}


 可以看到ViewPager限制了传入缓存页数不能小于1,所以无论如何ViewPager都会给我们缓存一个以上的页面。没关系,同样的道理,我们可以修改这个限制,这里我们直接通过反射修改mOffscreenPageLimit的值来实现。


try {
    Class<?> forName = Class.forName("android.support.v4.view.ViewPager");
    Field defauleField = forName.getDeclaredField("mOffscreenPageLimit");
    defauleField.setAccessible(true);
    defauleField.set(0,viewPager);
} catch (Exception e) {
}


通过这两个例子主要是想告诉大家不要被系统Api所限制,当发现SDK里没提供你所需要的方法时候,别放弃,细心看看源码,也许会柳暗花明又一村,代码是死的,人是活的,为达目的,可以不择手段,正常路径走不通,那么可以通过一些非正常的方式来达到目的。