Android中NDK开发基础
来源:互联网 发布:sony手机挂起网络 编辑:程序博客网 时间:2024/05/17 06:36
简介
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配置文件截图如下
有了上面的操作,通过选择提示,我们就会在项目的app里面看到自动创建了cpp文件夹,里面有我们自动生成的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引用路径。
生成了.h文件,然后再C文件引用它。在C文件顶部#include
public native int[] increaseArrayEles(int[] intArray); public native String addString(String s);
然后再.h文件里面添加相应方法。
JNIEXPORT jstringJNICALL Java_renk_testndk_MyLocalUtil_addString (JNIEnv * , jobject, jstring);JNIEXPORT jintArrayJNICALL Java_renk_testndk_MyLocalUtil_increaseArrayEles (JNIEnv * , jobject, jintArray);
在.C文件里面实现具体方法
JNIEXPORT jintArrayJNICALL 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 jintJNICALL 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 jintArrayJNICALL 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 __cplusplusextern "C" {#endif/* * Class: renk_addndk_MyLocalUtil * Method: add * Signature: (II)I */JNIEXPORT jint JNICALL Java_renk_addndk_MyLocalUtil_add (JNIEnv *, jobject, jint, jint);JNIEXPORT jstringJNICALL Java_renk_addndk_MyLocalUtil_addString (JNIEnv * , jobject, jstring);JNIEXPORT jintArrayJNICALL Java_renk_addndk_MyLocalUtil_increaseArrayEles (JNIEnv * , jobject, jintArray);JNIEXPORT jintJNICALL 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
- Android中 NDK开发基础
- Android中NDK开发基础
- Android NDK开发基础
- Android NDK开发基础
- Android NDK开发基础
- 【Android】 NDK开发基础
- Android-NDK开发之基础-
- Android Studio NDK开发基础
- Android-NDK开发之基础--Android NDK开发技巧二
- Android-NDK开发之基础--Android NDK开发技巧二
- Android NDK开发,Android.mk构建基础
- Android Studio中NDK开发
- Android Studio中NDK开发
- Android Studio中NDK开发
- Android Studio中NDK开发
- Android Studio中NDK开发
- NDK开发基础①使用Android Studio编写NDK
- NDK开发基础①使用Android Studio编写NDK
- Java LinkedList基本用法
- git 1天入门,使用大全
- 立体视觉相关资料网站
- 开发小Tip-智能感知另一个js文件的成员
- 一些小技巧
- Android中NDK开发基础
- poj9125+2135 最小费用最大流<模版>
- JavaScript 内置对象属性及方法集合
- 菱形虚拟继承
- CCF之画图(java)
- PL/SQL恢复默认窗口样式
- 翻纸牌游戏(HDU-2209)
- Linux磁盘工具
- 使用curl进行模拟登录