Android NDK 实战

Android NDK 技术实践

本篇主要讲解ndk相关的内容,主要对开发实际用到的例子分析和相关技术点的总结,NDK开发环境的配置忽略了

定义:NDK是一套工具,通过这个技术可以将C/C++一些优秀的库或者函数在Android应用程序中使用。

NDK主要组件:

  • 动态库:c/c++源文件构建或者第三方的.so文件。
  • 静态库:c/c++源文件可以构建成.a文件,可以将静态库关联到其他的库中使用。
  • java原生接口:JNI是Java和C++组件用于相互通信的接口
  • 应用的而二进制接口(ABI):ABI 对应不同的架构ARM V7、ARM V8等
        ndk {
            abiFilters 'armeabi-v7a', "arm64-v8a"
        }

在build.gradle 配置文件中常这样指定,表示Android应用程序兼容v7和v8两种架构。

配置Cmake

在Android应用程序中,使用CMakeLists.txt配饰编译规则,这个文件需要在build.gradle中进行配置,如下

  externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }

NDK版本的配置

一般情况下不需要对NDK版本进行修改直接使用默认的就可以,如果有特殊需要,也可以在File->Project Structure中进行配置,或者直接在local.properties里直接将需要使用ndk的版本的绝对路径配置上

ndk.dir=D\:\\Program Files\\SDK\\ndk\\21.3.6528147

java 数据类型签名描述符

javaC/C++签名符号
voidvoidV
booleanjbooleanZ
intjintI
longjlongJ
doublejdoubleD
floatjfloatF
bytejbyteB
charjcharC
shortjshortS
boolean[]jbooleanArray[Z
int[]jintArray[I
long[]jlongArray[J
double[ ]jdoubleArray[D
float[]jfloatArray[F
byte[]jbyteArray[B
char[]jcharArray[C
short[]jshortArray[S
class 对象jobjectL 加包名加类名

如果通过这个规则拼不出方法签名,可以先将java类编译成class文件,然后跳转到class所在文件夹下,执行:

D:\android-studio\jre\bin\javap -s *.class

JNI实际例子

首先,需要在java类中声明native方法

    private  native List<LinePointInfo> getLinePoint(byte[] var0, int var1, int var2, int var3);

    private  native LineBoxInfo getLineBox(byte[] var0, int var1, int var2);

    private  native String getVectorData(byte[] var0, int var1, int var2, float var3);

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

上边是列出的是三个有代表性的三个native方法,返回值包含了String、Object对象、List集合。

其次就是在c++中添加native方法的具体实现

这个,方式比较简单,现在Android studio的功能很强大,在c/c++写本地方法的前边几个字母就给提示了。

方法的命名规则是: extern “C” JNIEXPORT 返回值 Java_包名_类名_方法名(JNIENV* env, jclass/* this *,…)。

extern "C" JNIEXPORT jobject

JNICALL
Java_com_test_testimage_ImageContext_getLinePoint(JNIEnv *env, jclass /* this */,
                                                            jbyteArray bytes, jint width,
                                                            jint height, jint lineNum) {
	//对输入参数进行转化处理
    jbyte *data = env->GetByteArrayElements(bytes, 0);
    uint8_t *pSrcData = (uint8_t *) data;
    
	//声明返回结果的对象
    std::vector <std::pair<int, int>> resultPoints;
	//声明c++函数对象
    CTestEdgePoint cTestEdgePoint;
	//执行c++函数
    cTestEdgePoint.run(pSrcData, width * 4, width, height, lineNum, resultPoints);

    //截止到这里通过c++已经拿到了需要需要的结果,后边就是需要构造返回的结果
    //因为返回值是List<LinePointInfo> 所以需要构造 LinePointInfo对象 这个类里边有一个Point的属性
    
    //通过反射得到 LinePointInfo class声明
    jclass objectLinePointInfo_jcls = env->FindClass(AI_DATA_OBJECT_LINE_POINT_INFO_CLASS);
    //得到构造函数的方法ID
    jmethodID objectLinePointInfo_init = env->GetMethodID(objectLinePointInfo_jcls, "<init>",
                                                          "(Landroid/graphics/Point;)V");


    //反射得到java层对象 ArrayList 对象
    jclass list_jclas = env->FindClass("java/util/ArrayList");
    jmethodID list_init = env->GetMethodID(list_jclas, "<init>", "()V");
    jobject list_obj = env->NewObject(list_jclas, list_init);
    //得到ArrayList中的add(Obejct ojecgt) 的方法ID
    jmethodID list_add = env->GetMethodID(list_jclas, "add", "(ILjava/lang/Object;)V");

    //反射得到 Point class声明
    jclass point_jcls = env->FindClass("android/graphics/Point");
    //得到Point中的两个属性X、Y
    jfieldID x = env->GetFieldID(point_jcls, "x", "I");
    jfieldID y = env->GetFieldID(point_jcls, "y", "I");
	//遍历从c++中拿到的结果,赋值给point,并将point添加到ArrayList
    for (int i = 0; i < resultPoints.size(); ++i) {
        //得到Point对象
        jobject point_jobj = env->AllocObject(point_jcls);
        //给point的x、y 点赋值
        env->SetIntField(point_jobj, x, resultPoints[i].first);
        env->SetIntField(point_jobj, y, resultPoints[i].second);
		
        //得到LinePointInfo对象,在构造方法中将Point传进去
        jobject linePointObject = env->NewObject(objectLinePointInfo_jcls, objectLinePointInfo_init,point_jobj);
        //将LinePointInfo对象调用ArrayList的add方法添加到数组中
        env->CallVoidMethod(list_obj, list_add, i, linePointObject);   //添加到数组
        //释放point对象
        env->DeleteLocalRef(point_jobj);                            //释放对象
    }
	
    //释放bytes数组
    env->ReleaseByteArrayElements(bytes, data, 0);

    return list_obj;

}

上面是JNI层的代码实现,这个里边包含几个部分

1.参数的改造解析,这里的输入参数一个图片,c++使用的话参数类型需要进行转化

2.c++方法的调用

3.返回结果的构造,这个过程中包含了如何构造一个java对象,如何得到一个函数方法Id以及在jni中调用java对象的一个方法。

其中AI_DATA_OBJECT_LINE_POINT_INFO_CLASS 是声明的一个常量 static const char *const AI_DATA_OBJECT_LINE_POINT_INFO_CLASS = “com/test/testimage/LinePointInfo”;

    //对jstring类型参数的转换
const char * authorizeContent = env->GetStringUTFChars(content,0);
	//相应的释放函数
env->ReleaseStringUTFChars(content,authorizeContent);

上边三个native方法的第一个参数是byte[],这个参数表示的是一个图片的数据,在Android中图片是Bitmap的形式,传到JNI层是通过byte[]的形式传递过来的。为了方便以后使用,转换的方法总结在下面:

    public static byte[] bitmap2RGBA(Bitmap bitmap) {
        int bytes = bitmap.getByteCount();
        ByteBuffer buffer = ByteBuffer.allocate(bytes);
        bitmap.copyPixelsToBuffer(buffer);
        byte[] rgba = buffer.array();
        return rgba;
    }

NDK中日志的打印

#include <android/log.h>

#define  LOG_TAG    "native-dev"    //定义常量
//... 传进来的数据会直接给VA_ARGS 这个变量  __android_log_print 这个是方法名 ANDROID_LOG_INFO 这个是头文件定义的常量
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

需要注意的点

  • 好多时候将包含native的部分提供给三方时候,这个时候可以打成aar包的形式,这里就需要注意aar和app模块使用一样的ABI

  • 主要release版本混淆的问题 -keep class com.test.**{*;}

  • 避免库重名的问题,如果发生"More than one file was found with OS independent path …’’

    可以使用 packagingOptions配置修复:

    pickFirst:当有多个匹配项目的时候使用第一个就可以了

    exclude:在打包的时候移除项目中的相关文件,不打入apk

  • 如果使用new的方式床架了c++对象,说明是创建的指针,需要使用delete关键字删除对象

  • 如果使用malloc空间之后一定别忘了使用free释放了空间

NDK相关的内容就先分享这些,本人能力有限,如果有错误或者不足之处,请交流指正,多谢!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值