Android JNI和NDK 自动创建Hello world

Android NDK 与 JNI

为什么要有这两个?

    对于Android的嵌入式软件开发、调用到硬件、与操作系统交互或者执行一些比较重要的行为来说,一般是通过与C或C++结合进行,Java调用本地C/C++通常的做法就是将编译好的C/C++动态库.so放到我们的Android APK中,然后加载该库进行调用即可,但是如果我们需要在我们的项目中编写自己的本地代码怎么办呢?这就需要用到两个东西:NDK和JNI

1、NDK

What?

    NDK的全称呼是:Native Development Kit,它是一个本地开发工具集,能让我们在Android Studio上使用C和C++代码,并能生成.so库与应用一起打包成APK,也就是一个方便我们Native开发用的一套工具。

2、JNI

    上面有了NDK,我就可以构建出.so库共Java程序调用,但是如何编写出能够供java程序调用的C、C++代码还需要JNI提供

What?

    JNI全称Java Native Interface,即 Java本地接口,提供一套规范增强了 Java 与 本地代码交互的能力, 使得Java 与 本地其他类型语言(如C、C++)交互,是Java和C、C++联系的桥梁

使用NDK与JNI的优势

  1. 使用C、C++代码编写的程序执行效率更高
  2. 使用C、C++代码编写的程序更难进行反编译得到源代码,从而提高安全性
  3. 使用C、C++代码编写的程序代码不仅可用于android中,还可以嵌入到其他平台进行使用
  4. NDK提供了交叉编译器,可生成特定的CPU平台动态库
  5. NDK还可以方便的使用其他语言开发的开源库

另一个工具CMake

使用NDK构建代码的主要方法有以下三种:

  1. 基于Make的ndk-build。
  2. CMake的。
  3. 用于与其他构建系统集成或与configure基于项目的项目一起使用的独立工具链。
使用外部构建工具原因:

    当我们编写本地C、C++代码后,需要使用NDK一个一个编译生成目标动态库,期间需要定义一系列规则,如制定编译文件,指定交叉编译的平台等。这样导致了整个过程十分繁琐,因此就有了make自动化编译工具,它依据一个makefile规则文件进行批处理,相当与一个脚本。
    而对于一个大工程,编写makefile实在是件复杂的事,于是人们设计了一个工具CMake,它能够自动生成makefile和其它文件,从而帮助程序员减轻负担,我们需要关注的就是CmakeList.txt的编写工作。

开始–Hello World

本系列使用的是CMake进行外部构建
因此需要Android Studio2.2本版本以上(2.2版本以后开始支持CMake编译)

安装工具

安装NDK开发所需的工具,直接打开android studio中的SDK Manager勾选SDK Tools下的NDK和CMake进行安装即可。

创建项目

新建一个支持Native的Android工程,咔咔下一步就好了。
在这里插入图片描述
完成后整个app项目的结构如下图:
在这里插入图片描述
接下来看一下与普通项目不一样的地方
1. 首先看到build.gradle的内容:新增了两个externalNativeBuild

android {
    compileSdkVersion 28
    buildToolsVersion '28.0.3'

    defaultConfig {
        applicationId "com.example.myapplication"
        minSdkVersion 21
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
            	//指定C++版本,这里用了个默认
                cppFlags ""
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
        	//指定CMakeLists.txt所在的位置
            path "src/main/cpp/CMakeLists.txt"
            //指定CMake版本号
            version "3.10.2"
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

更多的常用CMake属性:
buildStagingDirectory:配置native构建后文件的存放路径

cmake {
		buildStagingDirectory "./outputs/cmake"
}

2. CMakeLists.txt内容如下:

# 指定 cmake 的最小版本
cmake_minimum_required(VERSION 3.10.2)
# 设置项目名称
project("myapplication")
# 创建库,设置编译类型
add_library( # 设置库的名称
             native-lib
             # 设置编译类型为一个动态库.so,static表示静态库.a,不指定表示为一般可执行文件
             SHARED
             # c++代码的所在位置,相对路径
             native-lib.cpp )

# 查找到指定的预编译库,并将它的路径存储在变量中
find_library( # 设置路径变量的名称.
              log-lib
              # 指定NDK库的名称
              log )

# 设置 target 需要链接的库进行链接
# 在 Windows 下,系统会根据链接库目录,搜索xxx.lib 文件,
# Linux 下会搜索 xxx.so 或者 xxx.a 文件,如果都存在会优先链接动态库(so 后缀)。
target_link_libraries( native-lib
                       ${log-lib} )

3.native-lib.cpp 是AS自动给我们生成的c++示例代码,内容如下:

#include <jni.h>
#include <string>
//与c代码兼容
extern "C" 
JNIEXPORT jstring JNICALL
//JNIEnv 结构体指针
//env二级指针
//代表Java运行环境,可调用Java中的代码
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* 相当于java中的this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

每个native函数,都至少有两个参数(JNIEnv*, jobject)

  1. 当native方法为静态方法时:
    jclass 代表native方法所属类的class对象(这里就是MainActivity.class)
  2. 当native方法为非静态方法时:
    jobject 代表native方法所属的对象

数据类型对比
Java基本数据类型与JNI数据类型的映射关系如下表

Java类型JNI类型
booleanjboolean
booleanjboolean
bytejbyte
charjchar
shortjshort
intjint
longjlong
floatjfloat
doublejdouble
voidvoid

引用类型映射关系如下表

Java类型JNI类型
Stringjstring
objectjobject
byte[]jByteArray
object[](String[])jobjectArray

4. MainActivity:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 调用本地方法
        findViewById<TextView>(R.id.sample_text).text = stringFromJNI()
    }
	//声明Native方法
  	// MainActivity中新增一个native方法,将会在加载的native-lib下查找
    external fun stringFromJNI(): String

    companion object {
        //加载native-lib动态库
        init {
            System.loadLibrary("native-lib")
        }
    }
}

5. CPU架构
运行项目后将会在以下路径生成.so动态库:
在这里插入图片描述
这里可以看到一共有四个文件夹:
arm64-v8a、armeabi-v7a、x86、x86_64
这就是我们通常所说的ABIs,Android 设备的CPU类型,不同的CPU支持的指令集是不一样的。同时由于C/C++语言本身不具备跨平台的能力,所以必须针对不同的cpu类型编译出不同的.so库,android设备在加载.so库时将会在对应的文件夹下查找。所以为了减少apk大小同时保证很好的运行,通常在编译时需要为不同设备编译出对应的.so库即可,而不是进行全部编译适配。
以下是对应关系:

  1. armeabiv-v7a: 第7代及以上的 ARM 处理器。2011年15月以后的生产的大部分Android设备都使用它.
  2. arm64-v8a: 第8代、64位ARM处理器,很少设备,三星 Galaxy S6是其中之一。
  3. armeabi: 第5代、第6代的ARM处理器,早期的手机用的比较多。
  4. x86: 平板、模拟器用得比较多。
  5. x86_64: 64位的平板。

6. 最终结果:
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值