IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android NDK 实战 -> 正文阅读

[移动开发]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相关的内容就先总结这些,如果有错误的地方或者不足的地方,请雅正!!!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-12-23 15:51:56  更:2021-12-23 15:52:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:05:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码