Android Native code 的绘图方法2

本文转载自http://www.codemud.net/~thinker/GinGin_CGI.py/show_id_doc/404 对原作者表示感谢


大部分 Android应用程式,应该都是用 Java Code 完成所有的工作,包括绘图。但 有些情況下,你会希望绘图有更快的反应;例如 game ,这时 native code 可能是一个选择。

在 Android 上,有一个graphic engine ,称为 Skia 。 Skia 的功能约等于 Cairo ,功能上相似,但 Skia 的支援沒 Cairo 广。其实, Android 的 Java Code 都是透过 Skia 进行绘图,而 Skia 主要的 class type 是 SkCanvas ,所的有绘图功能都建构在这个 class 上。

因此,如果我們能在 native code 取得 Android 所建立的 SkCanvas ,能直接使用 Skia 对画面做输出。

在 Android 的 UI 设计里,每一个 UI component 都是一个view ;例如: button 、 label 等等,全是 view 。当Android 要画一个 view 时,会调用 view 的 onDraw() 画出 component 的外观。而 Android会将一个 android.graphics.Canvas type 的物件,当成参数给 onDraw() 。 onDraw() 就在這个 canvas 上输出 component 的外观,例如画一个 button 。這個 canvas 其实就对应到一个 SkCanvas ,

我們只要在这个 canvas 上作画,就等于画到画面上的一个区域。

Native code

Android 是透过 JNI 调用 native code的 ,于是我们在 Java side 定义了一个 View ,使该 View 透过 JNI 把 canvas 传送給我们的native code ,以便在 native code 里绘图。

package com.example;

import Android.view.View;

import Android.graphics.Canvas;


class myview extends View {

public void onDraw(Canvas canvas) {

nativedraw(canvas);

}

native void nativedraw(Canvas canvas);

static {

System.loadLibrary("mynative");

}

}


在这个 class 里,我们将 canvas 透过 JNI 传给 native code ,接下來我們定义 JNI 的 native function 。

#include <SkCanvas.h>


extern "C" {


#include <jni.h>


void

Java_com_example_myview_nativedraw(JNIEnv *env, jobject thiz, jobject obj) {

jclass cls;

jfieldID fid;

SkCanvas *canvas;


cls = env->GetObjectClass(obj);

fid = env->GetFieldID(cls, "mNativeCanvas", "I");

canvas = (SkCanvas *)env->GetIntField(obj, fid);

/* ..... draw on canvas .... */

}


}


obj這個参数就是 Java side 的 canvas ,而 SkCanvas 是放在 Java side canvas 的 mNativeCanvas 栏位,是一個整数。

这个整数其实就是 SkCanvas 的位址,我们直接 casting 成 SkCanvas 的 pointer ,如此我们就取得 SkCanvas ,

可以透过Skia 介面直接作图作圖。

注意!! JNI 是定义在 C 語言上,而 Skia 是 C++ 的 library 。因此,我們使用 C++写 native code ,但在 JNI 的 function 定义时,必须用 extern "C" 包起來,告知 C++ compiler 這個 function 是使用 C的调用规则。

动画

如果你要在 native code 里作动画,那么就必需要能更新画面。如果只是把 canvas 的位址存起來,不断地对 canvas 做更新,你会发觉画面并不会跟着更新。正确的作法是要调用 View 的 invalidate() function ,让Android 進行更新的动作。

当 View.invalidate() 被调用之后, Android framework 会调用 View 的 onDraw(),这时调用 Native code 进行画图,更新的結果才会显示在画面上。因此,进行动画时,Java side 必需定期的呼叫 View.invalidate() ,以進行画面的更新。

SurfaceView

SurfaceView 也是 View ,但有独立的 buffer ,称为surface 。由于这个 buffer 不需透过Androidframework 作画面更新,

能直接对应到画面上的区块,提供更好的效能。(其实还是间接更新到画面上,但少了 Android framework 這一层,

而且可透过hardware 加速。加速的功能視平台而定。)

当你需要更好的效能时, 继承 SurfaceView 也是一個方式。透過 SurfaceView 时, canvas 不再是透過 onDraw() 取得了。

你能通过调用 SurfaceView.getHolder() 取得 surface holder ,而 SurfaceHolder.lockCanvas() 会传回 SkCanvas 。

 Native code 可以在這個 SkCanvas 上作画,然后调用 SurfaceHolder.unlockCanvasAndPost() ,將內容更新到画面上。

每次更新画面前,都要通过 SurfaceHolder.lockCanvas() 取得一个新的 SkCanvas,在這個 canvas 上作画才行。作完后

都必需调用 SurfaceHolder.unlockCanvasAndPost() 进行更新。如果才能正確的更新畫面。

另外, SurfaceHolder.lockCanvas() 传回的 SkCanvas 并不会保存上次绘图的內容。請特別注意。

SurfaceHolder.lockCanvas() 可以传入一个参数,指定要更新的部分。通过指定特定区域,我只需重绘该部分,而不需更新整个画面,并改善更新的效能。

結語

研究這個部分,是因为现在正在 porting MadButterfly 到 Android 上。在此順手記下一些心得。

ps:看到 canvas = (SkCanvas *)env->GetIntField(obj, fid);  豁然开朗。发现自己 思考的 对Java 保存指针的 做法是 大家 广泛使用的。

之前一直很疑惑怎么处理java的native代码中的指针 之后 想到将指针地址传到 java代码层面的方法。本人愚钝 ,且 见识有限,一直以为只是个人权宜之计。原 来  是通用做法。

该文章更是解决了我一直想的怎么将java层面的 canvas装载到native code 的问题。 苦于一直没有 时间和状态对

android source code以及skia 进行研究。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值