Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 【Android】Android動態代理為SurfaceHolder添加Hook

【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動態代理實踐

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved