JNI开发基础流程

155 阅读2分钟

1. 环境

android studio

2. JNI开发基本流程

2.1 创建 HelloWorld.java,并声明 native 方法 add();

package com.example.myapplication.utis;

public class HelloWorld {
    static {
        System.loadLibrary("mynativeso");
    }
    public native int add(int a,int b);
}

2.2 使用 javac 命令编译源文件,生成 HelloWorld.h 头文件

  1. 进入HelloWorld.java所在的目录
  2. javac -encoding utf-8 -h . HelloWorld.java
  3. 将生成的HelloWorld.h头文件放到app/src/main/jni目录中
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_myapplication_utis_HelloWorld */

#ifndef _Included_com_example_myapplication_utis_HelloWorld
#define _Included_com_example_myapplication_utis_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_myapplication_utis_HelloWorld
 * Method:    add
 * Signature: (II)V
 */
JNIEXPORT jint JNICALL Java_com_example_myapplication_utis_HelloWorld_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

2.3 在源文件 HelloWorld.cpp 中实现函数原型

2.3.1 自定义头文件的方法实现

#include <jni.h>
#include <string>

extern "C"
jint Java_com_example_myapplication_utis_HelloWorld_add
  (JNIEnv *env, jobject jj, jint a, jint b){
    return a + b;
  };

2.3.2 调用非标准so中的方法

  1. 将非标准的libtest.so放到app/src/main/jniLibs对应的文件夹中
  2. 将libtest.so对应的头文件test.h放到app/src/main/jni中
  3. 在jni中创建文件HelloWorld.cpp文件
#ifndef _TEST_JNI_ADD_H_
#define _TEST_JNI_ADD_H_

int add(int x, int y);

#endif
#include <jni.h>
#include <string>
#include <test.h>//导入需要的.h文件,这个是必须的,如果依赖的第三方库没有.h,需要自己编写

extern "C"
jint Java_com_example_myapplication_utis_HelloWorld_add
  (JNIEnv *env, jobject jj, jint a, jint b){
    int result = add(a,b);//引入test.so中的方法
    return result;
  };

2.4 编译本地代码,生成 Hello-World.so 动态原生库文件

2.4.1 ndk构建

2.4.2 cmake构建

2.4.2.1 创建文件app/CMakeLists.txt文件

#指定cmake最小版本(影响下面api的使用)
cmake_minimum_required(VERSION 3.4.1)

#设置生成的so动态库最后输出的路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

#1.指定库的名字、类型(STATIC,SHARED)、以及关联源码的相对路径
#2.可以多次调用
add_library( 
				# 设置生成库的名字(这里最终会生成libhelloworld.so)
        helloworld
        # 生成动态库
        SHARED
        # 指定源码文件,HelloWorld.cpp文件
        src/main/jni/HelloWorld.cpp )

#指定头文件所在的目录
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/src/main/jni/include/)

#添加已经构建好的库,并配置相关参数,指定其所在路径
#${PROJECT_SOURCE_DIR} 项目根目录
#${CMAKE_CURRENT_SOURCE_DIR} 当前文件所在地址
add_library(test SHARED IMPORTED)
set_target_properties(test  
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_CURRENT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libtest.so)

#将ndk中的源码构建至静态库
add_library( app-glue
             STATIC
             ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

#从ndk库从找到内置的库(liblog)并将其所在路径存储为一个变量(log-lib)
find_library( 
				# log库的别名
        log-lib
        #log库
        log )

#关联cmake编译时需要使用的库
target_link_libraries(
      	#即将要生成的libhelloworld.so
        helloworld
      	#对应libtest.so
        test
      	#对应上面定义的静态库
      	app-glue
      	#对应上面定义的ndk内置liblog库
        ${log-lib} )

2.4.2.2 配置app下的build.gradle

android {
  ...
   defaultConfig {
    ...
    //可选配置项
    externalNativeBuild {

      // For ndk-build, instead use the ndkBuild block.
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

        // Sets a flag to enable format macro constants for the C compiler.
        cFlags "-D__STDC_FORMAT_MACROS"

        // Sets optional flags for the C++ compiler.
        cppFlags "-fexceptions", "-frtti"
      }
    }
    ...
     //添加
    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your app.
      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
    }
  }
  buildTypes {...}

  //模块配置
  externalNativeBuild {
    //添加
    cmake {
      // CMakeLists.txt相对路径
      path "CMakeLists.txt"
    }
  }
  ...
  //添加
    packagingOptions {//加上这些代码(可选)
        pickFirst 'lib/armeabi-v7a/libhelloworld.so'
    }
    
}

packagingOptions选项说明:

1)pickFirst:匹配到多个相同文件,只提取第一个。只作用于APK,不能过滤aar和jar中的文件

2)exclude:过滤掉某些文件或者目录不添加到APK中,作用于APK,不能过滤aar和jar中的内容。

3)doNotStrip:可以设置某些动态库不被优化压缩。

4)merge:将匹配的文件都添加到APK中,和pickFirst有些相反,会合并所有文件。

2.4.2.3 点击make project就可以在jniLibs下生成指定的libhelloworld.so文件

2.4.2.4 在其他项目中使用时需要创建同包名下的HelloWorld.java文件;同时将依赖的so文件放到项目的jniLibs下的响应目录中

3. 用so生成aar供三方使用

3.1 新建C++项目

默认会生成下面的文件

android {
    ...
    defaultConfig {
        ...
    }
    buildTypes {}
    ...
    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.18.1'
        }
    }
    ...
}
cmake_minimum_required(VERSION 3.18.1)

project("nativeapplication")

add_library(
        nativeapplication
        SHARED
        native-lib.cpp)

find_library(
        log-lib
        log)

target_link_libraries(
        nativeapplication
        ${log-lib})
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_kemai_nativeapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
public class MainActivity extends AppCompatActivity {

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

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    public native String stringFromJNI();
}

make project生成so文件,如下图

3.2 新建android-lib

创建帮助类DemoHelper.java

package com.kemai.videostacker;

public class DemoHelper {
    static {
        System.loadLibrary("nativeapplication");
    }

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

将上面生成的so拷贝到jniLibs相应的文件夹中

修改上一步中的native-lib.cpp文件,增加add方法的实现

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_kemai_nativeapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

//新增内容
extern "C"
JNIEXPORT jint JNICALL
Java_com_kemai_videostacker_DemoHelper_add(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a + b;
}

添加完毕后,重新make project项目,在app下重新生成so文件(否则新增的c方法无法被找到),然后重新拷贝到mylibrary中。接着assebleRelease命令执行mylibrary,生成aar文件

将mylibrary-release.aar放到其他项目中进行测试

若有收获,就点个赞吧