mark:以前学过的东西,现转到csdn以作记录

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011418943/article/details/79449108

作者: 夏至 欢迎转载,但保留这段申明

http://blog.csdn.net/u011418943/article/details/79449108

关于 JNI 的基础就不多说了,这篇文章主要讲解如何在 AS 中用 ndk-build 和 用 cmake 去构建我们的 JNI 工程,并总结他们的特点以及优缺点。

本文代码链接:https://github.com/LillteZheng/JniDemo.git

通过这篇文章,你讲学习到:

用 AS 构建自己的 JNI 工程

学会使用 mk 去加载自己的 so 文件

学会调用第三方 so 或 .a 的方法 (工程提供测试的 so )

学会使用 camke,体验丝般顺滑的 C/C++ 编写体验

1、ndk-build

先用传统的方式,即 ndk-build 的方式

首先,新建一个工程,配置 ndk 的环境:

然后,新建一个工程,在 gradle.properties 中,添加如下:

android.useDeprecatedNdk=true

接着,先使用 AS 自带的功能,在 module 中的 build.gradle 添加 so 库的名字:

新建一个类,用来生成 native 方法:

public class JniUtils {

static {

System.loadLibrary("JNIDemo");

}

public static native String getName();

}

1

2

3

4

5

6

7

接着,就是生成 class 文件了,先 build module 一下

(如果嫌麻烦,可以跳到快捷设置,不用写这么麻烦,不过我建议你还是操作一遍)

打开 cmd,或者用 as 的 Terminal ,这里用cmd演示,去到你的工程路径下,生成我们需要的 .h 文件 :

首先,我们需要设置 src 的根路径 ,如果不先设置根路径,一般会提示找不到类,用 set classpath 的命令,指向你的 java 文件:

然后,再使用 javah 去生成 .h 文件,即上面的 JniUtils:

就可以看到生成了 .h 文件,如下图:

接着,我们新建一个 jni 的文件夹:

把 .h 文件复制过去,然后复制多一份 .h 文件,后缀名改为.cpp ,如下:

#ifdef __cplusplus#endif#include <jni.h>

extern "C"

JNIEXPORT jstring JNICALL Java_com_zhengsr_jnidemo_JniUtils_getName

(JNIEnv *env, jobject obj) {

return env->NewStringUTF("这是个 jni 测试");

}

1

2

3

4

5

6

7

8

9

make module 一下,会发现,已经生成了 so 库:

最后再 MainActivity 中调用即可看到效果。

1.1、配置快捷方式

如果每次都这样,想想都觉得崩溃,这个时候,我们就可以配置快捷方式,这样就不用每次都开终端去输入,怎么配置呢?

去到 Setting 选择 external tools ,新建一个 ,命名为 javah,(忽略我配置的 ndk_build,后面会用到):

配置以下参数:

program 为要执行的命令

parameters ,先设置路径,然后就是把命令敲一遍,注意是 /src/main/jni ,如果你的路径不一样,记得修改

working directory 是 .h 的生成路径

然后在你的 jni 类中,按住右键:

之后会弹出一个弹窗,可以自己输入 .h 的名字 (ps:先把以前的去掉):

效果如下:

接下来的步骤,就跟上面的差不多了,这里就不赘述了。

1.2、编写自己的 mk

上面已经说过,我们并没有 mk 的文件,这是因为 as 用了自身的mk,如果我们需要引入第三方的so或者.a,或者需要特殊配置时,就需要编写自己的 mk 文件了。

关于 mk 的学习,可以参考这篇文章 (写得还不错),这里就不多说了:

http://blog.csdn.net/mynameishuangshuai/article/details/52577228

回到 build.gradle ,先把上面的 ndk 的属性去掉,然后添加:

在 jni 路径,添加 Android.mk 和 application.mk :

首先,先编写 Android.mk :

#设置路径LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := jniutils

LOCAL_SRC_FILES := jniutils.cpp

include $(BUILD_SHARED_LIBRARY)

1

2

3

4

5

6

7

8

可以看到,我们把 jni 的 so 的名字改成了 jniutils,用于区别,记得改 JniUtils 中 loadLibrary 的名字,不然报错了,别怪我没提醒;

Application.mk 则如下:

APP_ABI:=all

1

指定生成所有平台下的 so。

由于我们使用了 mk 编译了,as 并不知道,我们要像刚才配置 javah 那样,配置一下 ndk-build ,配置信息如下:

参数已经解释过了,然后在 jni 的文件夹上右键,编译一下:

可以看到,生成的 so 包如下:

这样,我们就完成了我们的编译了,run 一下,就可以看到你想要的结果了。

1.3、在 build.gradle 中配置编译

从上面中,我们可以看到,如果改动了 .cpp 的方法,每次都要 ndk-build 一下,其实是很烦的;

所以我们可以在 build.gradle 中,添加任务,在每次 run 的时候,自动编译。

build 应该这样配置:

完整 build.gradle 文件如下:

apply plugin: 'com.android.application'

android {

compileSdkVersion 26

buildToolsVersion "26.0.2"

defaultConfig {

applicationId "com.zhengsr.jnidemo"

minSdkVersion 19

targetSdkVersion 26

versionCode 1

versionName "1.0"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

}

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

}

}

sourceSets {

main{

jni.srcDirs=[]; //禁用as自动生成mk

jniLibs.srcDirs 'src/main/jniLibs' //这里设置 so 生成的位置

}

}

//设置编译任务,编译ndkBuild

tasks.withType(JavaCompile) {

compileTask -> compileTask.dependsOn 'ndkBuild'

}

}

task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {

//应该都看得明白,就不解释了

commandLine "C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk\\ndk-bundle\\ndk-build.cmd",

'NDK_PROJECT_PATH=build/intermediates/ndk',

'NDK_LIBS_OUT=src/main/jniLibs',

'APP_BUILD_SCRIPT=src/main/jni/Android.mk',

'NDK_APPLICATION_MK=src/main/jni/Application.mk'

}

....

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

接下来,我们在 jniutils.cpp 中,把返回的字符串改一下:

直接run,可以看到效果:

1.4、引入第三方 so,.a 包

很多时候,像一些比较涉及加密或者核心代码,都是用 so 库来实现,java 只要编写对应的 jni 即可,这里就涉及到引入第三方包的问题,怎么写呢?

首先,我们需要有个第三方的 so 库,这里我从网上下载了一个,下载地址在 github 的demo 中;目录如下:

在引入第三方 so 库的时候,需要特别注意的是,这个 so 你要选择好版本,如果你的 so 是32的,而你在 appliaction.mk 的API版本中,选择了 all 或者 arm64-v8a等,那么编译肯定是报错的;

一般手机是 armeabi ,模拟器是 x86 ,机顶盒等板子是 arm64-v8a 的, 我的模拟器刚好是 x86_64 的,所以,这里引入的 so 库是 x86_64 下的,导入之后,目录如下:

重新编写 mk 文件:

LOCAL_PATH := $(call my-dir)

#引入第三方 so include $(CLEAR_VARS)

LOCAL_MODULE := vvw

#这里的so名字叫做 vvw,规则是lib 与 so 之间的名字,在加载时使用 vvw,如果是# libvvw1.0.so,则在 loadlibaray 用 "vvw1.0",module 名字只是给下面加载的LOCAL_SRC_FILES := libvvw.so

LOCAL_EXPORT_C_INCLUDES := includeinclude $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := jniutils

LOCAL_SRC_FILES := jniutils.cpp

LOCAL_LDLIBS :=-llog

#引入第三方编译模块LOCAL_SHARED_LIBRARIES := \

vvw

include $(BUILD_SHARED_LIBRARY)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

与前面相比,多了一个第三方模块的引入。接着,我们要指定 application.mk 的 API:

#模拟器是 x86_64 的

APP_ABI := x86_64

1

2

如果导入的工程报错,可以试着 APP_ABI 为 x86 ,替换相应的 so 。

接着,我们在 java 类这里,添加一个 调用 so 方法的 java 方法 getIntValue :

public class JniUtils {

static {

System.loadLibrary("jniutils");

System.loadLibrary("vvw");

}

public static native String getName();

public static native int getIntValue(int a,int b);

}

1

2

3

4

5

6

7

8

9

10

11

JniUtils.cpp 的代码如下:

#include <jni.h>#include <string>#include "include/vvwUtils.h"

extern "C" jstring Java_com_zhengsr_jnidemo_JniUtils_getName(

JNIEnv* env,

jobject /* this */) {

return env->NewStringUTF("获取两数字之和:");

}

extern "C" jint Java_com_zhengsr_jnidemo_getIntValue(

JNIEnv* env,

jobject obj,jint a,jint b) {

# addMethod 为 libvvw.so 的方法

return addMethod(a,b);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

修改一下 MainActivity.java

效果如下;

2、使用 cmake 的方式

上面的 demo 中,写 c/c++ 的时候,并没有任何提示,这真的是让人崩溃啊,写了都不知道写对了没有。所以,在 as 2.2.2 之后,as 就支持用 cmake 的方式去编写 jni 了,而使用 camke,除了 c/c++ 有提示之外,在 jni 的配置上,也更加的人性化,如果是新建项目,我是推荐你用 camke 的构建方式去编写。

官方中文文档如下

https://developer.android.google.cn/studio/projects/add-native-code.html

首先,在新建工程的时候,勾选上 c++ support ( 3.0 往下拉才有)

一路 next ,然后有两个提示框:

这两个也勾选上,解释如下:

Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

工程已经给了我们一个 jni 的例子,而它的编译方式就是通过 CMakeLists.txt 来构建的。

下面是对 CMakeLists.txt 的解释,由于篇幅,这里会删掉一些注释:

cmake_minimum_required(VERSION 3.4.1)

#这里会把 native-lib.cpp 转换成共享库,并命名为 native-libadd_library( # 库的名字

native-lib

# 设置成共享库

SHARED

# 库的原文件

src/main/cpp/native-lib.cpp )

#如果需要使用第三方库,则可以使用 find_library 来找到,比如这里的 log 这个库find_library(

# so库的变量路径名字,在关联的时候是使用

log-lib

#你需要关联的so名字

log )

#因为使用了第三方库,所以,这里我们通过 link 这这个库添加进来target_link_libraries( # 关联的so的路径变量名

native-lib

#把上面的 log 中的关联的变量名 log-lib 添加进来即可

${log-lib} )

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

如果要添加库,则使用 add_library,括号以空格区分,如果要使用第三方库,比如打印的 log 这个库,就通过 find_library 的方式添加,最后通过 target_link_libraries 把源文件的库,和第三方的库变量名引进来,注意第三方库是个路径变量名,所以 ${}的方式引用。

相较传统配置,如果对 mk 不熟悉的小伙伴,估计会很喜欢 cmake 的方式.

2.1 用 cmake 写 jni

按照上面的方式,新建 JniUtils.java 这个类:

public class JniUtils {

static {

System.loadLibrary("jniutils");

}

public static native String getName();

}

1

2

3

4

5

6

然后编写,jniutils.cpp,你会惊喜地发现,竟然有提示!!

#include <jni.h>#include <string>extern "C"

jstring

Java_com_zhengsr_jnidemo_camke_JniUtils_getName(

JNIEnv* env,

jobject /* this */) {

std::string hello = "这是使用 camke 的编译方式啦";

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

}

1

2

3

4

5

6

7

8

9

10

接下来就是 用 add_library 的方式,我们把 jniutils 加进来:

同步一下即可,修改一下 mainactivity,运行,效果如下:

可以看到,使用 cmake 的方式,除了有代码提示,在添加类上,简直不能太方便了。

2.2、引入第三方 so 库

官方推荐,每次库变动之前,先 clean project 一下,所以,先clean 一下,免得出现找不到 so 的情况;

接着,我们添加一下第三方so,还是上面的 libvvw.so ,目录如下:

接着,我们需要制定一下 ndk 编译时的 类型,不然会增加一个 mips 的类型,这个是编不过的。

接着,则是配置最重要的 CMakeLists.txt 了,具体如下:

cmake_minimum_required(VERSION 3.4.1)

find_library( # Sets the name of the path variable.

log-lib

# Specifies the name of the NDK library that

# you want CMake to locate.

log )

#导入第三方so包,并声明为 IMPORTED 属性,指明只是想把 so 导入到项目中add_library( vvw

SHARED

IMPORTED )

#指明 so 库的路径,CMAKE_SOURCE_DIR 表示 CMakeLists.txt 的路径set_target_properties(

vvw

PROPERTIES IMPORTED_LOCATION

${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libvvw.so )

#指明头文件路径,不然会提示找不到 so 的方法include_directories(scr/main/cpp/include/ )

add_library(jniutils SHARED src/main/cpp/jniutils.cpp)

target_link_libraries( # Specifies the target library.

jniutils

#关联第三方 so

vvw

${log-lib} )

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

注释已经写得很清楚了,关键是要写对 so 的路径,不然会提示 missing and no rules to make 等错误;

jniutils.cpp 的代码如下:

#include <jni.h>#include <string>#include "include/vvwUtils.h"

extern "C" jstring Java_com_zhengsr_jnidemo_1camke_JniUtils_getName(

JNIEnv* env,

jobject /* this */) {

std::string hello = "这是使用 camke 的编译方式啦,还获取到两数之和啦: ";

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

}

extern "C" jint Java_com_zhengsr_jnidemo_1camke_JniUtils_getIntValue(

JNIEnv* env,

jobject obj,jint a,jint b) {

return addMethod(a,b);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

效果如下:

3、总结

不管是 ndk-build 传统的方式,还是 cmake 的方式,都有一定的可取之处,当然,在我看来, cmake 无论在学习成本还是代码编写提示上都要优于 ndk-build。

如果是新建项目,我建议还是用 cmake 的方式,毕竟只 c/c++ 有提示这一点,我相信你也拒绝不了的。

当然,实际项目上,还有动态加载 so 的方法,这里就不深入了,这里就当做个 入门介绍吧。

这是一篇让你少走弯路的 JNI/NDK 实例教程(转)相关推荐

  1. 英语这样学最有效------少走弯路的学习方法

    本文主要涉及英语的学习方法,以及相关资料的推荐.建议耐心看完.如果这点耐性都没有,那你还指望能做好什么? 1.前言 国人学了多年英语,但效果并不好.造成这种现象的原因很多,但总的来说,是懒和没找对方法 ...

  2. 带你少走弯路:五篇文章学完吴恩达机器学习

    本文是吴恩达老师的机器学习课程[1]的笔记和代码复现部分,这门课是经典,没有之一.但是有个问题,就是内容较多,有些内容确实有点过时. 如何在最短时间学完这门课程?作为课程的主要翻译者和笔记作者,我推荐 ...

  3. 还在担心零基础绘画?这篇文章让你少走弯路!

    零基础学想画画应该怎么学?这些技巧可以让你少走弯路!经常有人问我,我没有绘画基础,现在学画画还能学好吗?会不会很难学?学好之后好不好找工作等等问题!梵高学院就此系统地回答一下这些问题,帮助有需要的同学 ...

  4. 写给30岁以下年轻人的话,人生的感悟,不是鸡汤,愿你们的人生少走弯路。

    以下都是我给年轻人写的话,愿你们的人生少走弯路. 幼儿篇 写给父母 学前的孩子是父母的宝宝,一个健康的身体是最重要的,其余的都是次要的. 这些话语在我以前看来都是废话,每个人都向上天祈求"一 ...

  5. 人生少走弯路的十条忠告

    刚刚走上社会的年轻人,充满了蓄势待发的豪情.青春的朝气.前卫的思想,梦想着丰富的待遇和轰轰烈烈的事业.可是,社会毕竟是一 所包罗万象.喧嚣复杂的大学校,这里没有寒暑假,拒绝虚假和肤浅,更拒绝空想和庸碌 ...

  6. 【少走弯路】关于安卓抓包的个人经验

    [少走弯路]关于安卓抓包的个人经验 前几天发了一个关于安卓抓包的一个文章,当时就是将之前记得随手一发,这次来讲一下关于安卓抓包的一些个人经验(可能会和那篇文章有重复的地方),通常我们在对app进行测试 ...

  7. 计算机二战选学校,二战失败箴言:如何少走弯路直达终点!

    二战失败箴言:如何少走弯路直达终点! 摘要:如果说考研二战也失败了,你的复习方法可能存在一些问题,当然排除考场上的突发因素.今天要介绍的前辈虽然难两次考研都没能成功,但是 作者 Thanksjesus ...

  8. 亚马逊、Lazada、速卖通、Shopee、阿里国际、沃尔玛、Shopify、mercari、Newegg自养号测评,卖家如何少走弯路?

    今天我想带大家了解一下,亚马逊.Lazada.速卖通.Shopee.阿里国际.沃尔玛.独立站.mercari.Newegg测评(补单)行业的所有的知识框架,看完这篇文章,一定能够让你对测评(补单)有一 ...

  9. 给你整理好了,新手必备的设备和工具,让你做自媒体少走弯路

    新手们在做自媒体的初期都是很迷茫的.今天大周给大家分享在做自媒体的前期需要投资多少.需要准备哪些必要的设备.新手该如何选择领域.推荐常用的工具等满足自媒体的日常需求,都是个人的实战经验.希望能帮助你们 ...

最新文章

  1. 简明 Git 命令速查表
  2. 【Java深入研究】10、红黑树
  3. 【codeforces 103E】 Buying Sets
  4. spdlog linux编译出错,Linux下编写Makefile引入第三方库
  5. JAVA基础——异常详解
  6. 老年人手里有多少积蓄,该不该告诉子女?
  7. java微调器_java-更改微调器标题栏样式
  8. UnityShader之遮挡透明
  9. Android中使用SurfaceView和Canvas来绘制动画
  10. Linux:管线命令
  11. centos 网络流量监控方法总结
  12. 【C++】模板(初级)
  13. 中科院大牛博士是如何进行文献检索和阅读的(好习惯受益终生)
  14. 人工神经网络与深度神经网络
  15. 专访实在智能孙林君:颠覆传统RPA的实在IPA模式,如何做到真正人人可用?
  16. 保研文书——个人陈述模板
  17. java递归怎么写_Java 基本的递归写法
  18. 千与千寻 中日歌词与罗马音译(最准确啦)
  19. Spring中用到的设计模式
  20. 算法设计与分析:动态规划(3)-序列联配问题(以算代存)

热门文章

  1. 关于学习JavaScript!
  2. 华为认证HCIP的持证人数
  3. C语言的 d触发器程序,一个带直接置0/1端的D触发器置为0或1有哪几种?
  4. 51单片机const unsigned char number[16]是啥意思
  5. 批量下载网页中所有的PDF文档
  6. Macbook matlab启动无响应问题解决方案
  7. c语言显示格式错误,C语言,输出里多空格,提交格式错误,怎么改下
  8. 仰望星空 ecnu
  9. 基于SSM的演唱会网上订票系统
  10. 龙迅HDMI接口信号转换