专栏首页AnRFDevAndroid NDK 示例-返回字符串,数组,Java对象;兼容性问题

Android NDK 示例-返回字符串,数组,Java对象;兼容性问题

Android Studio 2.2.3 创建工程 NDKProj

工程准备

SmartAlgorithm.java中加载了库文件

java
`-- com
    `-- rustfisher
        `-- ndkproj
            |-- MainActivity.java
            `-- SmartAlgorithm.java

JNI目录,需要mk文件,头文件和源文件。这里头文件和源文件故意不统一文件名,也可实现效果。

但还是建议用同样的文件名,方便定位。

jni/
|-- Android.mk
|-- Application.mk
|-- com_rustfisher_ndkproj_SmartAlgorithm.h
`-- com_rustfisher_ndkproj_SmartAlgorithm_if_not_the_same.cpp

NDK返回值

加载SmartAlgorithm;这个是统一标示。LOCAL_MODULE 与 APP_MODULES 均为此标示。 NDK中的方法要声明为native。

package com.rustfisher.ndkproj;

public class SmartAlgorithm {

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

    public native String getMsg();
    public native int add(int a,int b);
}

注意,Java文件生成头文件后,Java文件的路径不能轻易改动。

编写Android.mk文件;ABI 选择all,编译出支持多个平台的so文件。 填入源文件的文件名。

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := SmartAlgorithm
TARGET_ARCH_ABI := all
LOCAL_SRC_FILES := com_rustfisher_ndkproj_SmartAlgorithm_if_not_the_same.cpp
include $(BUILD_SHARED_LIBRARY)

编写Application.mk文件(网上copy来的)。同样ABI 选择all。

APP_PLATFORM := android-16
APP_MODULES := SmartAlgorithm
APP_ABI := all
APP_STL := stlport_static
APP_CPPFLAGS += -fexceptions
# for using c++ features,you need to enable these in your Makefile
APP_CPP_FEATURES += exceptions rtti

修改工程build.gradle文件,添加jni的配置。

sourceSets {
    main {
        jni.srcDirs = []
        jniLibs.srcDirs = ['src/main/libs']// 指定so库的位置
    }
}

编译出头文件,得到 com_rustfisher_ndkproj_SmartAlgorithm.h

Administrator@rust-PC /cygdrive/g/rust_proj/NDKProj/app/src/main/java
javah com.rustfisher.ndkproj.SmartAlgorithm

将头文件放到jni目录下,与源文件一起。

生成的头文件不要手动去修改,直接使用即可。

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

#ifndef _Included_com_rustfisher_ndkproj_SmartAlgorithm
#define _Included_com_rustfisher_ndkproj_SmartAlgorithm
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_rustfisher_ndkproj_SmartAlgorithm
 * Method:    getMsg
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_rustfisher_ndkproj_SmartAlgorithm_getMsg
  (JNIEnv *, jobject);

/*
 * Class:     com_rustfisher_ndkproj_SmartAlgorithm
 * Method:    add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_rustfisher_ndkproj_SmartAlgorithm_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

编写源文件,实现头文件中的方法。一个是返回字符串,一个是加法。

#include <jni.h>
#include <string.h>
#include <android/log.h>

#include "com_rustfisher_ndkproj_SmartAlgorithm.h"

/* Already define in com_rustfisher_ndkproj_SmartAlgorithm.h, no need to extern C here.
extern "C" {
    JNIEXPORT jstring JNICALL Java_com_rustfisher_ndkproj_SmartAlgorithm_getMsg(JNIEnv *env, jobject obj);
    JNIEXPORT jint JNICALL Java_com_rustfisher_ndkproj_SmartAlgorithm_add(JNIEnv *env, jobject obj, jint a, jint b);
};*/

JNIEXPORT jstring JNICALL Java_com_rustfisher_ndkproj_SmartAlgorithm_getMsg(JNIEnv *env, jobject obj) {

    return env->NewStringUTF("Hello from the JNI.");
}

JNIEXPORT jint JNICALL Java_com_rustfisher_ndkproj_SmartAlgorithm_add(JNIEnv *env, jobject obj, jint a, jint b) {
    return a + b;
}//*

然后在命令行 ndk-build。这里是win7下的Cygwin。

Administrator@rust-PC /cygdrive/g/rust_proj/NDKProj/app/src/main/jni
$ ndk-build.cmd
[all] Compile++      : SmartAlgorithm <= com_rustfisher_ndkproj_SmartAlgorithm_if_not_the_same.cpp
[all] SharedLibrary  : libSmartAlgorithm.so
[all] Install        : libSmartAlgorithm.so => libs/arm64-v8a/libSmartAlgorithm.so
# ...... 后面还有很多

在libs目录下出现了对应的so库

Administrator@rust-PC /cygdrive/g/rust_proj/NDKProj/app/src/main/libs
$ tree
.
|-- arm64-v8a
|   `-- libSmartAlgorithm.so
|-- armeabi
|   `-- libSmartAlgorithm.so
|-- armeabi-v7a
|   `-- libSmartAlgorithm.so
|-- mips
|   `-- libSmartAlgorithm.so
|-- mips64
|   `-- libSmartAlgorithm.so
|-- x86
|   `-- libSmartAlgorithm.so
`-- x86_64
    `-- libSmartAlgorithm.so

7 directories, 7 files

在MainActivity中调用这两个方法。

运行apk到机器上,查看log。发现调用成功。

com.rustfisher.ndkproj D/MainActivity: onCreate: Hello from the JNI.
com.rustfisher.ndkproj D/MainActivity: onCreate: 3

处理数组的方法

1.不要直接操作输入的数组; 2.注意释放本地引用,防止溢出。

public native short[] getConvertedArray(short[] data, int dataLen);
JNIEXPORT jshortArray JNICALL Java_com_rustfisher_ndkproj_SmartAlgorithm_getConvertedArray(JNIEnv *env, jobject obj, jshortArray input, jint len) {
    jshort* inputPtr;
    inputPtr = env->GetShortArrayElements(input,0);// 直接操作指针会改变Android Dalvik中的值
    jshort* resPtr;
    jshortArray result;
    result = env->NewShortArray(len);// 创建新的数组
    resPtr = env->GetShortArrayElements(result,0);// 指针

    for(jint i = 0;i < len;i++) {
        resPtr[i] = inputPtr[i] * 2;
    }
    env->ReleaseShortArrayElements(input, inputPtr, 0);// 释放本地引用
    env->SetShortArrayRegion(result,0,len,resPtr);     // 存入数据
    env->ReleaseShortArrayElements(result, resPtr, 0); // 释放本地引用
    return result;// 返回结果
}

JNI层 unsigned char 与 jbyte 数组转换

本例说明的是unsigned char 与 jbyte之间互相转换 注意方法:(*env)->SetByteArrayRegion(env, jbyte_arr, 0, len, uc_ptr); java代码

public byte[] getByteArrayFromJNI() {
    return nativeGetByteArray();
}
public byte[] byteArrayTravelJNI(byte[] input) {
    return nativeSendByteArray(input, input.length);
}
private native byte[] nativeGetByteArray(); // 从JNI中获取byte数组

// 输入byte数组,在JNI中转换后再获取回来
private native byte[] nativeSendByteArray(byte[] input, int len);

JNI C代码

// return byte array from unsigned char array.  jbytes:  1 2 0 7f 80 81 ff 0 1
JNIEXPORT jbyteArray JNICALL Java_com_rustfisher_ndkalgo_NDKUtils_nativeGetByteArray(JNIEnv *env, jobject jObj)
{
    unsigned char uc_arr[] = {1, -2, 0, 127, 128, 129, 255, 256, 257};
    int uc_arr_len = sizeof(uc_arr) / sizeof(uc_arr[0]);
    jbyte byte_array[uc_arr_len];
    int i = 0;
    for(;i < uc_arr_len; i++) {
        byte_array[i] = uc_arr[i];
    }
    jbyteArray jbyte_arr = (*env)->NewByteArray(env, uc_arr_len);
    (*env)->SetByteArrayRegion(env, jbyte_arr, 0, uc_arr_len, byte_array);
    return jbyte_arr;
}

// jbyte -> unsigned char -> jbyte
JNIEXPORT jbyteArray JNICALL Java_com_rustfisher_ndkalgo_NDKUtils_nativeSendByteArray
    (JNIEnv *env, jobject jObj, jbyteArray input_byte_arr, jint input_len)
{
    int len = (int)input_len;
    jbyte *jbyte_ptr = (*env)->GetByteArrayElements(env, input_byte_arr, 0);

    unsigned char *uc_ptr = (unsigned char *)jbyte_ptr;

    jbyteArray jbyte_arr = (*env)->NewByteArray(env, len);
    jbyte byte_array[input_len];
    int i = 0;
    for(;i < input_len; i++) {
        byte_array[i] = uc_ptr[i];
    }
    (*env)->SetByteArrayRegion(env, jbyte_arr, 0, len, byte_array);
    (*env)->ReleaseByteArrayElements(env, input_byte_arr, jbyte_ptr, 0);
    return jbyte_arr;
}

关于SetByteArrayRegion这个方法

方法说明:void SetXxxArrayRegion(JNIEnv *env, jarray array, jint start, jint length, Xxx elems[])

将C数组的元素复制到Java数组中。注意最后一个参数要和前面的对应上。

void ReleaseXxxArrayElements(JNIEnv *env, jarray array, Xxx elems[], jint mode) 通知虚拟机通过GetXxxArrayElements获得的一个指针已经不再需要了。Mode是0,更新数组 元素后释放elems缓存。

在这里遇到过一个bug,同样的代码在armeabi上正常运行,但是到了v7a或v8a平台上就闪退。 使用SetXxxArrayRegion这个方法时,传入的参数一定要和方法名中的Xxx对应上 详细可以参考Core Java中的Java Native和Android Develop上关于abi的解释

测试调用

NDKUtils ndkUtils = new NDKUtils();
byte[] res = ndkUtils.getByteArrayFromJNI(); // 从JNI中获取byte数组
logBytes(res);
Log.d(TAG, "-------------------------------------------------------------");
byte[] inputBytes = new byte[]{1, 2, 127, (byte) 128, (byte) 255, -120};
byte[] tRes = ndkUtils.byteArrayTravelJNI(inputBytes); // 让byte数组在JNI中旅游一圈
logBytes(inputBytes);
logBytes(tRes);

输出

bytes:  1 2 0 7f 80 81 ff 0 1 
-------------------------------------------------------------
bytes:  1 2 7f 80 ff 88 
bytes:  1 2 7f 80 ff 88 

直接操作输入的数组

以int数组为例 输入一个数组后,获取数组然后直接改变数组中的元素,最后释放掉本地引用

JNIEXPORT void JNICALL Java_com_rustfisher_ndkalgo_NDKUtils_nativeModifyArray
  (JNIEnv *env, jobject jObj, jintArray input_arr, jint input_len) {
    int * input_ptr = (*env)->GetIntArrayElements(env, input_arr, 0);
    input_ptr[input_len - 1] =  input_ptr[input_len - 1] - 1;
    (*env)->ReleaseIntArrayElements(env, input_arr, input_ptr, 0);
  }
NDKUtils moUtil = new NDKUtils();
int[] origin = new int[]{1, 2, 3, 4, 5, 6, 7};
Log.d(TAG, "origin before: " + Arrays.toString(origin));
moUtil.modifyArray(origin);
Log.d(TAG, "origin after:  " + Arrays.toString(origin));

观察输出可以看出,输入的数组直接被改变了

origin before: [1, 2, 3, 4, 5, 6, 7]
origin after:  [1, 2, 3, 4, 5, 6, 6]

或者

JNIEXPORT void JNICALL Java_com_rustfisher_face_1detect_1lib_CalHelper_cvtNV21
  (JNIEnv *env, jclass jcls, jbyteArray input_arr,jint in_arr_len,
   jbyteArray target_arr, jint nv21_size, jint y_size, jint yuv_gap) {
    jbyte *in_ptr = env->GetByteArrayElements(input_arr, false);
    jbyte *target_ptr = env->GetByteArrayElements(target_arr, false);
    for(int i = y_size; i < nv21_size; i+=2){
        target_ptr[i] = in_ptr[i + yuv_gap + 1];
        target_ptr[i + 1] = in_ptr[i + yuv_gap];
    }
  }

返回Java对象

NDK中可以创建Java对象并返回。 例如我们新建一个JavaUser类。

public class JavaUser {
    private int age;
    private String name;

    public JavaUser(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return name + ", " + age;
    }
}

native方法返回一个JavaUser对象

public class NDKUtils {

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

    public JavaUser createUser(int age, String name) {
        return nativeGetUser(age, name);
    }

    private native JavaUser nativeGetUser(int age, String name);

}

c文件实现代码。注意参数签名的写法,要参照标准。

JNIEXPORT jobject JNICALL Java_com_rustfisher_ndkalgo_NDKUtils_nativeGetUser
    (JNIEnv *env, jobject jObj, jint age, jstring name)
{
    jclass userClass = (*env)->FindClass(env, "com/rustfisher/ndkalgo/JavaUser");
    jmethodID userConstruct = (*env)->GetMethodID(env, userClass, "<init>", "(ILjava/lang/String;)V");
    return (*env)->NewObject(env, userClass, userConstruct, age, name);
}

调用native方法生成对象

private void testJavaUserNDK() {
    NDKUtils ndkUtils = new NDKUtils();
    JavaUser tom = ndkUtils.createUser(20, "Tom");
    Log.d(TAG, tom.toString());
}

NDK兼容性问题

Vivo x6plus 兼容性问题。Vivo x6plus 打开Parrot界面即崩溃。但是Parrot官方APP能够正常使用。 我自己的so库与Parrot的so库不兼容,出现

java.lang.UnsatisfiedLinkError:
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.xx.xx.xxx-1/base.apk"],nativeLibraryDirectories=[/data/app/com.xx.xx.xxx-1/lib/arm64, /vendor/lib64, /system/lib64]]] couldn't find "libjson.so"
    at java.lang.Runtime.loadLibrary(Runtime.java:366)
    at java.lang.System.loadLibrary(System.java:988)
    at com.parrot.arsdk.ARSDK.loadSDKLibs(ARSDK.java:20)
    at com.parrot.sdk.activity.DronesListActivity.<clinit>(DronesListActivity.java:44)
    at java.lang.reflect.Constructor.newInstance(Native Method)

分析处理兼容性问题

将Parrot官方apk解包后,找到so库文件。发现只有x86、mips、armeabi_v7a、armeabi 这4个。 而我加载了有更多的库。

将我自己的so文件删除至只剩下Parrot那4个即可。

Android.mk TARGET_ARCH_ABI := x86 mips armeabi armeabi-v7a

同名so文件引起UnsatisfiedLinkError

主工程app中带有C工程与so文件。现需要将所有的C工程移到新的模块mylib中。

新建模块mylib,将C工程复制进来。gradle中配置jni,因为修改了文件路径,重新生成头文件并修改cpp文件。 在模块中进行ndk-build,获得so库。

安装运行app,出现UnsatisfiedLinkError:

java.lang.UnsatisfiedLinkError: No implementation found for void com.xx.jni.MyJNI.init(java.lang.String) 
(tried Java_com_xx_jni_MyJNI_init and Java_com_xx_jni_MyJNI_init__Ljava_lang_String_2)

分析原因,app能够正常加载库文件,但未找到实现方法。app使用的so库,究竟是不是我们指定的那个。

尝试进行修复,原app工程的Android.mk

LOCAL_MODULE := main

移动到模块后,新的Android.mk修改为

LOCAL_MODULE := mynewmain

库改了名字后,修改Java代码

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

重装app即可正常使用。

经过分析与尝试,删除原app工程中所有的so文件,再次重装app即可正常运行。不需要修改so库的名字。

错误原因猜想:app主工程与模块mylib中有同名的so文件,安装app时会优先使用app主工程中的so库。

jstring转为char的方法 jstring -> char

jstring转为char的方法

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

参考 JNI中string 、 char* 和 jstring 两种转换 - CSDN xlxxcc

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Android JNI(一)——NDK与JNI基础

    Android 平台从一开就已经支持了C/C++了。我们知道Android的SDK主要是基于Java的,所以导致了在用Android SDK进行开发的工程师们都...

    隔壁老李头
  • Android JNI 基础知识

    自从 Android Studio 升级到 2.3 版本以后,使用 CMake 进行编译就方便多了,不需要再写 Android.mk 了,也不需要用 javah...

    音视频开发进阶
  • Android程序中,内嵌ELF可执行文件-- Android开发C语言混合编程总结

    都知道的,Android基于Linux系统,然后覆盖了一层由Java虚拟机为核心的壳系统。跟一般常见的Linux+Java系统不同的,是其中有对硬件驱动进行支持...

    俺踏月色而来
  • Android程序中,内嵌ELF可执行文件--Android开发C语言混合编程总结

    都知道的,Android基于Linux系统,然后覆盖了一层由Java虚拟机为核心的壳系统。跟一般常见的Linux+Java系统不同的,是其中有对硬件驱动进行支持...

    俺踏月色而来
  • Android开发笔记(六十九)JNI实战

    NDK全称为Native Development Kit,意即原生的开发工具,NDK允许开发者在APP中通过C/C++代码执行部分程序。它是Android提...

    用户4464237
  • Android 面试之必问高级知识点

    在Android早期的版本中,应用程序的运行环境是需要依赖Dalvik虚拟机的。不过,在后来的版本(大概是4.x版本),Android的运行环境却换到了 And...

    xiangzhihong
  • Android Studio2.2下NDK开发初试

    forrestlin
  • JNI基础

    JNI基础 将java中的字符串转换成C中字符串的工具方法 char* Jstring2CStr(JNIEnv* env, ...

    xiangzhihong
  • rust 开发编译 Android 动态库实践

    rust 的学习曲线比较陡峭,在开始学习之前建议看看王垠的这篇文章 《如何掌握所有的编程语言》,地址如下:

    音视频开发进阶
  • Android与Python爱之初体验

    看到这个标题,大家可能会认为就是Android运行python脚本,或者用python写app,这些用QPython和P4A就可以实现了。我在想既然C可以调用P...

    陈宇明
  • Android JNI出坑指南

    笔者结合自身经验、网上资料对 JNI 的坑进行总结,如果有不正确或遗漏之处欢迎指出。

    腾讯Bugly
  • Android Q 文本新功能

    文本显示是大部分应用的重要任务之一。为了帮助您打造更好的文本体验,我们在 Android Q 中引入多项新特性,在满足开发者需求的同时,持续提升应用性能。其中包...

    Android 开发者
  • 给 Java 开发者的 Kotlin 快速上手教程(Kotlin for Java Developers)v0.1

    1995年,当年如日中天的Sun公司发布了Java语言,引起了巨大的轰动,与当时主流的C语言和Basic语言比起来,Java语言简单、面向对象、稳定、与平台无关...

    一个会写诗的程序员
  • 给 Java 开发者的 Kotlin 快速上手教程(Kotlin for Java Developers)v0.1

    1995年,当年如日中天的Sun公司发布了Java语言,引起了巨大的轰动,与当时主流的C语言和Basic语言比起来,Java语言简单、面向对象、稳定、与平台无关...

    一个会写诗的程序员
  • Android流媒体开发之路三:基于NDK开发Android平台RTSP播放器

    本人的交叉编译平台是ubuntu 64bit,编译成动态库,然后让APP通过JNI来调用,跟其他程序的编译方式差不多。当然,首先需要系统内布置好NDK编译环境。...

    hbstream
  • JNI 数据类型及Java与C++之间互调

    JNI,全称Java NativeInterface,是一种为Java编写本地方法和JVM嵌入本地应用程序标准的应用程序接口。

    Yif
  • Android Ndk and Opencv Development 2

    本节主要介绍的内容是Android NDK开发的核心内容和开发总结(包括很多常见问题的解决方案)。

    宅男潇涧
  • Android手机App安全漏洞整理(小结)

    当前APK文件的安全性是非常令人堪忧的。APK运行环境依赖的文件/文件夹 res、DEX、主配文件Lib 只有简单的加密或者甚至没有任何加密。诸如apktool...

    砸漏
  • Android老司机教你如何快速突击大厂面试,快恶补这些知识点,成功必看!

    最近不少人在后台私信问我:做了几年 Android 工程师,现在很迷茫,想跳槽但是没有目标,不知道接下来该朝着哪个方向发展。

    Android技术干货分享

扫码关注云+社区

领取腾讯云代金券