android jni编程笔记

android jni编程笔记


获得方法签名

Java生成类方法签名,JNI回调应用层时特别有用

  • 首先Java类需编译生成class字节码
  • cd到字节码所在目录
  • 执行cmd: javap -s 类名(不需要加上.class)
  • 控制台即会打印签名信息,如下
Compiled from "JniParser.java"
public class com.gosuncn.libparser.jni.JniParser {
  public static com.gosuncn.libparser.jni.JniParser getInstance();
    descriptor: ()Lcom/gosuncn/libparser/jni/JniParser;

  public void responseDataCallBack(long, int, java.lang.String);
    descriptor: (JILjava/lang/String;)V

  public native long createParser();
    descriptor: ()J

    ...
}

这里写图片描述

jni-回调到应用层

在jni中经常需要将数据回调给应用层,通常的做法是将要回调给应用层的对象序列化为json字符串(cJSON此库非常好用,下载后也有例子说明),传递到应用层再反序列化为对象使用。
下面举例说明

  • Step 1.声明回调
public class JniInstant {

    private static final String TAG = "JniInstant";

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

    private static JniInstant instance;

    private JniInstant() {
    }

    public static JniInstant getInstance() {
        if (instance == null) {
            instance = new JniInstant();

        }
        return instance;
    }

    /**
     * 初始化
     * 此接口必须首先调用,且成功后才可使用其他接口
     *
     * @return 0表示成功
     */
    public native int init();

    //////////////////////////////////////////////////////回调///////////////////////////////////////

    /**
     * GxxApp异常回调
     * descriptor: (I)V
     * @param status 0--下线 1--上线
     */
    public void gxxAppExceptionCallback(int status){
        Log.e(TAG, "gxxAppExceptionCallback:status(0--下线 1--上线)= "+status);
    }
  • Step 2.初始化回调
// c或cpp文件中
#include <jni.h>
JavaVM *jvmInstant= NULL;
jobject objInstant = NULL;
jmethodID gxxAppExceptionCallback=NULL;

extern "C"
JNIEXPORT jint JNICALL
Java_com_gosuncn_instant_jni_JniInstant_init(JNIEnv *env, jobject instance) {
    env->GetJavaVM(&jvmInstant);
    //函数参数中 jobject 或者它的子类,其参数都是 local reference。Local reference 只在这个 JNI函数中有效,JNI函数返回后,引用的对象就被释放,它的生命周期就结束了。
    // 若要留着日后使用,则需根据这个 local reference 创建 global reference。Global reference 不会被系统自动释放,它仅当被程序明确调用 DeleteGlobalReference 时才被回收。(JNI多线程机制)
    objInstant = env->NewGlobalRef(instance);
    //在子线程中不能这样用
    // jclass objclass = env->FindClass( "com/gosuncn/video/JniRGBPlayer");
    //这种写法可以用在子线程中,但限制了回调方法的位置必须在当前类中,如本例必须在JniInstant类里
    jclass objclass = env->GetObjectClass(instance);
    //此处的gxxAppExceptionCallback为应用层的回调方法名,回调方法必须在应用层的java类里,如本例是在JniInstant类里,关于方法对应的方法签名(如"(I)V")可使用javap获得,具体在文章中有说明
    gxxAppExceptionCallback = env->GetMethodID(objclass, "gxxAppExceptionCallback", "(I)V");
    return 0;
}
 ...
  • Step 3.回调应用层
/**
* 判断是否已经绑定线程
* @param env   注意这里用取地址符
* @return  true--已经绑定成功,false--已经绑定,无需再绑定
*/
bool attachThread(JavaVM *mJavaVM,JNIEnv* &env) {
    bool attached = false;
    if (mJavaVM == NULL) {
        LOGE("JavaVM == NULL!!!,please check");
        return attached;
    }
    switch (mJavaVM->GetEnv((void **) &env, JNI_VERSION_1_6)) {
        case JNI_OK://已绑定
            LOGI("JNI_OK");
            break;
        case JNI_EDETACHED://已解绑
            LOGI("JNI_EDETACHED");
            if (mJavaVM->AttachCurrentThread(&env, NULL) != JNI_OK) {
                LOGE("Could not attach current thread. ");
            } else {
                attached = true;
            }

            break;
        case JNI_EVERSION:
            LOGE("Invalid java version. ");
    }
    return attached;
}
/**
 * GxxApp异常消息回调
 * @param lHandle  登陆句柄
 * @param eEvent 异常事件
 * @param pUserData 用户数据
 */
void funPtrExceptionCallback(long lHandle,
                             EnumExceptionEvent eEvent,
                             void *pUserData) {   
    JNIEnv *env;
    bool attached = attachThread(jvmInstant, env);
    //在此处将数据回调到应用层,,如本例中JniInstant类中的gxxAppExceptionCallback即可收到此回调
    if (env != NULL && objInstant != NULL && gxxAppExceptionCallback != NULL) {
        //CallVoidMethod后面为方法的参数,方法签名时有多少个,就得写多少个,本例中只有一个整型参数
        env->CallVoidMethod(objInstant, gxxAppExceptionCallback, (jint) eEvent);
    }
    if (attached) {
        jvmInstant->DetachCurrentThread();
    }

}

字符处理

/**
 * 将jstring转化为GBK编码char数组
 * @param [in]env
 * @param [in]jstr
 * @param [out]returnChar 用户自己管理内存,自己开辟自己销毁
 * @param [in]returnCharLen 用户开辟的returnChar的长度,如果长度不够会导致失败
 * @return 字符串长度,为-1则表示失败
 */
int jstringToPCharGBK(JNIEnv *env, jstring jstr, char *returnChar, int returnCharLen) {
    if (returnChar == NULL) {
        return 0;
    }
    jclass tmpClass = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("gb2312");
    jmethodID mid = env->GetMethodID(tmpClass, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    if(alen<=0){
        return 0;
    }
    if (returnCharLen <= alen) {
        return -1;
    }
    jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
    strcpy(returnChar, reinterpret_cast<const char *>(ba));
    returnChar[alen]=0;
    env->ReleaseByteArrayElements(barr, ba, 0);
    return alen;

}

/**
 * 将GBK编码的字符串char*转化为jstring
 * @param env 
 * @param pchar 
 * @return 
 */
jstring charToJstringGBK(JNIEnv *env, const char *pchar ) {
    // 定义java String类 strClass 
    jclass strClass = env->FindClass("java/lang/String");
    // 获取java String类方法String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String  
    jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
    // 建立byte数组  
    jbyteArray bytes = env->NewByteArray(strlen(pat));
    // 将char* 转换为byte数组  
    env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte *) pat);
    //设置String, 保存语言类型,用于byte数组转换至String时的参数  
    jstring encoding = env->NewStringUTF(
            "gb-2312");
    //将byte数组转换为java String,并输出  
    jstring result = (jstring) env->NewObject(strClass, ctorID, bytes, encoding);
    env->DeleteLocalRef(bytes);
    env->DeleteLocalRef(encoding);
    return result;
}

CMakeLists.txt例子

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

set(JNILIBS_SO_PATH  ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs)
# 定义源文件目录
get_filename_component(CPP_SRC_DIR  ${CMAKE_SOURCE_DIR}/src/main/cpp  ABSOLUTE)
# 定义源文件目录下的源文件
file(GLOB_RECURSE cpp_sources ${CPP_SRC_DIR}/*.*)

if (ANDROID_ABI MATCHES "^armeabi-v7a$")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=softfp -mfpu=neon")
elseif(ANDROID_ABI MATCHES "^arm64-v8a")
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -ftree-vectorize")
endif()


# Specifies a path to native header files.
include_directories(src/main/cpp/encoder/)
include_directories(src/main/cpp/camera/)
include_directories(src/main/cpp/common/)
include_directories(src/main/cpp/audio/)
include_directories(src/main/cpp/media/)
include_directories(src/main/cpp/libyuv/include/libyuv/)
include_directories(src/main/cpp/libyuv/include/)


//添加动态库或静态库,注意动静态库都必须放置在${ANDROID_ABI}文件夹下
add_library(libcamera  SHARED IMPORTED )
set_target_properties(libcamera PROPERTIES IMPORTED_LOCATION
                    ${JNILIBS_SO_PATH}/${ANDROID_ABI}/libcamera.so )
add_library(libyuv  SHARED IMPORTED )
set_target_properties(libyuv PROPERTIES IMPORTED_LOCATION
                    ${JNILIBS_SO_PATH}/${ANDROID_ABI}/libyuv.so )
add_library(uv STATIC IMPORTED)
set_target_properties(uv
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libuv.a)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             instant


             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
            ${cpp_sources}
            )



# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                        # 如果有依赖关系,被依赖的要放置在后面,假如instant依赖libyuv,则libyuv要放置在后面
                       instant
                       libyuv

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

#升级到gradle4之后会报错误:More than one file was found with OS independent path 'lib/armeabi/libstreamhandler.so'
#解决办法有两个:一是删除jniLibs/armeabi/libstreamhandler.so,同时注释掉下面生成so输出路径的语句即可
#二是在当前build.gradle中添加 android{ packagingOptions { pickFirst 'lib/armeabi/libstreamhandler.so' }}

#在指定目录生成so文件,注意目录区分大小写,如jniLibs_DIR的“jniLibs”必须和后面build.gradle指定的sourceSet目录中指定的“jniLibs”完全一致
set(jniLibs_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs)
set_target_properties( instant
                             PROPERTIES
                             LIBRARY_OUTPUT_DIRECTORY
                             "${jniLibs_DIR}/${ANDROID_ABI}")

build.gradle例子

apply plugin: 'com.android.library'

android {
    compileSdkVersion 27

    defaultConfig {
       ...

        externalNativeBuild {
            cmake {
                cppFlags "-frtti -fexceptions -D_LINUX -Wno-error=format-security"
            }
        }
        ndk {
            // Specifies the ABI configurations of your native
            // libraries Gradle should build and package with your APK.
            abiFilters 'armeabi-v7a'// ,'arm64-v8a','x86', 'x86_64', 'armeabi'

        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
    packagingOptions {
        pickFirst 'lib/armeabi-v7a/libinstant.so'

        //假如so冲突,可在此进行排除
        exclude 'lib/armeabi-v7a/libavcodec-57.so'
        exclude 'lib/armeabi-v7a/libAvDecodePlugin.so'
        exclude 'lib/armeabi-v7a/libavdevice-57.so'
        exclude 'lib/armeabi-v7a/libavfilter-6.so'
        exclude 'lib/armeabi-v7a/libavformat-57.so'
        exclude 'lib/armeabi-v7a/libavutil-55.so'
        exclude 'lib/armeabi-v7a/libGMFLib.so'
        exclude 'lib/armeabi-v7a/libGSFoundation.so'
        exclude 'lib/armeabi-v7a/libGSLog.so'
        exclude 'lib/armeabi-v7a/libGSUtil.so'
        exclude 'lib/armeabi-v7a/libpicture.so'
        exclude 'lib/armeabi-v7a/libpostproc-54.so'
        exclude 'lib/armeabi-v7a/libswresample-2.so'
        exclude 'lib/armeabi-v7a/libswscale-4.so'
    }

}
...

猜你喜欢

转载自blog.csdn.net/huweijian5/article/details/80234249