Android jni基础使用(1)

193 阅读3分钟

Android开发中遇到c++与java方法相互调用,通过中间jni处理转换,jni注册有两种模式:

  1. 静态注册 Android开发通过ide生成的demo基本都是静态注册,形如:
package com.aonions.opengl.jni

object MainApp {

    init {
        System.loadLibrary("aonions")
    }

    external fun test(): String
}

对应生成jni代码

extern "C"
JNIEXPORT jstring JNICALL
Java_com_aonions_opengl_jni_MainApp_test(JNIEnv *env, jobject thiz) {
    return env->NewStringUTF("test-jni");
}

如上所示:Java_com_aonions_opengl_jni_MainApp_test 中包含了java中对该方法的包名,类名,方法名,需要一一对应,缺陷很明亮,java中修改、增加、修改包名等,都需要在对应jni中一一处理,在接口过多情况下,明显不适用

  1. 动态注册

参考Android原生代码,其实可以发现使用Jni_onload动态注册,对其做基本封装使用

新建onLoad.cpp类

/*
 * Copyright (C) 2009 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "jni.h"
#include "LogUtils.h"

#ifdef LOG_UTILS_TAG
#undef LOG_UTILS_TAG
#endif //LOG_UTILS_TAG
#define LOG_UTILS_TAG  "JNIOnload"

namespace android {
   int register_MainApp(JNIEnv* env);
}

using namespace android;

extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = nullptr;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return result;
    }

   register_MainApp(env);

    return JNI_VERSION_1_4;
}

jni动态注册提供一个JNIhelp类 头文件

/*
 * JNI helper functions.
 *
 * This file may be included by C or C++ code, which is trouble because jni.h
 * uses different typedefs for JNIEnv in each language.
 *
 * TODO: remove C support.
 */
#ifndef NATIVEHELPER_JNIHELP_H_
#define NATIVEHELPER_JNIHELP_H_

#include "jni.h"
#include <errno.h>
#include <unistd.h>

#ifndef NELEM
# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
#endif

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Register one or more native methods with a particular class.
 * "className" looks like "java/lang/String". Aborts on failure.
 * TODO: fix all callers and change the return type to void.
 */
int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, int numMethods);

#ifdef __cplusplus
}
#endif


#endif

方法实现 help类



#if defined(__ANDROID__)
/* libnativehelper is built by NDK 19 in one variant, which doesn't yet have the GNU strerror_r. */
#undef _GNU_SOURCE
#endif

#include "jni.h"
#include "JNIHelp.h"
#include "LogUtils.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cassert>

#include <string>

#ifdef LOG_UTILS_TAG
#undef LOG_UTILS_TAG
#endif //LOG_UTILS_TAG
#define LOG_UTILS_TAG  "JNIOnload"

extern "C" int jniRegisterNativeMethods(JNIEnv* env, const char* className,
    const JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    if (clazz == nullptr) {
        LOGE("Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }

    LOGD("%s, success\n", __func__);
    return JNI_TRUE;
}

构建main.cpp类,集成onLoad中 注册的方法

#include "jni.h"
#include "LogUtils.h"
#include "JNIHelp.h"

#ifdef CAR_LOG_TAG
#undef CAR_LOG_TAG
#endif //CAR_LOG_TAG
#define CAR_LOG_TAG  "MainApp"

#define APP_PACKAGE_CLASS_NAME "com/aonions/opengl/jni/MainApp"

namespace android {

    jstring test(JNIEnv *env, jclass type){
        return env->NewStringUTF("test-jni");
    }

    //------------------------------------jni loaded----------------------------------------------------------

    static const JNINativeMethod methodsRx[] = {
       {"test", "()Ljava/lang/String;", (void*)test },
    };

    int register_MainApp(JNIEnv *env){
       return jniRegisterNativeMethods(env, APP_PACKAGE_CLASS_NAME , methodsRx, NELEM(methodsRx) );
    }
};

对比静态注册,在修改包名,添加修改方法下能够快速修改完善

在onLoad类中还可以通过添加新的java实现类处理注册

namespace android {
   int register_MainApp(JNIEnv* env);
   //注册新类
   int register_MainSecondApp(JNIEnv* env);
}

using namespace android;

extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = nullptr;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("GetEnv failed!");
        return result;
    }

   register_MainApp(env);
   //注册新类
   register_MainSecondApp(env);

    return JNI_VERSION_1_4;
}

拆分类对比静态多出,但在后续迭代优化是方便

提供一个额外的jni log输出类LogUtils.h

#include <android/log.h>

#ifndef _LOG_UTILS_H_
#define _LOG_UTILS_H_ 1


#ifndef LOG_UTILS_TAG
#define LOG_UTILS_TAG "default"
#endif //LOG_UTILS_TAG

#define CarLogE(...) \
        __android_log_print(ANDROID_LOG_ERROR, LOG_UTILS_TAG, __VA_ARGS__)

#define CarLogD(...)  \
        __android_log_print(ANDROID_LOG_DEBUG, LOG_UTILS_TAG, __VA_ARGS__)

#define CarLogI(...)  \
        __android_log_print(ANDROID_LOG_INFO, LOG_UTILS_TAG, __VA_ARGS__)

#define CarLogW(...)  \
        __android_log_print(ANDROID_LOG_WARN, LOG_UTILS_TAG, __VA_ARGS__)

#define LOGE CarLogE
#define LOGD CarLogD
#define LOGI CarLogI
#define LOGW CarLogW

#endif //_LOG_UTILS_H_

配置CMakeList.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)

# now build app's shared lib
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall")

# 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.

file(GLOB SRC_LIST "main/*.cpp")
file(GLOB SRC_MAIN_LIST "main/src/*.cpp")

add_library( # Sets the name of the library.
        aonions

        # Sets the library as a shared library.
        SHARED

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

# 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.


# 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.
        aonions

        # Links the target library to the log library
        # included in the NDK.
        android
        log)

上述导入ide中即可运行编译,但在工程量大下,项目中导入c++编译实际是很费时间,并且涉及环境问题,在不同配置下可能出现无法编译成功;

解决方案可把c++代码在linux服务器配置编译 通过cmake编译动态so库,在导入项目, 提供一个编译脚本configure.sh

rm -rf build
mkdir -p build
# shellcheck disable=SC2164
cd build

cmake \
    -D CMAKE_SYSTEM_NAME=Android \
    -D CMAKE_SYSTEM_VERSION=21 \
    -D ANDROID_PLATFORM=android-21 \
    -D CMAKE_BUILD_TYPE=Release \
    -D CMAKE_TOOLCHAIN_FILE=/usr/local/ndk/android-ndk-r21b/build/cmake/android.toolchain.cmake \
    -D CMAKE_ANDROID_NDK=/usr/local/ndk/android-ndk-r21b \
    -D CMAKE_ANDROID_ARCH_ABI=armeabi-v7a \
    ..


make -j4

前提服务器配置好ndk交叉编译环境