Android中NDK开发基础

简介
Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”。
众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序。
NDK包括了:
从C / C++生成原生代码库所需要的工具和build files。
将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files ,即.apk文件)中。
支持所有未来Android平台的一系列原生系统头文件和库
为何要用到NDK?
概括来说主要分为以下几种情况:
1. 代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。
2. 在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。
3. 便于移植,用C/C++写的库可以方便在其他的嵌入式平台上再次使用。
本篇文章环境为Android studio2.2.3

流程
1,新建android工程,其中创建加载本地java类,这里命名为MyLocalUtil.class。

public class MyLocalUtil {
     {
        System.loadLibrary("china");
    }

    /**
     * 让C代码做加法运算,把结果返回
     * @param x
     * @param y
     * @return
     */
    public native int add(int x, int y);

    /**
     * 从java传入字符串,C代码进程拼接
     *
     * @param s I am from java
     * @return  I am form java add I am from C
     */
    public native String addString(String s);

    /**
     * 让C代码给每个元素都加上10
     * @param intArray
     * @return
     */
    public native int[] increaseArrayEles(int[] intArray);
    /*
     * 应用: 检查密码是否正确, 如果正确返回200, 否则返回400
     */
    public native int checkPwd(String pwd);
    }

这里要注意的是,有些教程在写加载本地Library时,前面会加上static关键字,作用不言而喻,是希望在类加载前就调用本地文件。
这里,我们没有这种需求,就没有添加static关键字。
2.通过提示创建出.c文件

如果没有在gradle(app)里面配置的话,是提示不出来的。所以这一步进行还需要在gradle文件defaultConfig里面添加

        ndk{
        //对应MyLocalUtil中加载本地文件名称(必须)
         moduleName "china"
        // 指定要ndk需要兼容的架构(这样其他依赖包里mips,x86,armeabi,arm-v8之类的so会被过滤掉)
        abiFilters "armeabi","armeabi-v7a","x86"
        }

对应的gradle配置文件截图如下
gradle
有了上面的操作,通过选择提示,我们就会在项目的app里面看到自动创建了cpp文件夹,里面有我们自动生成的c文件。
自动生成c文件
同时c文件里面会自动生成jni方法。这里的命名是有规范的,这里Java_renk_addndk_MyLocalUtil_add。》Java_包名类名方法名。在JNI中变量类型也不在是java里面的数据类型,关于数据类型变化请看下表:
基本类型映射
基本类型
非基本类型映射
引用类型
这里还要注意的是自动生成的方法里面的参数(JNIEnv * env, jobject jobj,jint jx, jint jy) ,第一个是C指针,第二个,是java对象的引用。第三四个则是,调用本地方法传入的两个参数。当然,在本地方法中jint可以和int互用。故在jni方法中可以将add方法这样写

JNIEXPORT jint JNICALL Java_renk_addndk_MyLocalUtil_add(JNIEnv * env, jobject jobj,jint jx, jint jy) {
    int result = jx + jy;
    return  result;
};

最后,本地调用此方法就可以直接的到结果,不用再转换数据格式了。当然,现在还没完。因为我们还没有.h文件,其作用有点类似于java代码中的接口,提供规范标准。C也一样,我们直接在C文件里面添加方法名是不起作用的。还要在gradle.properties文件里面加上android.useDeprecatedNdk=true

3.生成.h文件。
生成.h文件需要用到javah命令,具体用法,是打开,Android studio下面的 Terminal面板。进入到项目中的java文件路径中。输入命令cd app\src\main\java 进入java文件,然后再输入 javah -d ..\jni 包名+类名 回车。这样就会自动在main下面jni文件夹下生成.h文件。
注意的是,这里的包名+类名,是加载本地文件的类名,倘若,你不是专门用一个类来加载native方法的话,就应该使用 写native方法的那个类的全路径。..\jni代表同级jni引用路径。
javah
生成了.h文件,然后再C文件引用它。在C文件顶部#include

public native int[] increaseArrayEles(int[] intArray); 
public native String addString(String s);

然后再.h文件里面添加相应方法。

JNIEXPORT jstring
JNICALL Java_renk_testndk_MyLocalUtil_addString
        (JNIEnv * , jobject, jstring);

JNIEXPORT jintArray
JNICALL Java_renk_testndk_MyLocalUtil_increaseArrayEles
        (JNIEnv * , jobject, jintArray);

在.C文件里面实现具体方法


JNIEXPORT jintArray
JNICALL Java_renk_addndk_MyLocalUtil_increaseArrayEles(JNIEnv * env, jobject jobj,jintArray jarray) {

//1.得到数组的长度
//jsize (*GetArrayLength)(JNIEnv*, jarray);
    jsize size = (*env)->GetArrayLength(env, jarray);
//2.得到数组元素
//jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
    jint *intArray = (*env)->GetIntArrayElements(env, jarray, JNI_FALSE);
//3.遍历数组,给每个元素加上10
    int i;
    for(i = 0;i<size;i++){
        // *(intArray+i) = *(intArray+i) + 10;
        *(intArray+i) +=  10;
    }
//4.返回结果
    return jarray;
}
JNIEXPORT jstring JNICALL Java_renk_addndk_MyLocalUtil_sayHello
        (JNIEnv * env, jobject jobj,jstring jstr) {
    char *fromJava = mJString2CStr(env, jstr);//转换方法
//拼接函数strcat,C/C++自带
    strcat(fromJava, fromC);//把拼接的结果放在第一参数里面
//jstring (*NewStringUTF)(JNIEnv*, const char*);
    return  (*env)->NewStringUTF(env, fromJava);
}

这里在字符串操作的时候有一个转换,因为java字符串与C中的字符串是不一样的,字符串在C中是char类型的指针数组,所以要单独写一个转换方法具体如下

/**
 * 把一个jstring转换成一个c语言的char* 类型.
 */
char *mJString2CStr(JNIEnv * env, jstringjstr) {
    char *rtn = "";
    //得到java类
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");
    //新建一个java字符串(GB2312)
    jstring strencode = (*env)->NewStringUTF(env, "GB2312");
    //获得String类的转换Byte数组方法的方法Id
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    //通过方法Id将字符串转化为数组
    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) {
    //C/C++自带,malloc,memcpy函数
        rtn = (char *) malloc(alen + 1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen]=0;
    }
    //释放内存
    (*env)->ReleaseByteArrayElements(env, barr, ba,0);
    return   rtn;
}

好了,这样就可以调用了,在jni里面主要是用到C的知识比较多,而C 最主要的就是指针啦(数组就是特殊的指针)。对C/C++不熟悉的,就可以学习一下。
最后,我们在实现一个小功能,用JNI验证密码。在java层传入密码,在C层验证密码是否正确,这样可以用于本地验证登陆。
同样的步骤,先.h文件写出相应的方法名,然后再C文件中写具体实现。最后在需要用到的地方调用该类的方法即可。
.h文件方法

JNIEXPORT jint
JNICALL Java_renk_addndk_MyLocalUtil_checkPwd
        (JNIEnv * , jobject, jstring);

.c文件方法

JNIEXPORT jint JNICALL Java_renk_addndk_MyLocalUtil_checkPwd(JNIEnv * env, jobject jobj,jstring jstr) {
//服务器的密码是
    char *origin = "renk";
    char *fromUser = mJString2CStr(env, jstr);
//函数比较字符串是否相同,C/C++自带
    int code = strcmp(origin, fromUser);
    if(code==0){
        return 200;
    }else{
        return 400;
    }
}

运行后,把SO文件拿出来,就可以用了。而且反编译难度很大。相对很很安全的。
最后,今天要讲的东西,就这么多,以上就是基本类型与引用类型的基本用法,内容很简单,以后会加大点难度。谢谢。国际惯例,源码贴出啦。
C文件

#include <jni.h>
#include <renk_addndk_MyLocalUtil.h>
JNIEXPORT jint JNICALL Java_renk_addndk_MyLocalUtil_add(JNIEnv * env, jobject jobj,jint jx, jint jy) {
    int result = jx + jy;
    return  result;
};

JNIEXPORT jintArray
JNICALL Java_renk_addndk_MyLocalUtil_increaseArrayEles(JNIEnv * env, jobject jobj,jintArray jarray) {

//1.得到数组的长度
//jsize       (*GetArrayLength)(JNIEnv*, jarray);
    jsize size = (*env)->GetArrayLength(env, jarray);
//2.得到数组元素
//jint*       (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*);
    jint *intArray = (*env)->GetIntArrayElements(env, jarray, JNI_FALSE);
//3.遍历数组,给每个元素加上10
    int i;
    for(i = 0;i<size;i++){
        // *(intArray+i) = *(intArray+i) + 10;
        *(intArray+i) +=  10;
    }
//4.返回结果
    return jarray;
}
JNIEXPORT jstring JNICALL Java_renk_addndk_MyLocalUtil_addString
        (JNIEnv * env, jobject jobj,jstring jstr) {

    char *fromJava = mJString2CStr(env, jstr);
//I am form java add I am from C
//c:
    char *fromC = "add I am from C";
//拼接函数strcat
    strcat(fromJava, fromC);//把拼接的结果放在第一参数里面
//jstring     (*NewStringUTF)(JNIEnv*, const char*);
//    LOGD("fromJava===%s\n",fromJava);
    return  (*env)->NewStringUTF(env, fromJava);
}
/**
 * 把一个jstring转换成一个c语言的char* 类型.
 */
char *mJString2CStr(JNIEnv * env, jstringjstr) {
    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;
}
JNIEXPORT jint JNICALL Java_renk_addndk_MyLocalUtil_checkPwd(JNIEnv * env, jobject jobj,jstring jstr) {
//存储的密码是
    char *origin = "liguo";
    //获得传入的密码。转换为C识别的类型
    char *fromUser = mJString2CStr(env, jstr);

//函数比较字符串是否相同 
    int code = strcmp(origin, fromUser);
    if(code==0){
        return 200;
    }else{
        return 400;
    }
}

H文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class renk_addndk_MyLocalUtil */

#ifndef _Included_renk_addndk_MyLocalUtil
#define _Included_renk_addndk_MyLocalUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     renk_addndk_MyLocalUtil
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_renk_addndk_MyLocalUtil_add
  (JNIEnv *, jobject, jint, jint);

JNIEXPORT jstring
JNICALL Java_renk_addndk_MyLocalUtil_addString
        (JNIEnv * , jobject, jstring);

JNIEXPORT jintArray
JNICALL Java_renk_addndk_MyLocalUtil_increaseArrayEles
        (JNIEnv * , jobject, jintArray);
JNIEXPORT jint
JNICALL Java_renk_addndk_MyLocalUtil_checkPwd
        (JNIEnv * , jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

本地加载类

public class MyLocalUtil {
    static {
        System.loadLibrary("china");
    }
    public native int add(int x, int y);

    public native int[] increaseArrayEles(int[] intArray);

    public native String addString(String s);
    public native int checkPwd(String password);
}

build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.0"
    defaultConfig {
        applicationId "renk.addndk"
        minSdkVersion 17
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "china"
            abiFilters "armeabi","armeabi-v7a","x86"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            jniDebuggable true
        }
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.0.1'
    testCompile 'junit:junit:4.12'
}

End

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值