android ndk develop,Android——JNI与NDK

对Android 开发艺术探索的阅读笔记... 待我 AS 里配置好 NDK 后再来完善,大流程没毛病....未完待续

一、作用

JNI:使得 Java 可以与本地其他类型语言(如C、C++,编译成的so库)交互 。即在 Java 代码中调用 C、C++ 等语言的代码,或 C、C++ 代码调用 Java 代码。

NDK:可以帮助我们在 Android Studio 中快速开发 C、C++ 的动态库(写好C、C++ 源文件,然后将他们编译成so库),并自动将 so 和应用一起打包成 apk

可通过 NDK 在 Android 中使用 JNI 与本地代码(如C、C++)交互

二、概念

1.JNI(Java Native Interface,Java 本地接口)

JNI 是属于 Java 的,与 Android 无直接关系

JNI 是为了方便 Java 调用 C、C++ 等本地代码所封装的一层接口。Java 的优点是跨平台,但作为优点的同时,其在和本地交互时就出现了短板。Java 的跨平台特性导致其本地交互的能力不够强大,一些和操作系统相关的特性 Java 无法完成,于是 Java 提供了 JNI 专门用于和本地代码交互,这样就增强了 Java 语言的本地交互能力。通过 JNI,用户可以调用C、C++所编写的本地代码。

2.NDK(Native Development Kit,是 Android 的一个开发工具包)

NDK 是属于 Android 的,和 Java 无直接关系

NDK 是 Android 提供的一个工具集合,通过 NDK 可以在 Android 中更加方便地通过 JNI 来访问本地代码,如C、C++。NDK 还提供了交叉编译器,开发人员只需要简单地修改 mk 文件就可以生成特定 CPU 平台的动态库。

使用 NDK 的好处:

提高代码安全性

so 库反编译比较困难,从而提高了 Android 程序的安全性

可方便地使用目前已有的 C、C++开源库

提高程序在某些特定情形下的执行效率,但并不能明显提升 Android 程序的性能

三、使用

1. JNI 开发流程

自己制作so库给自己用。

(1)在 Java 中声明 native 方法

package com.example.myapplication.jnitest;

import java.lang.System;

// Java 调用 JNI 方法

public class JniTest {

static{

System.loadLibrary("jni-test");

}

public static void main(String[] args) {

JniTest jniTest = new JniTest();

System.out.println(jniTest.get());

jniTest.set("hello world");

}

// 这两个 native 方法就是需要在 JNI中实现的方法

public native String get();

public native void set(String str);

}

代码中声明了两个native方法,这两就是需要在 JNI 中实现的方法。

在JniTest的头部加载动态库,其中jni-test是so库的标志,完整的名称是libjni-test.so,这是加载so库的规范。

(2)编译 Java 源文件得到.class文件,然后通过javah命令导出 JNI 的头文件

.java文件的路径是MyApplication/app/src/main/java/com/example/myapplication/jnitest

进入上述路径运行

// 编译得到 class 文件

javac ./JniTest.java

在MyApplication/app/src/main/java/com/example/myapplication/jnitest下得到.class文件

然后,进入到MyApplication/app/src/main/java,运行下面指令,在该路径下生成头文件com_example_myapplication_jnitest_JniTest.h。用javah 导出类的头文件, 常见的错误及正确的使用方法

// 导出 JNI 的头文件

javah com.example.myapplication.jnitest.JniTest

/* DO NOT EDIT THIS FILE - it is machine generated */

#include

/* Header for class com_example_myapplication_jnitest_JniTest */

#ifndef _Included_com_example_myapplication_jnitest_JniTest

#define _Included_com_example_myapplication_jnitest_JniTest

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class: com_example_myapplication_jnitest_JniTest

* Method: get

* Signature: ()Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_com_example_myapplication_jnitest_JniTest_get

(JNIEnv *, jobject);

/*

* Class: com_example_myapplication_jnitest_JniTest

* Method: set

* Signature: (Ljava/lang/String;)V

*/

JNIEXPORT void JNICALL Java_com_example_myapplication_jnitest_JniTest_set

(JNIEnv *, jobject, jstring);

#ifdef __cplusplus

}

#endif

#endif

函数名的命名规则:Java_包名_类名_方法名

Java 和 JNI 的数据类型之间的对应关系:ref 参考文献

(3)自己实现 JNI 方法

JNI 是指 Java 中声明 native 方法,可以选择 C++ 来实现。

在工程的主目录main下创建一个子目录,命名为jni,将之前通过javah生成的头文件拷贝到该目录下,接着创建test.cpp文件,实现如下。

c06fe36a4c8a

我的存储路径

下面直接拷贝书中的实现....

#include "com_example_myapplication_jnitest_JniTest.h"

#include

JNIEXPORT jstring JNICALL Java_com_example_myapplication_jnitest_JniTest_get(JNIEnv *env, jobject thiz) {

printf("invoke get in c++\n");

return env->NewStringUTF("Hello from JNI !");

}

JNIEXPORT void JNICALL Java_com_example_myapplication_jnitest_JniTest_set(JNIEnv *env, jobject thiz, jstring string) {

printf("invoke set from C++\n");

char* str = (char*)env->GetStringUTFChars(string,NULL);

printf("%s\n", str);

env->ReleaseStringUTFChars(string, str);

}

(4)编译so库并在 Java 中调用

so库的编译这里采用gcc,切换到jni目录下MyProjects/AndroidStudioProjects/MyApplication/app/src/main/java/com/example/myapplication/jni,编译指令如下。

gcc -shared -I /Library/Java/JavaVirtualMachines/jdk1.8.0_192.jdk/Contents/Home/include -fPIC test.cpp -o libjni-test.so

有可能会提示找不到jni_md.h,去本机的 jdk 目录下找到jni_md.h把它拷贝到XXXX/include下。

c06fe36a4c8a

生成so库

在 Java 中调用

main$ java -Djava.library.path=jni java.com.example.myapplication.jnitest.JniTest

输出日志

???????

2. NDK 开发流程

自己制作so库给自己用。

NDK 的开发是基于 JNI 的。简化了上述 JNI 开发流程。

从官网下载 NDK ,android-ndk-r21-darwin-x86_64,下载完成后将解压路径设置为:Android Studio 的sdk目录里,并命名为ndk-bundle。

这样做的好处:启动 Android Studio 时,AS 会自动检查它并直接添加到ndk.dir中,那么在使用时,就不用配置 AS 与 NDK 的关联工作

c06fe36a4c8a

解压路径

c06fe36a4c8a

AS 自动检测 NDK

下载完成后为 NDK 配置环境变量,修改.bash_profile

在终端运行命令ndk-build

c06fe36a4c8a

说明配置成功

(2)创建一个 Android 项目,并声明所需的 native 方法

package com.example.myapplication.jnitest;

import android.os.Bundle;

import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.myapplication.R;

public class JniActivity extends AppCompatActivity {

static {

System.loadLibrary("jni-test");

}

private TextView mShowTv;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_jni);

mShowTv = findViewById(R.id.activity_jni_show_tv);

mShowTv.setText(get());

}

public native String get();

public native void set(String string);

}

(3)实现 Android 项目中所需的 native 方法

在main下创建jni文件夹,jni下创建3个文件:test2.cpp、Android.mk、Application.mk。

// test.cpp

同上文,只是方法名称有变化

//

// Created by yanglijin on 2020-04-11.

//

#include "com_example_myapplication_jnitest_JniTest.h"

#include

JNIEXPORT jstring JNICALL Java_com_example_myapplication_jnitest_JniActivity_get(JNIEnv *env, jobject thiz) {

printf("invoke get in c++\n");

return env->NewStringUTF("Hello from JNI !");

}

JNIEXPORT void JNICALL Java_com_example_myapplication_jnitest_JniActivity_set(JNIEnv *env, jobject thiz, jstring string) {

printf("invoke set from C++\n");

char* str = (char*)env->GetStringUTFChars(string,NULL);

printf("%s\n", str);

env->ReleaseStringUTFChars(string, str);

}

// Android.mk

???

// Application.mk

???

APP_ABI:表示 CPU 的架构平台的类型,目前市面上常见的架构平台有 armeabi、x86、mips,其中在移动设备中占据主要地位的是 armeabi,这也是大部分 apk 中只包含 armeabi 类型的 so 库的原因。默认情况下,NDK 会编译产生各个 CPU 平台的 so 库,通过 APP_ABI 选项即可指定 so 库的 CPU 平台的类型,这样 NDK 就只会编译特定平台下的 so 库。

(4)通过 ndk-build 命令编译产生 so 库

切换到jni目录的父目录,然后通过ndk-build命令编译产生so库

这个时候 NDK 会创建一个和jni目录平级的目录libs,libs下面存放的就是so库。ndk-build 命令会默认置顶jni目录为本地源码的目录名,如果源码存放目录名不是jni,则 ndk-build 无法编译成功。

(5)在 AS 中使用 so 库

在app/src/main中创建目录jniLibs,将生成的so库复制到jniLibs中,然后 AS 即可编译运行成功。

3. JNI 调用 Java 方法的流程

(1)先通过类名找到类;(2)通过方法名找到方法的 id;(3)调用这个方法。

如果是调用 Java 中的非静态方法,需要构造出类的对象后再调用它。

(1)Java 中定义一个静态方法供 JNI 调用

/**

* JNI调用Java方法

*/

public class InvokeByJni {

private static final String TAG = "InvokeByJni";

public static void methodCalledByJni(String msgFromJni) {

Log.d(TAG, "methodCalledByJni()::msg=" + msgFromJni);

}

}

(2)JNI 中调用上面定义的静态方法

void callJavaMethod(JNIEnv * env, jobecjt thiz) {

jclass clazz = env - > FindClass("com/example/myapplication/jnitest/JniActivity");

if (clazz == null) {

printf("find class JniActivity error");

return;

}

jmethodId id = env - > GetStaticMethodID(clazz, "methodCalledByJni", "(Ljava/lang/String;)V");

if (id == NULL) {

printf("find method methodCalledByJni error");

return;

}

jstring msg = env - > NewStringUTF("msg send by callJavaMethod in testActivity.cpp");

env - > CallStaticVoidMethod(clazz, id, msg);

}

程序先根据类名找到类,然后再根据方法名找到方法,接着再通过JNIEnv对象的CallStaticVoidMethod方法来完成最终的调用过程。

4.作为伸手党 如何在 AS 中使用so库

四、参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值