【Android】Android动态代理为SurfaceHolder添加Hook

简介: 本博客将会介绍动态代理在Android应用中的一种使用场景 代理模式 代理模式的作用是为其它对象提供一种代理以控制对这个对象的访问。比如用户调用了一个“吃饭”的方法,如果不依靠代理,用户可能自己拿碗饭吃就行,而如果通过代理的话,可能连碗都不需要用户自己拿,用户只需要张开嘴,代理来喂就行了,需要注意的是,这里代理除了负责拿碗和喂饭外还可以做其他的任何事情,比如说帮你把饭吹凉一些,或者

本博客将会介绍动态代理在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,从而实现了这个奇怪的需求,也就是实现右侧图片的效果。


源码下载

整个工程的源码如下

Android动态代理实践


目录
相关文章
|
监控 JavaScript 前端开发
|
JavaScript Shell Android开发
安装使用Frida在Android上进行hook
我们对Android应用进行hook最常用的就是Xposed,它相对来说更加完善,而且有强大的社区和丰富的插件。而Frida则于Xposed不同,它是一款轻量级的Hook框架,可用于多平台,相同的是它依然需要root环境。本文就以Android为例来详细说说如何安装并使用它。
504 0
|
2月前
|
Android开发
安卓逆向 -- Hook多个dex文件
安卓逆向 -- Hook多个dex文件
19 1
|
3月前
|
算法 安全 Android开发
安卓逆向 -- Frida Hook某车_sign算法分析
安卓逆向 -- Frida Hook某车_sign算法分析
77 0
|
3月前
|
Shell Android开发 数据安全/隐私保护
安卓逆向 -- Frida环境搭建(HOOK实例)
安卓逆向 -- Frida环境搭建(HOOK实例)
42 0
|
9月前
|
XML 存储 JavaScript
Android打造专有Hook第三篇,实战全量代码规范检查
目前的规范检查,我分为了全量文件检查和增量文件检查,基本上和Git提交保持一致,在实际的业务中,开发者可以动态修改配置文件参数gitIncrement,来切换是增量还是全量,增量和全量有一些是共通的地方,接下来的代码中会陈述。
|
6月前
|
算法 安全 Android开发
安卓逆向 -- Frida Hook某车_sign算法分析
安卓逆向 -- Frida Hook某车_sign算法分析
34 0
|
6月前
|
Shell Android开发 数据安全/隐私保护
安卓逆向 -- Frida环境搭建(HOOK实例)
安卓逆向 -- Frida环境搭建(HOOK实例)
92 0
|
9月前
|
Java 开发工具 Android开发
Android打造专有Hook第四篇,实战增量代码规范检查
在全量文件检查中,我们只需要得到Git提交的文件,然后逐一针对文件内容,获取,做相关的逻辑检查即可,但是增量就不能这样搞了,我们都知道,每次增量的提交,是没有规律可言的,也许增量中只有一行,也许有百行,而且增量的代码位置,有可能是在方法中,有可能是在资源中,所以针对增量代码的检查,是必须要做出取舍的,因为,通过一行,或者几行,很难达到一定的规范标准
|
9月前
|
JavaScript 前端开发 Shell
Android打造专有hook第二篇,走进规范第一步
关于Git的Hooks,其实很常见,位置存在于 /.git/hooks 目录下,都是一些 shell 脚本,然后在对应的钩子中执行这些脚本就行了,比如下图中,这是一个还没有配置 Git Hooks 的仓库,默认会有很多.sample结尾的文件,这些都是示例文件,比如我们常见的,commit,push等。
122 0