本博客将会介绍动态代理在Android应用中的一种使用场景
代理模式
代理模式的作用是为其它对象提供一种代理以控制对这个对象的访问。比如用户调用了一个“吃饭”的方法,如果不依靠代理,用户可能自己拿碗饭吃就行,而如果通过代理的话,可能连碗都不需要用户自己拿,用户只需要张开嘴,代理来喂就行了,需要注意的是,这里代理除了负责拿碗和喂饭外还可以做其他的任何事情,比如说帮你把饭吹凉一些,或者担心你的体重而偷偷帮你倒掉了一半的饭,又或者是往饭里加点什么奇奇怪怪的东西,谁知道呢,这就是代理干的活。实际上在java里面也提供了代理这一神奇的模式,而且还分为静态和动态两种,两者的区别是静态代理的结构在程序运行前就已经安排好了的,而动态代理则是在程序运行过程中指定的,本文所采用的就是动态代理方法。
镜像翻转
在之前的一篇博客【Android】android镜像翻转 中分析过如何对一个View进行镜像翻转,也就是实现如下的效果:
镜像水平翻转前后效果
对于一般的View而言,直接用view.setScaleY(-1)即可达到这样的效果,而且针对自定义SurfaceView,也可以使用
canvas.scale(-1,1,canvas.getWidth()/2,canvas.getHeight()/2)
来进行翻转。但是如果面对的是第三方SurfaceView,且无法直接获取到SurfaceView用于绘制的canvas对象的话。比如上面左图实现代码如下。
public class TestSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
public TestSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
getHolder().addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 获取canvas
Canvas canvas = holder.lockCanvas();
canvas.drawColor(Color.rgb(220,220,220));
Paint paint = new Paint();
paint.setTextSize(60);
// 绘制文字
canvas.drawText("Hello, this is SurfaceView",200,600,paint);
// 绘制圆
canvas.drawCircle(300,800,100,paint);
// 显示
holder.unlockCanvasAndPost(canvas);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {}
}
假设TestSurfaceView在第三方Jar包中,且无法对其进行修改。那么该如何对这个界面进行镜像翻转呢,下面给出通过动态代理实现的方案
源码分析
结合上面的代码,我们知道,SurfaceView的绘制过程如下:
SurfaceView绘制流程
通常调用代码如下:
// 获取canvas画布
Canvas canvas = holder.lockCanvas();
//绘制内容
... ...
// 解锁画布,显示画布内容
holder.unlockCanvasAndPost(canvas);
holder是什么呢,我们可以在SurfaceView的源码里找到SurfaceHolder的实例mSurfaceHolder以及实现。
public class SurfaceView extends View {
......
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
private static final String LOG_TAG = "SurfaceHolder";
@Override
public boolean isCreating() {
return mIsCreating;
}
......
@Override
public Canvas lockCanvas() {
return internalLockCanvas(null);
}
@Override
public Canvas lockCanvas(Rect inOutDirty) {
return internalLockCanvas(inOutDirty);
}
......
};
}
在源码里,我们可以看到mSurfaceHolder里实现了lockCanvas方法,并且返回了Canvas,这里就是突破口。
代理实现
代理的目标是要实现对surfaceholder里的lockCanvas方法进行监控,并为其返回值添加一定的操作,也就是将原先的流程改为如下结构:
动态代理添加访问控制
首先需要实现的是代理处理器,其代码如下:
public class TestInvocation implements InvocationHandler {
Object mObject ;
public TestInvocation(Object object) {
mObject = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 截取lockCanvas方法调用
if ("lockCanvas".equals(method.getName())) {
// lockCanvas方法返回值是canvas画布
Canvas canvas = (Canvas) method.invoke(mObject,args);
// 添加镜像
canvas.scale(-1,1,canvas.getWidth()/2,canvas.getHeight()/2);
return canvas;
}
return method.invoke(mObject,args);
}
}
代理处理器做的是在invoke的地方将lockCanvas方法过滤了出来,然后执行lockCanvas方法,并获返回值,这个返回值就是canvas对象,这个canvas是SurfaceView将要在上面作画的画布,所以这里我们可以通过代理事先为其添加镜像翻转的效果,添加完成之后之后返回给正常流程来继续执行。
完成代理处理器之后就可以为surfaceHolder添加动态代理,这里需要注意的是surfaceHolder在SurfaceView中,所需先取出来,再为之添加代理,代码如下:
// 获取 surfaceView中的 surfaceHolder
SurfaceHolder mSurfaceHolder = mTestSurfaceView.getHolder();
// 创建代理接口的实现
TestInvocation testInvocation = new TestInvocation(mSurfaceHolder);
// 为 mSurfaceHolder 添加动态代理,并获取添加代理之后的 newSurfaceHolder
SurfaceHolder newSurfaceHolder = (SurfaceHolder) Proxy.newProxyInstance(mSurfaceHolder.getClass().getClassLoader(),mSurfaceHolder.getClass().getInterfaces(),testInvocation);
新生成的newSurfaceHolder就是已经添加上动态代理的surfaceHolder,因为在SurfaceView中SurfaceHolder是私有属性,无法直接替换,所以这里需要借助反射机制来讲newSurfaceHolder替换掉SurfaceView中原先的mSurfaceHolder,代码如下:
// 获取mSurfaceHolder的field
Field fieldHolder = SurfaceView.class.getDeclaredField("mSurfaceHolder");
// 更改为可访问权限
fieldHolder.setAccessible(true);
// 用添加代理后的 newSurfaceHolder 替换 mSurfaceHolder
fieldHolder.set(mTestSurfaceView,newSurfaceHolder);
添加动态代理之后,TestSurfaceView中调用holder的lockCanvas方法所获取到的canvas都是经过TestInvocation转置的canvas,从而实现了这个奇怪的需求,也就是实现右侧图片的效果。
源码下载