IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android - JNI 开发你所需要知道的基础,Android工程师面试题 -> 正文阅读

[移动开发]Android - JNI 开发你所需要知道的基础,Android工程师面试题

// 2. 获取成员变量 id
jfieldID strFieldId = env->GetFieldID(clazz,“testField”,“Ljava/lang/String;”);
// 3. 根据 id 获取值
jstring jstr = static_cast(env->GetObjectField(thiz, strFieldId));
const char* cStr = env->GetStringUTFChars(jstr,NULL);
LOGE(“获取 MainActivity 的 String field :%s”,cStr);

// 4. 修改 String
jstring newValue = env->NewStringUTF(“新的字符创”);
env-> SetObjectField(thiz,strFieldId,newValue);

// 5. 释放资源
env->ReleaseStringUTFChars(jstr,cStr);
env->DeleteLocalRef(newValue);
env->DeleteLocalRef(clazz);

// 获取静态变量
jfieldID staticIntFieldId = env->GetStaticFieldID(clazz,“staticField”,“I”);
jint staticJavaInt = env->GetStaticIntField(clazz,staticIntFieldId);

GetFieldID 和 GetStaticFieldID 需要三个参数:

  • jclass
  • filed name
  • 类型签名: JNI 使用 jvm 的类型签名
类型签名一览表
TypeSignature Java Type
Zboolean
Bbyte
Cchar
Sshort
Iint
Jlong
Ffloat
Ddouble
Vvoid
L fully-qualified-class;fully-qualified-class
[typetype[]
(arg-types) ret-typemethod type
  • 基本数据类型的比较好理解,不如要获取一个 int ,GetFieldID 需要传入签名就是 I;

  • 如果是一个类,比如 String,签名就是 L+全类名; :Ljava.lang.String;

  • 如果是一个 int array,就要写作 [I

  • 如果要获取一个方法,那么方法的签名是:(参数签名)返回值签名,参数如果是多个,中间不需要加间隔符,比如: | java 方法|JNI 签名| |–|--| |void f (int n); |(I)V| |void f (String s,int n); |(Ljava/lang/String;I)V| |long f (int n, String s, int[] arr); |(ILjava/lang/String;[I)J|

操作 method

操作 method 和 filed 非常相似,先获取 MethodID,然后对应的 CallXXXMethod 方法

Java层返回值方法族本地返回类型NativeType
voidCallVoidMethod()(无)
引用类型CallObjectMethod( )jobect
booleanCallBooleanMethod ( )jboolean
byteCallByteMethod( )jbyte
charCallCharMethod( )jchar
shortCallShortMethod( )jshort
intCallIntMethod( )jint
longCallLongMethod()jlong
floatCallFloatMethod()jfloat
doubleCallDoubleMethod()jdouble

在 java 中我们要想获取 MainActivity 的 className 会这样写:

this.getClass().getName()

可以看到需要先调用 getClass 方法获取 Class 对象,然后调用 Class 对象的 getName 方法,我们来看一下如何在 native 方法中调用:

extern “C” JNIEXPORT jstring JNICALL
Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
std::string hello = “Hello from C++”;
// 1. 获取 thiz 的 class,也就是 java 中的 Class 信息
jclass thisclazz = env->GetObjectClass(thiz);
// 2. 根据 Class 获取 getClass 方法的 methodID,第三个参数是签名(params)return
jmethodID mid_getClass = env->GetMethodID(thisclazz, “getClass”, “()Ljava/lang/Class;”);
// 3. 执行 getClass 方法,获得 Class 对象
jobject clazz_instance = env->CallObjectMethod(thiz, mid_getClass);
// 4. 获取 Class 实例
jclass clazz = env->GetObjectClass(clazz_instance);
// 5. 根据 class 的 methodID
jmethodID mid_getName = env->GetMethodID(clazz, “getName”, “()Ljava/lang/String;”);
// 6. 调用 getName 方法
jstring name = static_cast(env->CallObjectMethod(clazz_instance, mid_getName));
LOGE(“class name:%s”, env->GetStringUTFChars(name, 0));

// 7. 释放资源
env->DeleteLocalRef(thisclazz);
env->DeleteLocalRef(clazz);
env->DeleteLocalRef(clazz_instance);
env->DeleteLocalRef(name);

return env->NewStringUTF(hello.c_str());
}

创建对象

首先定义一个 java 类:

public class Person {
private int age;
private String name;

public Person(int age, String name){
this.age = age;
this.name = name;
}

public void print(){
Log.e(“Person”,name + age + “岁了”);
}
}

然后我们再 JNI 中创建一个 Person 并调用它的 print 方法:

// 1. 获取 Class
jclass pClazz = env->FindClass(“com/wangzhen/jnitutorial/Person”);
// 2. 获取构造方法,方法名固定为
jmethodID constructID = env->GetMethodID(pClazz,"","(ILjava/lang/String;)V");
if(constructID == NULL){
return;
}
// 3. 创建一个 Person 对象
jstring name = env->NewStringUTF(“alex”);
jobject person = env->NewObject(pClazz,constructID,1,name);

jmethodID printId = env->GetMethodID(pClazz,“print”,"()V");
if(printId == NULL){
return;
}
env->CallVoidMethod(person,printId);

// 4. 释放资源
env->DeleteLocalRef(name);
env->DeleteLocalRef(pClazz);
env->DeleteLocalRef(person);

JNI 引用

JNI 分为三种引用:

  • 局部引用(Local Reference),类似 java 中的局部变量
  • 全局引用(Global Reference),类似 java 中的全局变量
  • 弱全局引用(Weak Global Reference),类似 java 中的弱引用

上面的代码片段中最后都会有释放资源的代码,这是 c/c++ 编程的良好习惯,对于不同 JNI 引用有不同的释放方式。

局部引用

创建

JNI 函数返回的所有 Java 对象都是局部引用,比如上面调用的 NewObject/FindClass/NewStringUTF 等等都是局部引用。

释放

  • 自动释放 局部引用在方法调用期间有效,并在方法返回后被 JVM 自动释放。
  • 手动释放
手动释放的场景

有了自动释放之后为什么还需要手动释放呢?主要考虑一下场景:

  • 本机方法访问大型Java对象,从而创建对Java对象的局部引用。然后,本机方法在返回到调用方之前执行附加计算。对大型Java对象的本地引用将防止对该对象进行垃圾收集,即使该对象不再用于计算的其余部分。
  • 本机方法创建大量本地引用,但并非所有本地引用都同时使用。因为 JVM 需要一定的空间来跟踪本地引用,所以创建了太多的本地引用,这可能导致系统内存不足。例如,本机方法循环遍历一个大型对象数组,检索作为本地引用的元素,并在每次迭代时对一个元素进行操作。每次迭代之后,程序员不再需要对数组元素的本地引用。

所以我们应该养成手动释放本地引用的好习惯。

手动释放的方式
  • GetXXX 就必须调用 ReleaseXXX。

在调用 GetStringUTFChars 函数从 JVM 内部获取一个字符串之后,JVM 内部会分配一块新的内存,用于存储源字符串的拷贝,以便本地代码访问和修改。即然有内存分配,用完之后马上释放是一个编程的好习惯。通过调用ReleaseStringUTFChars 函数通知 JVM 这块内存已经不使用了。

  • 对于手动创建的 jclass,jobject 等对象使用 DeleteLocalRef 方法进行释放

全局引用

创建

JNI 允许程序员从局部引用创建全局引用:

static jstring globalStr;
if(globalStr == NULL){
jstring str = env->NewStringUTF(“C++”);
// 从局部变量 str 创建一个全局变量
globalStr = static_cast(env->NewGlobalRef(str));

//局部可以释放,因为有了一个全局引用使用str,局部str也不会使用了
env->DeleteLocalRef(str);
}

释放

全局引用在显式释放之前保持有效,可以通过 DeleteGlobalRef 来手动删除全局引用调用。

弱全局引用

与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象

所以在使用弱引用时,必须先检查缓存过的弱引用是指向活动的对象,还是指向一个已经被GC的对象

创建

static jclass globalClazz = NULL;
//对于弱引用 如果引用的对象被回收返回 true,否则为false
//对于局部和全局引用则判断是否引用java的null对象
jboolean isEqual = env->IsSameObject(globalClazz, NULL);
if (globalClazz == NULL || isEqual) {
jclass clazz = env->GetObjectClass(instance);
globalClazz = static_cast(env->NewWeakGlobalRef(clazz));
env->DeleteLocalRef(clazz);
}

释放

删除使用 DeleteWeakGlobalRef

线程相关

局部变量只能在当前线程使用,而全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。

加载动态库

在 android 中有两种方式加载动态库:

  • System.load(String filename) // 绝对路径
  • system library path // 从 system lib 路径下加载

比如下面代码会报错,在 java.library.path 下找不到 hello

static{
System.loadLibrary(“Hello”);
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0HR5wu7m-1637978792238)(https://user-gold-cdn.xitu.io/2020/6/16/172bcae4b92c0365?imageView2/0/w/1280/h/960/ignore-error/1)]

可以使用下面代码打印出 java.library.path ,并且吧 hello 拷贝到改路径下:

public static void main(String[] args){
System.out.println(System.getProperty(“java.library.path”));
}

JNI_OnLoad

调用System.loadLibrary()函数时, 内部就会去查找so中的 JNI_OnLoad 函数,如果存在此函数则调用。 JNI_OnLoad 必须返回 JNI 的版本,比如 JNI_VERSION_1_6、JNI_VERSION_1_8。

动态注册

JNI 匹配对应的 java 方法有两种方式:

  • 静态注册: 之前我们使用的 Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI 来进行与java方法的匹配就是静态注册
  • 动态注册:就是将 java 中的方法在代码中动态的与 JNI 方法对应起来

静态注册的名字需要包名,太长了,可以使用动态注册来缩短方法名。

比如我们再 Java 中有两个 native 方法:

public class MainActivity extends AppCompatActivity {
public native void dynamicJavaFunc1();

public native int dynamicJavaFunc2(int i);
}

在 native 代码中,我们不使用静态注册,而使用动态注册

void dynamicNativeFunc1(){
LOGE(“调用了 dynamicJavaFunc1”);
}
// 如果方法带有参数,前面要加上 JNIEnv *env, jobject thisz
jint dynamicNativeFunc2(JNIEnv *env, jobject thisz,jint i){
LOGE(“调用了 dynamicTest2,参数是:%d”,i);
return 66;
}

// 需要动态注册的方法数组
static const JNINativeMethod methods[] = {
{“dynamicJavaFunc1”,"()V",(void*)dynamicNativeFunc1},
{“dynamicJavaFunc2”,"(I)I",(int*)dynamicNativeFunc2},
};
// 需要动态注册native方法的类名
static const char *mClassName = “com/wangzhen/jnitutorial/MainActivity”;

jint JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env = NULL;
// 1. 获取 JNIEnv,这个地方要注意第一个参数是个二级指针

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

int result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
// 2. 是否获取成功
if(result != JNI_OK){
LOGE(“获取 env 失败”);
return JNI_VERSION_1_6;
}
// 3. 注册方法
jclass classMainActivity = env->FindClass(mClassName);
// sizeof(methods)/ sizeof(JNINativeMethod)
result = env->RegisterNatives(classMainActivity,methods, 2);

if(result != JNI_OK){
LOGE(“注册方法失败”)
return JNI_VERSION_1_2;
}

return JNI_VERSION_1_6;
}

这样我们再 MainActivity 中调用 dynamicJavaFunc1 方法就会调用 native 中的 dynamicNativeFunc1 方法。

native 线程中调用 JNIEnv*

前面介绍过 JNIEnv* 是和线程相关的,那么如果在 c++ 中新建一个线程A,在线程A 中可以直接使用 JNIEnv* 吗? 答案是否定的,如果想在 native 线程中使用 JNIEnv* 需要使用 JVM 的 AttachCurrentThread 方法进行绑定:

JavaVM *_vm;

jint JNI_OnLoad(JavaVM* vm, void* reserved){
_vm = vm;
return JNI_VERSION_1_6;
}

void* threadTask(void* args){
JNIEnv *env;
jint result = _vm->AttachCurrentThread(&env,0);
if (result != JNI_OK){
return 0;
}

// …

// 线程 task 执行完后不要忘记分离
_vm->DetachCurrentThread();
}

extern “C”
JNIEXPORT void JNICALL
Java_com_wangzhen_jnitutorial_MainActivity_nativeThreadTest(JNIEnv *env, jobject thiz) {
pthread_t pid;
pthread_create(&pid,0,threadTask,0);
}

交叉编译

在一个平台上编译出另一个平台上可以执行的二级制文件的过程叫做交叉编译。比如在 MacOS 上编译出 android 上可用的库文件。 如果想要编译出可以在 android 平台上运行的库文件就需要使用 ndk。

两种库文件

linux 平台上的库文件分为两种:

  • 静态库: 编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了,linux中后缀名为”.a”。
  • 动态库: 在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库。linux 中后缀名为”.so”,gcc在编译时默认使用动态库。

Android 原生开发套件 (NDK):这套工具使您能在 Android 应用中使用 C 和 C++ 代码。 CMake:一款外部编译工具,可与 Gradle 搭配使用来编译原生库。如果您只计划使用 ndk-build,则不需要此组件。 LLDB:Android Studio 用于调试原生代码的调试程序。

NDK

原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库。 我们可以在 sdk/ndk-bundle 中查看 ndk 的目录结构,下面列举出三个重要的成员:

  • ndk-build: 该 Shell 脚本是 Android NDK 构建系统的起始点,一般在项目中仅仅执行这一个命令就可以编译出对应的动态链接库了。
  • platforms: 该目录包含支持不同 Android 目标版本的头文件和库文件, NDK 构建系统会根据具体的配置来引用指定平台下的头文件和库文件。
  • toolchains: 该目录包含目前 NDK 所支持的不同平台下的交叉编译器 - ARM 、X86、MIPS ,目前比较常用的是 ARM。 // todo ndk-depends.cmd

ndk 为什么要提供多平台呢? 不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。更具体的内容参考官方文档

使用 ndk 手动编译动态库

在 ndk 目录下的 toolchains 下有多个平台的编译工具,比如在 /arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin 下可以找到 arm-linux-androideabi-gcc 执行文件,利用 ndk 的这个 gcc 可以编译出在 android(arm 架构) 上运行的动态库:

arm-linux-androideabi-gcc -fPIC -shared test.c -o libtest.so

参数含义 -fPIC: 产生与位置无关代码 -shared:编译动态库,如果去掉代表静态库 test.c:需要编译的 c 文件 -o:输出 libtest.so:库文件名

独立工具链 版本比较新的 ndk 下已经找不到 gcc 了,如果想用的话需要参考独立工具链。 比如执行 $NDK/build/tools/make_standalone_toolchain.py --arch arm --api 21 --install-dir/$yourDir 可以产生 arm 的独立工具链

$NDK 代表 ndk 的绝对路径, $yourDir 代表输出文件路径

当源文件很多的时候,手动编译既麻烦又容易出错,此时出现了 makefile 编译。

makefile

makefile 就是“自动化编译”:一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,如何进行链接等等操作。 Android 使用 Android.mk 文件来配置 makefile,下面是一个最简单的 Android.mk:

源文件在的位置。宏函数 my-dir 返回当前目录(包含 Android.mk 文件本身的目录)的路径。

LOCAL_PATH := $(call my-dir)

引入其他makefile文件。CLEAR_VARS 变量指向特殊 GNU Makefile,可为您清除许多 LOCAL_XXX 变量

不会清理 LOCAL_PATH 变量

include $(CLEAR_VARS)

指定库名称,如果模块名称的开头已是 lib,则构建系统不会附加额外的前缀 lib;而是按原样采用模块名称,并添加 .so 扩展名。

LOCAL_MODULE := hello

包含要构建到模块中的 C 和/或 C++ 源文件列表 以空格分开

LOCAL_SRC_FILES := hello.c

构建动态库

include $(BUILD_SHARED_LIBRARY)

我们配置好了 Android.mk 文件后如何告诉编译器这是我们的配置文件呢? 这时候需要在 app/build.gradle 文件中进行相关的配置:

apply plugin: ‘com.android.application’

android {
compileSdkVersion 29

defaultConfig {

// 应该将源文件编译成几个 CPU so
externalNativeBuild{
ndkBuild{
abiFilters ‘x86’,‘armeabi-v7a’
}
}
// 需要打包进 apk 几种 so
ndk {
abiFilters ‘x86’,‘armeabi-v7a’
}
}
// 配置 native 构建脚本位置
externalNativeBuild{
ndkBuild{
path “src/main/jni/Android.mk”
}
}
// 指定 ndk 版本
ndkVersion “20.0.5594570”


}

dependencies {
implementation fileTree(dir: “libs”, include: ["*.jar"])

}

Google 推荐开发者使用 cmake 来代替 makefile 进行交叉编译了,makefile 在引入第三方预编译好的 so 的时候会在 android 6.0 版本前后有些差异,比如在 6.0 之前需要手动 System.loadLibrary 第三方 so,在之后则不需要。 关于 makefile 还有很多配置参数,这里不在讲解,更多参考官方文档

在 6.0 以下,System.loadLibrary 不会自动加载 so 内部依赖的 so 在 6.0 以下,System.loadLibrary 会自动加载 so 内部依赖的 so 所以使用 mk 的话需要做版本兼容

cmake

CMake是一个跨平台的构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。能够输出各种各样的makefile或者project文件。Cmake 并不直接建构出最终的软件,而是产生其他工具的脚本(如Makefile ),然后再依这个工具的构建方式使用。 Android Studio利用CMake生成的是ninja,ninja是一个小型的关注速度的构建系统。我们不需要关心ninja的脚本,知道怎么配置cmake就可以了。

CMakeLists.txt

Make的脚本名默认是CMakeLists.txt,当我们用 android studio new project 勾选 include c/c++ 的时候,会默认生成以下文件:

|- app |-- src |— main |---- cpp |----- CMakeLists.txt |----- native-lib.cpp

先来看一下 CMakeLists.txt:

设置 cmake 最小支持版本

cmake_minimum_required(VERSION 3.4.1)

创建一个库

add_library( # 库名称,比如现在会生成 native-lib.so
native-lib

设置是动态库(SHARED)还是静态库(STATIC)

SHARED

设置源文件的相对路径

native-lib.cpp )

搜索并指定预构建库并将路径存储为变量。

NDK中已经有一部分预构建库(比如 log),并且ndk库已经是被配置为cmake搜索路径的一部分

可以不写 直接在 target_link_libraries 写上log

find_library( # 设置路径变量的名称
log-lib

指定要CMake定位的NDK库的名称

log )

指定CMake应链接到目标库的库。你可以链接多个库,例如构建脚本、预构建的第三方库或系统库。

target_link_libraries( # Specifies the target library.
native-lib
${log-lib} )

我们再来看下 gradle 中的配置:

android {
compileSdkVersion 29
buildToolsVersion “29.0.1”
defaultConfig {

testInstrumentationRunner “androidx.test.runner.AndroidJUnitRunner”
// 设置编译版本
externalNativeBuild {
cmake {
abiFilters “armeabi-v7a”,“x86”
}
}
}

// 设置配置文件路径
externalNativeBuild {
cmake {
path “src/main/cpp/CMakeLists.txt”
version “3.10.2”
}
}
}

这样在编译产物中就可以看到两个版本的 so:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k3umOWWU-1637978792295)(https://user-gold-cdn.xitu.io/2020/6/16/172bcb4b3dd44795?imageView2/0/w/1280/h/960/ignore-error/1)]

添加多个源文件

比如我们添加一个 extra.h:

#ifndef JNITUTORIAL_EXTRA_H
#define JNITUTORIAL_EXTRA_H
const char * getString(){
return “string from extra”;
}
#endif //JNITUTORIAL_EXTRA_H

然后在 native-lib.cpp 中使用:

#include <jni.h>
#include
#include <android/log.h>
#include “extra.h”
// VA_ARGS 代表… 可变参数
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR,“JNI”,VA_ARGS);

extern “C” JNIEXPORT jstring JNICALL
Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
// std::string hello = “Hello from new C++”;
std::string hello = getString();
return env->NewStringUTF(hello.c_str());
}

源文件已经写好了,这时候要修改一下 CMakeLists.txt:

add_library(
native-lib
SHARED
native-lib.cpp
// 添加 extra.h
extra.h )

#==================================

当然如果源文件非常多,并且可能在不同的文件夹下,像上面明确的引入各个文件就会非常繁琐,此时可以批量引入

如果文件太多,可以批量加载,下面时将 cpp 文件夹下所有的源文件定义成了 SOURCE(后面的源文件使用相对路径)

file(GLOB SOURCE *.cpp *.h)

add_library(
native-lib
SHARED

引入 SOURCE 下的所有源文件

${SOURCE}
)

添加第三方动态库

那么如何添加第三方的动态库呢?

第三方库的存放位置

动态库必须放到 src/main/jniLibs/xxabi 目录下才能被打包到 apk 中,这里用的是虚拟机,所以用的是 x86 平台,所以我们放置一个第三方库 libexternal.so 到 src/main/jniLibs/x86 下面。 libexternal.so 中只有一个 hello.c ,里面只有一个方法:

const char * getExternalString(){
return “string from external”;
}

CMakeLists.txt 的位置

这里将 CMakeLists.txt 重新放到了 app 目录下,和 src 同级,这样方便找到 jniLibs 下面的库。 所以别忘了修改 gradle

externalNativeBuild {
cmake {
path “CMakeLists.txt”
version “3.10.2”
}
}

配置 CMakeLists.txt

cmake_minimum_required(VERSION 3.4.1)

如果文件太多,可以批量加载,下面时将 cpp 文件夹下所有的源文件定义成了 SOURCE(后面的源文件使用相对路径)

file(GLOB SOURCE src/main/cpp/.cpp src/main/cpp/.h)

add_library(
native-lib
SHARED

引入 SOURCE 下的所有源文件

${SOURCE}
)
set_target_properties(native-lib PROPERTIES LINKER_LANGUAGE CXX)

#add_library( # Sets the name of the library.

native-lib

# Sets the library as a shared library.

SHARED

# Provides a relative path to your source file(s).

native-lib.cpp

extra.h )

find_library(
log-lib
log )

引入外部 so=

message(“ANDROID_ABI : ${ANDROID_ABI}”)
message(“CMAKE_SOURCE_DIR : ${CMAKE_SOURCE_DIR}”)
message(“PROJECT_SOURCE_DIR : ${PROJECT_SOURCE_DIR}”)

external 代表第三方 so - libexternal.so

SHARED 代表动态库,静态库是 STATIC;

IMPORTED: 表示是以导入的形式添加进来(预编译库)

add_library(external SHARED IMPORTED)

#设置 external 的 导入路径(IMPORTED_LOCATION) 属性,不可以使用相对路径

CMAKE_SOURCE_DIR: 当前cmakelists.txt的路径 (cmake工具内置的)

android cmake 内置的 ANDROID_ABI : 当前需要编译的cpu架构

set_target_properties(external PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libexternal.so)
#set_target_properties(external PROPERTIES LINKER_LANGUAGE CXX)

引入外部 so end=

target_link_libraries( # Specifies the target library.
native-lib

Links the target library to the log library

included in the NDK.

${log-lib}

链接第三方 so

external
)

使用第三方库

#include <jni.h>
#include
#include <android/log.h>
#include “extra.h”
// VA_ARGS 代表… 可变参数
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR,“JNI”,VA_ARGS);

extern “C”{
const char * getExternalString();
}

extern “C” JNIEXPORT jstring JNICALL
Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
// std::string hello = “Hello from new C++”;
// std::string hello = getString();
=引入外部 so end=====

target_link_libraries( # Specifies the target library.
native-lib

Links the target library to the log library

included in the NDK.

${log-lib}

链接第三方 so

external
)

使用第三方库

#include <jni.h>
#include
#include <android/log.h>
#include “extra.h”
// VA_ARGS 代表… 可变参数
#define LOGE(…) __android_log_print(ANDROID_LOG_ERROR,“JNI”,VA_ARGS);

extern “C”{
const char * getExternalString();
}

extern “C” JNIEXPORT jstring JNICALL
Java_com_wangzhen_jnitutorial_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
// std::string hello = “Hello from new C++”;
// std::string hello = getString();

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-28 11:24:56  更:2021-11-28 11:26:54 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:59:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码