JNI开发----NDK开发第一步

NDK开发流程

不同版本的Android Studio可能对于NDK的配置略有不同,请知悉。

步骤1:安装配置NDK

(1)打开AS的Project Structure目录,如下:

image

点击Android NDK location 下方的Download来下载NDK,下载好的NDK会自动安装在SDK的目录里面。

image

(2)配置path环境变量,将NDK的根目录配置到环境变量的path里面,如下是我的配置

C:\AndroidDevelop\sdk\ndk-bundle

步骤2:给AS配置关联NDK

(1)在项目的local.properties中添加配置ndk的根目录,默认情况下,经过步骤1的配置已经在local.properties中添加了ndk的根目录,如果没有手动添加,下面是我的配置

ndk.dir=C\:\\AndroidDevelop\\sdk\\ndk-bundle

(2)gradle.properties中添加配置,目的是兼容老的NDK。

android.useDeprecatedNdk=true

步骤3:编写native方法

我是在新建的类JniKit中创建的native方法

public class JniKit {

    /**
     * 定义native方法
     * 调用C代码对应的方法
     * @return
     */
    public native String sayHello();
}

步骤4:定义对应的JNI

(1)通过命令行,生成native方法对应的JNI函数声明头文件:

在控制台或者Android studio自带的控制台使用javah -jni的命令,利用class文件生成.h头文件,这里要注意 javah -jni 在使用时有可能会出问题,比如找不到app.activity 找不到类文件,最后总结出来的就是严格按照以下来写:

image

(2)在项目jni文件夹下创建一个对应的函数文件:Hello.c

#include <com_bruce_chang_testndk_JniKit.h>

JNIEXPORT jstring JNICALL Java_com_bruce_chang_testndk_JniKit_sayHello
(JNIEnv *env, jobject jobj) {
    return (*env)->NewStringUTF(env, "Hello from C");
}

(3)在jni文件夹下创建一个空的C文件: empty.c。说明: 这是AS的bug, 必须至少2个C文件才能通过编译。

步骤5:指定编译的不同CPU。

在app的build.gradle的defaultConfig下添加如下代码:

defaultConfig {
        ndk{
            moduleName "Hello" //so文件: lib+moduleName+.so
            abiFilters "armeabi", "armeabi-v7a", "x86" //cpu的类型
        }
    }

步骤6:编译生成不同平台下的动态链接文件

(1)执行rebuild, 生成so文件

(2)so文件目录: build\intermediates\ndk\debug\lib…..

步骤7:调用native方法

(1)在native方法所在的类JniKit.java中加载so文件

 static {
        System.loadLibrary("Hello");
    }

(2)在Activity中调用native方法:

 String s = new JniKit().sayHello();
tvNdk.setText(s);

以上就是NDK从下载到本地配置,以及实现简单一个测试程序的过程。

结果:显示C程序中返回的字符串”Hello from C”

image

总结:使用NDK的大致开发流程主要包含下面几步

  1. 在java里面写native代码
  2. 在main目录下创建jni目录,写C代码,生成头文件
  3. 配置动态链接库的名称
  4. 加载动态链接库
  5. 代码具体调用

注意,使用c代码返回字符串类型的数据的时候,字符串类型是jstring,这里涉及到了java数据类型和C语言数据类型与本地JNI的一个对应关心,如下:

java类型 JNI别名 C类型
boolean jboolean unsigned char
byte jbyte signed char
char jchar unsigned char
short jshort short
int jint int
long jlong long long
float jfloat float
double jdouble double

详解NDK开发

Java调用C函数

重温一下生成头文件的方法

E:\GithubDownload\TestNDK\javacallc\src\main>javah -d jni -classpath C:\AndroidDevelop\sdk\platforms\android-25\android.jar;E:\GithubDownload\TestNDK\javacallc\build\intermediates\classes\debug bruce.chang.javacallc.Jni

下面四个小程序用到的java类

public class Jni {

    static {
        System.loadLibrary("javacallc");
    }

    /**
     * 求两个数字的和
     *
     * @param x
     * @param y
     * @return
     */
    public native int sum(int x, int y);

    /**
     * 将两个字符串拼接后返回
     *
     * @param s
     * @return
     */
    public native String sayHello(String s);

    /**
     * 将数组中的每个元素增加10
     *
     * @param intArray
     * @return
     */
    public native int[] increaseArrayEles(int[] intArray);

    /**
     *  应用: 检查密码是否正确, 如果正确返回200, 否则返回400
     "123456"
     * @param pwd
     * @return
     */
    public native int checkPwd(String pwd);
}

测试1:将传入的两个int值相加并返回

/**
    * 求两个数字的和
    *
    * @param x
    * @param y
    * @return
    */
JNIEXPORT jint JNICALL Java_bruce_chang_javacallc_Jni_sum
        (JNIEnv *env, jobject jobj, jint int1, jint int2) {
    //jint可以直接进行算术运算
    int sum = int1 + int2;
    //可直接将int类型数据作为jint返回
    return sum;
}

测试2:将两个字符串拼接后返回

/**
     * 将两个字符串拼接后返回
     *
     * @param s  I am BruceChang
     *        c  I am SWJ
     * @return  I am BruceChang add I am SWJ
     */
JNIEXPORT jstring JNICALL Java_bruce_chang_javacallc_Jni_sayHello
        (JNIEnv * env, jobject jobj, jstring js){
    //将jstring类型的js转换为char*类型数据
    char * fromJava = _JString2CStr(env,js);
    //c
    char * fromC = "add I am SWJ";
    //将拼接两个char*类型字符串拼接在第一个上
    strcat(fromJava, fromC);
    //将结果转换为jstring类型返回
    return (*env)->NewStringUTF(env, fromJava);
}

这里面用到一个函数_JString2CStr 将字符串转换成字符指针


/**
 * 工具函数
 * 把一个jstring转换成一个c语言的char* 类型.
 */
char* _JString2CStr(JNIEnv* env, jstring jstr) {

    char* rtn;
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");
    jstring strencode = (*env)->NewStringUTF(env,"GB2312");
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); // String .getByte("GB2312");
    jsize alen = (*env)->GetArrayLength(env, barr);
    jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
    if(alen > 0) {
        rtn = (char*)malloc(alen+1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen]=0;
    }
    (*env)->ReleaseByteArrayElements(env, barr, ba,0);
    return rtn;
}

测试3:将数组的每个元素增加10

/**
    * 将数组中的每个元素增加10
    *
    * @param intArray
    * @return
    */
JNIEXPORT jintArray JNICALL Java_bruce_chang_javacallc_Jni_increaseArrayEles
        (JNIEnv * env, jobject jobj, jintArray arr){

    //1\. 得到数组的长度
    //jsize       (*GetArrayLength)(JNIEnv*, jarray);
    jsize length = (*env)->GetArrayLength(env, arr);
    //2\. 得到数组
    //jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
    jint* array = (*env)->GetIntArrayElements(env, arr, JNI_FALSE);
    //3\. 遍历数组, 并将每个元素+10
    int i;
    for(i=0;i<length;i++) {
        *(array+i) += 10;
    }
    //4\. 返回数组
    return arr;
}

测试4:检查密码是否正确

/**
 *  应用: 检查密码是否正确, 如果正确返回200, 否则返回400
 "123456"
 * @param pwd
 * @return
 */
JNIEXPORT jint JNICALL Java_bruce_chang_javacallc_Jni_checkPwd
        (JNIEnv * env, jobject jobj, jstring string){
    //1\. 将jString转换为char*
    char* cs = _JString2CStr(env, string);
    char* pwd = "123456";
    //2\. 比较两个字符串是否相等
    int result = strcmp(cs, pwd);
    //3\. 根据比较的结果返回不同的值
    if(result==0) {
        return 200;
    }
    return 400;
}

结果:

image

测试2,字符串的拼接,这个结果在不同的手机上显示效果还不太一样,在oppo r7上运行结果就是1、2、3、4、5,这个问题可能是程序不兼容导致的。

C调用Java方法

C调用java方法和java调用C的方法步骤一样,但C回调java方法的核心思想是利用反射的原理,其次还需要用到java中的javap -s命令来显示所有方法的签名信息,这个非常的重要,否则无法得到java类中的方法,从而也就无法实现C函数调用java方法。

显示方法签名的命令

E:\>cd E:\GithubDownload\TestNDK\ccalljava\src\main

E:\GithubDownload\TestNDK\ccalljava\src\main>javah -d jni -classpath C:\AndroidDevelop\sdk\platforms\android-23\android.jar;E:\GithubDownload\TestNDK\ccalljava\build\intermediates\classes\debug com.bruce.chang.ccalljava.Jni

E:\GithubDownload\TestNDK\ccalljava\src\main>javap -s -classpath C:\AndroidDevelop\sdk\platforms\android-23\android.jar;E:\GithubDownload\TestNDK\ccalljava\build\intermediates\classes\debug com.bruce.chang.ccalljava.Jni
Compiled from "Jni.java"
public class com.bruce.chang.ccalljava.Jni {
  public com.bruce.chang.ccalljava.Jni();
    descriptor: ()V

  public native void callbackHelloFromJava();
    descriptor: ()V

  public native void callbackAdd();
    descriptor: ()V

  public native void callbackPrintString();
    descriptor: ()V

  public native void callbackSayHello();
    descriptor: ()V

  public void helloFromJava();
    descriptor: ()V

  public int add(int, int);
    descriptor: (II)I

  public void printString(java.lang.String);
    descriptor: (Ljava/lang/String;)V

  public static void sayHello(java.lang.String);
    descriptor: (Ljava/lang/String;)V

  static {};
    descriptor: ()V
}

E:\GithubDownload\TestNDK\ccalljava\src\main>

一般调用步骤

  1. 加载类得到class对象
  2. 得到对应方法的Method对象
  3. 创建类对象
  4. 调用方法

测试1:回调一般方法(无参无返回)

java端方法

   public native void callbackHelloFromJava();

java端被回调方法

   public void helloFromJava() {
        Log.e("TAG", "helloFromJava()");
    }

C端函数

  void Java_com_bruce_chang_ccalljava_Jni_callbackHelloFromJava
        (JNIEnv * env, jobject obj) {

    //1\. 加载类得到jclass对象:
    //jclass      (*FindClass)(JNIEnv*, const char*);
    jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni");
    //2\. 得到对应方法的Method对象 : GetMethodId()
    //jmethodID   (*GetMethodID)(JNIEnv*, jclass, const char*, const char*)
    jmethodID method = (*env)->GetMethodID(env, jc, "helloFromJava", "()V");
    //3\. 创建类对象
    //jobject     (*AllocObject)(JNIEnv*, jclass);
    jobject obj2 = (*env)->AllocObject(env, jc);
    //4\. 调用方法
    (*env)->CallVoidMethod(env, obj2, method);
}

测试2:回调带int参数方法

java端方法

   public native void callbackAdd();

java端被回调方法

    public int add(int x, int y) {
        Log.e("TAG", "add() x=" + x + " y=" + y);
        return x + y;
    }

C端函数

  void  Java_com_bruce_chang_ccalljava_Jni_callbackAdd(JNIEnv * env, jobject obj){
     //1. 加载类得到class对象
     jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni");
     //2. 得到对应方法的Method对象
     jmethodID method = (*env)->GetMethodID(env, jc, "add", "(II)I");
     //3. 创建类对象
     jobject obj2 = (*env)->AllocObject(env, jc);
     //4. 调用方法
     (*env)->CallIntMethod(env, obj2, method, 3, 4);
 }

测试3:回调带String参数方法

java端方法

    public native void callbackPrintString();

java端被回调方法

   public void printString(String s) {
        Log.e("TAG", "C中输入的:" + s);
    }

C端函数

  void  Java_com_bruce_chang_ccalljava_Jni_callbackPrintString(JNIEnv * env, jobject obj){
    //1. 加载类得到class对象
    jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni");
    //2. 得到对应方法的Method对象
    jmethodID method = (*env)->GetMethodID(env, jc, "printString", "(Ljava/lang/String;)V");
    //3. 创建类对象
    jobject obj2 = (*env)->AllocObject(env, jc);
    //4. 调用方法
    jstring js = (*env)->NewStringUTF(env, "I from C");
    (*env)->CallVoidMethod(env, obj2, method, js);
}

测试4:回调静态方法

java端方法

   public native void callbackSayHello();

java端被回调方法

   public static void sayHello(String s) {
        Log.e("TAG", "我是java代码中的JNI."
                + "java中的sayHello(String s)静态方法,我被C调用了:" + s);
    }

C端函数

  void  Java_com_bruce_chang_ccalljava_Jni_callbackSayHello(JNIEnv * env, jobject obj){
    //1. 加载类得到class对象
    jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/Jni");
    //2. 得到对应方法的Method对象
    jmethodID method = (*env)->GetStaticMethodID(env, jc, "sayHello", "(Ljava/lang/String;)V");
    //3. 调用方法
    jstring js = (*env)->NewStringUTF(env, "I from C");
    (*env)->CallStaticVoidMethod(env, jc, method, js);
}

结果:

image

小应用:回调更新UI的方法

java端方法,在MainActivity中写

    /**
     * 让C代码调用MainActivity中的showToast方法
     */
    public native void callbackShowToast();

java端被回调方法

    public void showToast() {
        Toast.makeText(MainActivity.this, "this is a toast!!!", Toast.LENGTH_SHORT).show();
    }

C端函数

 /**
 * 让C代码调用MainActivity中的showToast方法
 * obj  谁调用了当前Java_com_bruce_chang_ccalljava_Jni_callbackShowToast对应的java方法就是就是谁的实例,
 * 该方法如果放在Jni这个类中就是是Jni.this,如果放在MainActivity中就是MainActivity.this
 */
void  Java_com_bruce_chang_ccalljava_MainActivity_callbackShowToast(JNIEnv * env, jobject obj){
    //1\. 加载类得到class对象
    jclass jc = (*env)->FindClass(env, "com/bruce/chang/ccalljava/MainActivity");
    //2\. 通过方法名和方法签名得到对应方法的Method对象
    jmethodID method = (*env)->GetMethodID(env, jc, "showToast", "()V");
    //3\. 创建类对象
   // jobject obj2 = (*env)->AllocObject(env, jc);
    //4\. 调用方法
    (*env)->CallVoidMethod(env, obj, method);
}

点击调用callbackShowToast方法

    // jni.callbackShowToast();不能调用jni中的callbackShowToast方法,这样会报空指针错误,因为发射得到的是MainActivity的类,
    // 并不是Activity,在执行showToast方法的时候就出错了
    MainActivity.this.callbackShowToast();

结果:

image

切记切记

通过本实例可以发现,通过C代码调用活动中的方法的时候,一定要在活动中调用native方法,否则在C回调的时候就会出现空指针的错误。

JNINativeInterface的相关函数指针

//功能: 加载类得到类对象
//返回: jni的class类型
jclass (*FindClass)(JNIEnv*, const char*)
//功能: 得到对应方法的Method对象
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*)
//功能: 创建对象
jobject (*AllocObject)(JNIEnv*, jclass)
//功能: 调用没有返回值的方法
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...)

//功能: 调用返回int值的方法
jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...)
//功能: 得到静态方法的method

jmethodID   (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*)
//功能: 调用静态方法
void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...)

//功能: 将C中的字符串转换为JNI中的jstring类型
//第二个: 被转换的字符串
jstring (*NewStringUTF)(JNIEnv*, const char*)

//功能: 将第二个字符串连接到第一个字符串上

string.h---char* strcat(char *, const char *);
//功能: 得到jni类型数组的长度(元素个数)

jsize (*GetArrayLength)(JNIEnv*, jarray);
//功能: 得到jni数组中所有元素的指针
//第二个: jni中的int数组   第三个: 是否返回一个复制的数组
jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
//参数为两个字符串
//第一个与第二个相等, 返回0    第一个大于第二个, 返回大于0   第一个小于第二个, 返回小于0
string.h---int strcmp(const char *, const char *)

NDK开发综合案例

案例1: 美图秀秀

步骤

  1. 创建工程MTXX

  2. 把对应的.so文件拷贝到java/main/jniLibs目录。。armeabi/libmtimage-jin.so

  3. 创建一个类叫做JNI.java并且要在com.mt.mtxx.image包下创建

  4. 加载动态链接库,System.loadLibrary(“mtimage-jni”)

  5. 写布局文件和实现各个效果的点击事件

  6. 处理图片相关的工作

    • 6.1把图片转换成矩阵(数组)
    • 6.2把数组传入给C代码处理
    • 6.3把处理好的数组重新生成图片
    • 6.4把图片显示

按照上面的步骤一步一步的来,布局文件非常简单,就不写出来了。

布局效果

image

java程序中调用处理

  1. 加载Jni程序
  JNI jni = new JNI();
  1. 高亮效果
 public void lomoHDR(View view) {

        //6.1,把图片转换成数组
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.fbb);
        //装图片的像数
        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
        /**
         * 参数
         pixels       接收位图颜色值的数组
         offset      写入到pixels[]中的第一个像素索引值
         stride       pixels[]中的行间距个数值(必须大于等于位图宽度)。可以为负数
         x             从位图中读取的第一个像素的x坐标值。
         y             从位图中读取的第一个像素的y坐标值
         width       从每一行中读取的像素宽度
         height   读取的行数
           异常
         */
        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        //6.2,把数组传入给C代码处理
        jni.StyleLomoHDR(pixels, bitmap.getWidth(), bitmap.getHeight());
        // 6.3,把处理好的数组重新生成图片
        bitmap = Bitmap.createBitmap(pixels, bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        // 6.4,把图片像数
        iv_icon.setImageBitmap(bitmap);

    }
  1. 黑白效果
public void lomoC(View view) {

        //6.1,把图片转换成数组
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.fbb);
        //装图片的像数
        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        //6.2,把数组传入给C代码处理
        jni.StyleLomoC(pixels, bitmap.getWidth(), bitmap.getHeight());
        // 6.3,把处理好的数组重新生成图片
        bitmap = Bitmap.createBitmap(pixels, bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        // 6.4,把图片像数
        iv_icon.setImageBitmap(bitmap);

    }
  1. 怀旧效果
public void lomoB(View view) {
        //6.1,把图片转换成数组
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.fbb);
        //装图片的像数
        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
        bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
        //6.2,把数组传入给C代码处理
        jni.StyleLomoB(pixels, bitmap.getWidth(), bitmap.getHeight());
        // 6.3,把处理好的数组重新生成图片
        bitmap = Bitmap.createBitmap(pixels, bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
        // 6.4,把图片像数
        iv_icon.setImageBitmap(bitmap);
    }
  1. 还原
 iv_icon.setImageResource(R.mipmap.fbb);

剩下的四种效果就不说明了,稍后看一下结果。

结果(只能在arm架构的处理器上运行,x86的处理器的手机是不能运行该程序的):

image

案例2: 锅炉压力系统

步骤

  1. 创建一个module名字叫做GuoLu,
    1.1 在MainActivity中写得到锅炉压力值的native方法—getPressure();
    1.2 在build.gradle文件中配置生成.so文件的名称
defaultConfig {
        applicationId "com.bruce.chang.guolu"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "GuoLu" //so文件: lib+moduleName+.so
            abiFilters "armeabi", "armeabi-v7a", "x86" //cpu的类型
        }
    }
  1. 分析实现的原理
  2. 在C代码中写锅炉的压力值,返回给java
/**
 得到锅炉的压力值
*/
int pressure = 20;
int getPressure(){
    int incease = rand()%20;
    pressure += incease;
    return pressure;
}
  1. 在视图中动态绘制

GuoLu.c代码

//
// Created by Administrator on 2016/4/19.
//
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
/**
 得到锅炉的压力值
*/
int pressure = 20;
int getPressure(){
    int incease = rand()%20;
    pressure += incease;
    return pressure;
}

/**
 * 从锅炉感应器中得到锅炉压力值
 */
jint  Java_com_bruce_chang_guolu_MainActivity_getPressure(JNIEnv *env, jobject instance) {
    int pressur = getPressure();
    return pressur;
}

MainActivity.java中调用

public class MainActivity extends AppCompatActivity {
    {
        System.loadLibrary("GuoLu");
    }
    PressureView pressureView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        pressureView = new PressureView(MainActivity.this);
        setContentView(pressureView);
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    SystemClock.sleep(500);
                    int pressure = Math.abs(getPressure());
                    pressureView.setPressure(pressure);
                    //如果压力大于220
                    if (pressure > 220) {
                        break;
                    }
                }
            }
        }).start();
    }

    /**
     * native代码
     * 调用C代码中的对应方法
     *
     * @return
     */
    public native int getPressure();
}

自定义view—PressureView来控制显示的过程

public class PressureView extends View {
    private int pressure;
    private Paint mPaint;

    public PressureView(Context context) {
        super(context);
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(50);
    }

    public void setPressure(int pressure) {
        this.pressure = pressure;
//        invalidate();//在主线程中运行
        postInvalidate();//ondraw()执行
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //1:如果压力值大于220,就绘制文本,显示锅炉爆炸了,快跑
        if (pressure > 220) {
            mPaint.setColor(Color.RED);
            canvas.drawText("要爆炸了,快跑炮", 10, getHeight() / 2, mPaint);
        } else {
            //2:正常和提示的情况
            //设置背景颜色为灰色
            mPaint.setColor(Color.GRAY);
            canvas.drawRect(10, 10, 60, 260, mPaint);
            canvas.drawText("pressure=="+pressure, 10, getHeight() / 2, mPaint);
            //2.1  如果是小于200正常显示并且设置画笔颜色绿色
            if (pressure < 200) {
                mPaint.setColor(Color.GREEN);
                canvas.drawRect(10, 260-pressure, 60, 260, mPaint);
            } else if (pressure>200){
                //2.2  如果是大于200警示显示给看护者,并且设置画笔颜色红色
                mPaint.setColor(Color.YELLOW);
                canvas.drawRect(10, 260-pressure, 60, 260, mPaint);
            }
        }
    }
}

结果(在x86的模拟器上运行)

image

以上就是自己对NDK的学习,以后工作中具体使用吧。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • 一、NDK产生的背景 Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于J...
    Ten_Minutes阅读 3,502评论 1 27
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,077评论 25 707
  • 注:原文地址 紧接上篇:Android NDK开发:JNI基础篇 | cfanr,这篇主要介绍 JNI Nativ...
    cfanr阅读 13,068评论 11 56
  • 观察:下午6点老婆的老表从汉川赶到武昌介绍微商,老婆没有主动在车站去接;老婆和我听了一个小时,我们又去凯德1818...
    陈诚chen阅读 78评论 0 0
  • 最近,因打算购买大器件,所以查看自己账上资金,突然想起来自己还有一笔基金放在证券公司,已经记不得任何的信息了。 回...
    热带鱼小井阅读 342评论 0 0