前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android NDK 示例-返回字符串,数组,Java对象;兼容性问题

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

作者头像
AnRFDev
发布2021-02-01 15:32:02
1.6K0
发布2021-02-01 15:32:02
举报
文章被收录于专栏:AnRFDevAnRFDev

Android Studio 2.2.3 创建工程 NDKProj

工程准备

SmartAlgorithm.java中加载了库文件

代码语言:javascript
复制
java
`-- com
    `-- rustfisher
        `-- ndkproj
            |-- MainActivity.java
            `-- SmartAlgorithm.java

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

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

代码语言:javascript
复制
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。

代码语言:javascript
复制
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文件。 填入源文件的文件名。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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的配置。

代码语言:javascript
复制
sourceSets {
    main {
        jni.srcDirs = []
        jniLibs.srcDirs = ['src/main/libs']// 指定so库的位置
    }
}

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

代码语言:javascript
复制
Administrator@rust-PC /cygdrive/g/rust_proj/NDKProj/app/src/main/java
javah com.rustfisher.ndkproj.SmartAlgorithm

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

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

代码语言:javascript
复制
/* 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

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

代码语言:javascript
复制
#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。

代码语言:javascript
复制
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库

代码语言:javascript
复制
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。发现调用成功。

代码语言:javascript
复制
com.rustfisher.ndkproj D/MainActivity: onCreate: Hello from the JNI.
com.rustfisher.ndkproj D/MainActivity: onCreate: 3

处理数组的方法

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

代码语言:javascript
复制
public native short[] getConvertedArray(short[] data, int dataLen);
代码语言:javascript
复制
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代码

代码语言:javascript
复制
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代码

代码语言:javascript
复制
// 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的解释

测试调用

代码语言:javascript
复制
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);

输出

代码语言:javascript
复制
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数组为例 输入一个数组后,获取数组然后直接改变数组中的元素,最后释放掉本地引用

代码语言:javascript
复制
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);
  }
代码语言:javascript
复制
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));

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

代码语言:javascript
复制
origin before: [1, 2, 3, 4, 5, 6, 7]
origin after:  [1, 2, 3, 4, 5, 6, 6]

或者

代码语言:javascript
复制
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类。

代码语言:javascript
复制
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对象

代码语言:javascript
复制
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文件实现代码。注意参数签名的写法,要参照标准。

代码语言:javascript
复制
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方法生成对象

代码语言:javascript
复制
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库不兼容,出现

代码语言:javascript
复制
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:

代码语言:javascript
复制
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

代码语言:javascript
复制
LOCAL_MODULE := main

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

代码语言:javascript
复制
LOCAL_MODULE := mynewmain

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

代码语言:javascript
复制
static {
    System.loadLibrary("mynewmain");
}

重装app即可正常使用。

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

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

jstring转为char的方法 jstring -> char

jstring转为char的方法

代码语言:javascript
复制
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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-08-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工程准备
  • NDK返回值
  • 处理数组的方法
  • JNI层 unsigned char 与 jbyte 数组转换
  • 直接操作输入的数组
  • 返回Java对象
  • NDK兼容性问题
    • 分析处理兼容性问题
    • 同名so文件引起UnsatisfiedLinkError
    • jstring转为char的方法 jstring -> char
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档