Android NDK开发

Android NDK 开发

NDK 开发其实就是在Android开发中使用C++这种底层开发语言。

环境依赖
  • Android Studio
  • 在Android Studio里使用SDK Manager安装sdk和ndk(建议r16版本)
NDK 开发
最简单的示例

在Android Studio中新建工程,选择Native C++模板;选择这个模板,他会给出一个最基本的能够运行的NDK应用模板。
在这里插入图片描述

基本原理
  • cpp源码会编译成so库,并在apk安装后被复制到一个特定的路径
  • java通过加载这个库,然后使用jni调用so里面定义的接口。
    就这么简单!
原理讲解

工程结构,如下图:相比普通的Android工程,这个工程里面多出了cpp这个文件夹。
在这里插入图片描述
当你打包成apk的时候,这里的CPP源码会首先编译成共享库(.so),然后放进apk里。然后,Java源码通过如下代码加载这个库:
在这里插入图片描述
注意这里面的字符串不是CPP源文件的名称,而是编译后的共享库的名称。CPP源码编译成的so库具体是什么名称,就是在CMakeLists.txt里面定义的。如下图:注意这里的“Sets the name of the library”。
在这里插入图片描述
这个就是需要在System.loadLibrary()里面填的字符串。不过,实际上所有编译出来的共享库,前面都必须要带一个“lib”,所以最终的真实的共享库的名称应该是“libnative-lib.so”。但是你在CMakeLists.txt和System.loadLibrary()里面不需要在前面加上lib前缀,它会自动帮你加的。
还有一个问题,就是System.loadLibrary()里面只是填写库的名称,那么,这个方法具体是去哪里查找这个库呢?查找路径可以通过以下语句获取:

System.getProperty("java.library.path");

在上面这个最简单的Android APP上,这个接口打印出来的是:

/system/lib64:/vendor/lib64

推测如果是很老的那种32bit手机,那么目录就是lib,而不是lib64.
再回头看一下Java源码那张图,加载库是放在一个Java静态块里面执行的,表示整个过程只加载一次。然后29行的代码:

public native String stringFromJNI();

你需要声明这样的用native关键字修饰的方法,它对应CPP源码里面定义的一个方法,然后才能像普通java方法那样调用。
所以这个对应关系是怎么样的呢?查看CPP源码:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_bytedance_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

这个对应的就是这个超级无敌长的函数名:Java+包名+类名+真正函数名:

Java_com_bytedance_myapplication_MainActivity_stringFromJNI

上面的java代码中的方法名stringFromJNI就是这么来的。
这个cpp源码中,第一行引入jni.h头文件,这个头文件里面定义了JNI专属的方法,数据类型和数据结构,比如上面用到的JNIEXPORT, jstring,JNICALL,JNIEnv,jobject。还有extern "C"是必须的。它表示下面这个是一个C方法,而不是C++方法。原因是因为C++为了实现重载,它会自动更改掉函数名,导致Java最后找不到这个方法。当然,如果这个方法不是对Java暴露的接口,就不用加这个修饰了。

运行Apk

打包成apk在手机上运行。此时,你可以用adb查看手机下的这个目录:

adb shell ls /system/lib64 | findstr native

你会看到。。。不,你看不到libnative-lib.so 。你也可以试一下/venfor/lib64,也没有。但是网上确实是说,System.loadLibrary()的加载路径是"java.library.path",怎么回事呢?
现在,你把System.loadLibrary()里面的名字改成一个错误的名字,再打包运行。应用闪退之后,查看logcat,你会看到这么一句话:
在这里插入图片描述
看到“nativeLibraryDirectories”这个词没有?这个就是真正的加载路径,它表示System.loadLibrary()会从这里罗列的路径去找,包括:

/data/app/com.qtc.test-2/lib/arm64
/data/app/com.qtc.test-2/base.apk!/lib/arm64-v8a
/system/lib64
/vendor/lib64

注意,每个手机,lib目录下的arm的名称可能是不同的,你可以通过adb shell ls查看。至于app包名后面为什么有个-2,是因为我的手机上已经有一个名为com.qtc.test的包了,如果没有,那么应该是-1

(注意,这个后缀不一定是这么一个规律。我在用最新的华为P30手机测试的时候,后缀就是一串无规律的字母串,像是一个哈希值。一般来说,/data/app/这个目录不root是没有权限访问的,但是却可以访问/data/app/com.qtc.test-1/。大概是为了保护隐私,一些手机在加这个包的后缀的时候不遵循-1, -2的规律。你如果想知道当前包在这个目录下的具体名称,可以在代码里打印。安卓代码是Context.getPackageCodePath(),如果是Unity工程,则是Application.datapath)。

现在我们再把库名称改回来(顺便我把原来的com.qtc.test卸载了),运行,然后查看一下第一个路径:
在这里插入图片描述
找到了!!!
说明,System.loadLibrary()并不只是从“java.library.path”里面查找。关于实际的搜索路径,这篇博客写得很好!

我们再做一件事情:解压apk,得到的目录结构会是这样:
在这里插入图片描述
注意在目录arm64-v8a,armeabi-v7a,x86,x86_64下都各有一份libnative-lib.so。这说明,如果你不手动用ndk-build将cpp编译成so库,那么Android Studio会自动帮你编译出四份so库,它分别对应四种指令集平台。其中arm64-v8a对应64位的CPU,armeabi-v7a对应32位的CPU。x86就只在桌面CPU上应用,一般是模拟器使用。apk在安装的时候会自动判断当前CPU指令集,如果是64位,就拷贝arm64-v8a目录下的so到手机上的arm64下,当然如果是64位的CPU但是没有64位的so库,就拷贝32位的so库,理论上v8a是兼容v7a的,反之则不行。如果是32位CPU,就只拷贝armeabi-v7a的so到arm下。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值