Android CMake集成JNI开发环境

什么是JNI

JNI是Java Native Interface(Java 原生接口)的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。例如,一个用C语言写的浏览器可以在一个嵌入式JVM上面执行从网上下载下来的applets。

如何在Android Studio中集成JNI开发环境

Android Studio 2.2之后,支持CMake构建工程的JNI开发环境,在Android Studio中创建JNI工程更加方便了。下面我们先来看看配置JNI开发环境大致的步骤:

  1. 下载NDK,CMake插件,LLDB(Native层)代码调试插件
  2. 创建CMakeLists.txt文件,并指明要生成的本地库信息
  3. 配置build.gradle文件,将CMakeLists.txt文件中原生库的配置信息利用Gradle构建起来

1.下载NDK,CMake插件,LLDB(native层)代码调试插件

这里写图片描述

打开Android Studio依次按File->Setting 打开Android SDK工具管理面板,将CMake,LLDB和NDK三个选项勾选,点击Apply,Android Studio就会帮我们下载CMake插件,LLDB插件和NDK开发工具包。下载之后可以SDK根目录下看到ndk-bundle,如下所示:
这里写图片描述

下载好后,在SDK根目录下可以看到ndk-build文件夹。其实也可以在Android官网下载NDK,再将其放到SDK的目录下NDK官网下载地址,然后打开Project Structure指明NDK路径即可。
最后将NDK的安装路径配置到系统环境变量中,便于可以在命令行中编译生成c头文件等。
这里写图片描述

创建CMakeLists.txt文件,并指明要生成的本地库信息

安装好NDK,CMake和LLDB插件后,我们便可以创建JNI工程了,我们先用Android Studio创建一个JNIDemo工程,然后再分析其项目结构。

新建Android Studio工程JNIDemo,勾选Include C++ support选项即可创建JNI工程,如下图:
这里写图片描述

配置C++支持功能(Customize C++ Support)
在Customize C++ Support 界面默认即可。
这里写图片描述
指定编译库的环境,其中Toolchain Default使用的是默认的CMake环境;C++ 11也就是C++环境。两种环境都可以编库。
* Exceptions Support
如果选中复选框,则表示当前项目支持C++异常处理,如果支持,在项目Module级别的build.gradle文件中会增加一个标识 -fexceptions到cppFlags属性中,并且在so库构建时,gradle会把该属性值传递给CMake进行构建。
* Runtime Type Information Support
同理,选中复选框,项目支持RTTI,属性cppFlags增加标识-frtti
建好的工程如下所示:
这里写图片描述

建好的Test工程既已经集成好JNI开发环境了,运行效果如下:

对比下该工程和普通AS工程的区别,发现多了CMakeLists.txt文件,CMakeList.txt文件用于配置JNI项目属性,主要用于声明CMake使用版本,so库名称,c和c++文件路径等信息。打开CMakeLists.txt文件,内容如下:
这里写图片描述
其中cmake_minimum_required(VERSION 3.4.1)
指明CMake最小使用的版本是3.4.1
- add_library()
表示配置so库信息(为当前脚本添加库)

  • native-lib
    这个是声明引用so库的名称,在项目中,如果需要使用这个so文件,引用的名称就是这个。值得注意的是,实际上生成的so文件名称是libnative-lib。当Run项目或者build项目是,在Module级别的build文件下的intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下会生成相应的so库文件。

  • SHARED
    这个参数表示共享so库文件,也就是在Run项目或者build项目时会在目录intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下生成so库文。此外,so库文件都会在打包到.apk里面,可以通过选择菜单栏的Build->Analyze Apk…*查看apk中是否存在so库文件,一般它会存放在lib目录下。

  • src/main/cpp/native-lib.cpp
    构建so库的源文件。

  • STATIC
    静态库,是目标文件的归档文件,在链接其它目标的时候使用。
  • SHARED
    动态库,会被动态链接,在运行时被加载。
  • MODULE
    模块库,是不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接。
  • 头文件
    也可以配置头文件路径,方法是(注意这里指定的是目录而非文件):include_directories([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])
  • find_library()
    这个方法与我们要创建的so库无关而是使用NDK的Apis或者库,默认情况下Android平台集成了很多NDK库文件,所以这些文件是没有必要打包到apk里面去的。直接声明想要使用的库名称即可(猜测:貌似是在Sytem/libs目录下)。在这里不需要指定库的路径,因为这个路径已经是CMake路径搜索的一部分。如示例中使用的是log相关的so库。
  • log-lib
    这个指定的是在NDK库中每个类型的库会存放一个特定的位置,而log库存放在log-lib中
  • log
    指定使用log库
  • target_link_libraries()
    如果你本地的库(native-lib)想要调用log库的方法,那么就需要配置这个属性,意思是把NDK库关联到本地库。
  • native-lib
    要被关联的库名称
  • ${log-lib}
    要关联的库名称,要用大括号包裹,前面还要有$符号去引用。

实际上NDK还预置一个源代码(C/C++)库,如果本地库想要关联这些代码,可以做如下配置

add_library( app-glue
                  STATIC 
                 ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )
 # You need to link static libraries against your shared native library.
 target_link_libraries( native-lib app-glue ${log-lib} )

app-glue 仍然是自定义库的名称
ANDROID_NDK这个是Android Studio已经定义好的变量,可以直接使用
它指定的是NDK源代码的根目录

使用第三方库在脚本导入第三方库,可在脚本加入如下配置

add_library( imported-lib
SHARED
IMPORTED )

其中IMPORTED表示只需要导入,不需要构建so库
接着设置so库路径

set_target_properties(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)

set_target_properties( imported-lib // so库的名称 
PROPERTIES IMPORTED_LOCATION // import so库 
libs/libimported-lib.so // so库路径
)

3.配置build.gradle文件,将CMakeLists.txt文件中原生库的配置信息利用Gradle构建起来

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.msi.jnidemo"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.0.0-beta1'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:0.5'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
}

其中还可以在defaultConfig中用abiFilters指明编译库的ABI配置类型,如:

defaultConfig {
        applicationId "com.example.javacallc"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
            ndk {
                // Specifies the ABI configurations of your native
                // libraries Gradle should build and package with your APK.
                abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                        'arm64-v8a'
            }
        }

    }

当使用已经存在so库时,不应该配置target_link_libraries()方法,因为只有在build 库文件时才能进行link操作。
在MainActivity中加载动态链接库,并声明native方法,即可调用链接库里面的C/C++函数

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

native-lib.cpp文件代码如下

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

extern "C"
JNIEXPORT jstring

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

运行即可获取cpp文件返回的字符串,将其显示在TextView中,如下所示:
这里写图片描述
限于文章篇幅,有关Java和C/C++的相互调用机制,以及打包so作为系统库的方法将在下篇博文中结合实际例子分析,尽情关注。

猜你喜欢

转载自blog.csdn.net/hjj378315764/article/details/79797343