Android NDK、JNI入门

1.背景

本来一直在做商城类的项目舒坦着,突然老板拿了一块Android的主板和芯片过来,说我们打算做一款自动售货机,从没做过这类项目的我,当时就一脸懵逼了,芯片、自动售货机,What?还好我依稀记得,这类项目是关于NDK、JNI的,于是,我来了!

2.介绍

  1. 什么是NDK?

NDK全称是Native Development Kit,NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

  1. 为什么使用NDK?
  • 代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。
  • 可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
  • 提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
  • 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
  1. 什么是JNI?

JNI的全称是Java Native Interface,它提供了若干的API实现了Java和其他语言的通信(主要是C和C++)。

  1. 为什么使用JNI?

JNI的目的是使java方法能够调用c实现的一些函数。

  1. 安卓中的so文件是什么?

android中用到的so文件是一个c++的函数库。在android的JNI中,要先将相应的C语言打包成so库,然后导入到lib文件夹中供java调用。

3.下载 NDK 和工具

为了给应用编译和调试原生代码,我们需要以下组件:

  • Android 原生开发工具包 (NDK):这套工具集允许您为 Android 使用 C 和 C++ 代码,并提供众多平台库,让您可以管理原生 Activity 和访问物理设备组件,例如传感器和触摸输入。
  • CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。
  • LLDB:一种调试程序,Android Studio 使用它来调试原生代码。

我们可以在SDK 管理器安装这些组件

  1. 依次打开 Settings>Appearance&Behavior>System Settings>Android SDK>SDK Tools
  2. 勾上LLDB、CMake 和 NDK进行下载
image

4.配置NDK的环境变量

  • 打开File > Project Structure > SDK Location,选择默认NDK的路径
image
  • 复制NDK的路径
image
  • 右击我的电脑>属性>高级系统设置>环境变量>新建,添加一个系统变量NDK_HOME,并把刚才复制的ndk-bundle的路径填上去,记得确认。
image
  • 找到Path系统变量(不需要创建),新建一个%NDK_HOME%,也就是上面NDK_HOME的变量添加进去。
image
  • 在Terminal/cmd中不需要考虑路径,直接输入 ndk-build ,如出现如下内容,则表明NDK环境配置成功!(配置NDK环境前如果已经打开了Android Studio或者Cmd需要重新启动,否则可能没效果!)


    image

5.创建native相关方法

image

5.1 native相关方法去掉报红

取消检测即可,打开 Settings>Editor>Inspections>Android>Missing JNI function 去掉勾选。

image

去掉后,效果如下:

image

6.创建c/c++文件

6.1 生成头文件

  1. Terminal终端,通过下面命令 切换到项目xx\app\目录下。
cd D:\Workspace\NDKFirst\app>

注:由于下面的路径都比较长,我们可以右击相应的目录进行快捷复制:

image
  1. 根据java文件生成c的头文件, 执行如下命令

格式:javah ­d jni -encoding utf-8 ­classpath java文件夹路径 包名+类名

javah ­d jni ­classpath D:\Workspace\NDKFirst\app\src\main\java com.brainbg.ndkfirst.NDKUtils
或者
javah ­d jni -encoding utf-8 ­classpath D:\Workspace\NDKFirst\app\src\main\java com.brainbg.ndkfirst.NDKUtils

其中

  • javah :
  • ­d jni :创建jni目录
  • -encoding utf-8 :指定编码格式为utf-8
  • ­classpath D:\Workspace\NDKFirst\app\src\main\java\ :到java目录的路径
  • com.brainbg.ndkfirst.JNIUtils :包名+类名

注:不加上-encoding utf-8,可能会提示错误: 编码GBK的不可映射字符

  1. 执行后,收缩app目录后重新打开,会发现多了一个jni的目录,com_brainbg_ndkfirst_NDKUtils.h就是新生成的头文件。
image

com_brainbg_ndkfirst_NDKUtils.h内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_brainbg_ndkfirst_NDKUtils */

#ifndef _Included_com_brainbg_ndkfirst_NDKUtils
#define _Included_com_brainbg_ndkfirst_NDKUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_brainbg_ndkfirst_NDKUtils
 * Method:    getStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_brainbg_ndkfirst_NDKUtils_getStringFromJNI
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

中间一段最为核心:

格式:Java_包名类名方法名

6.2 添加 c/c++文件

  • 添加cpp文件

first.cpp

  • 添加c文件

first.c

#include <jni.h>
#include "com_brainbg_ndkfirst_NDKUtils.h"
 JNIEXPORT jstring JNICALL
 Java_com_brainbg_ndkfirst_NDKUtils_getStringFromJNI(JNIEnv* env, jobject obj) {
     return (*env)->NewStringUTF(env,"This is my first jni!");
 }

修改好的内容后,你会留意到上面还有提示:大意就是目前的c/c++文件还不属于项目中的一部分!为此,我们还需要处理build.gradle、Android.mk等文件。

image

7.添加mk文件

7.1 添加 Android.mk文件(必加)

注意mk文件里面不能添加注释,不然编译不通过。

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE        := first-jni
LOCAL_SRC_FILES     := first.c
include $(BUILD_SHARED_LIBRARY)

更多内容,可以直接查看:Android.mk 官方介绍

7.2 添加 Application.mk文件(可选)

APP_PLATFORM := android-16
APP_ABI :=all
  • APP_PLATFORM :指定so库所支持最低的API
  • APP_ABI:指定生成平台的so库

注:不添加Application.mk,会提示Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.

更多内容,可以直接查看:Application.mk 官方介绍

8.编译so库文件

进入app目录,执行ndk-build进行编译

cd D:\Workspace\NDKFirst\app

ndk-build

执行成功后,效果如下

image

同时项目中会得到相应的so包,其中lib为核心,obj为编译中产生的文件,可删除。

image

9.用Gradle链接c++项目

  1. jni目录中右击任意文件选择Link C++ project with Gradle
image
  1. 其中Build System 选择ndk-build ,Project Path 选择Android.mk的路径,而后确认。
image
  1. 完成上面的操作后,app/build.gradle里面会出现如下代码
android {
   ......

   externalNativeBuild {
        ndkBuild {
            path file('jni/Android.mk')
        }
    }
}

当然,下次项目的话,我们直接加入上面代码也可。

10.加载so库、运行app

  • NDKUtils.java
public class NDKUtils {
    public static final String TAG = NDKUtils.class.getSimpleName();

    static {
        try {
            System.loadLibrary("first-jni");  //加载so库
        } catch (UnsatisfiedLinkError e) {
            e.printStackTrace();
            Log.e(TAG,"loadLibrary fail !");
        }
    }

    public static native String getStringFromJNI();
}
  • MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tvContent = findViewById(R.id.tv_content);
        tvContent.setText(NDKUtils.getStringFromJNI());
    }
}
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_content"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello World!" />

</RelativeLayout>
  • 运行后效果
image

到此为止,第一个关于NDK、JNI的Demo已经完成,相关文章,后续可能、应该、大概也会推出吧。

11.下载地址

GitHub项目下载

参考资料

https://developer.android.google.cn/ndk/guides
https://blog.csdn.net/young_time/article/details/80346631
https://yq.aliyun.com/articles/60710?spm=a2c4e.11153940.0.0.11bc68d9CLrDix

作者:Brainbg
GitHub:https://github.com/Brainbg
博客:https://www.brainbg.com/
简书:https://www.jianshu.com/u/94518ede7100
CSDN:https://blog.csdn.net/u014720022

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260