纯干货!Android-NDK开发入门

打印 上一主题 下一主题

主题 969|帖子 969|积分 2922

硬件抽象层

硬件抽象层(Hardware Abstraction Layer缩写),硬件抽象层主要为上层提供尺度表现界面,并向更高级别的 Java API 框架提供表现装备硬件功能。HAL 包含多个库模块,其中每个模块都为特定类型的硬件组件实现一个界面,比方相机或蓝牙模块。当框架 API 要求访问装备硬件时,Android 体系将为该硬件组件加载对应的库模块。
体系运行库和运行环境层

Android Runtime

Android 5.0(API 21)之前,使用的是Dalvik虚拟机,之后被ART所取代。ART是Android操作体系的运行环境,通过运行虚拟机来执行dex文件。其中,dex文件是专为安卓设计的的字节码格式,Android打包和运行的就是dex文件,而Android toolchain(一种编译工具)可以将Java代码编译为dex字节码格式,转化过程如下图。

如上所示,Jack就是一种编译工具链,可以将Java 源代码编译为 DEX 字节码,使其可在 Android 平台上运行。
原生C/C++ 库

许多核心 Android 体系组件和服务都是使用C 和 C++ 编写的,为了方便开发者调用这些原生库功能,Android的Framework提供了调用相应的API。比方,您可以通过 Android 框架的 Java OpenGL API 访问 OpenGL ES,以支持在应用中绘制和操作 2D 和 3D 图形。
应用步伐框架层

Android平台最常用的组件和服务都在这一层,是每个Android开发者必须熟悉和掌握的一层,是应用开发的基础。
Application层

Android体系App,如电子邮件、短信、日历、互联网欣赏和接洽人等体系应用。我们可以像调用Java API Framework层一样直接调用体系的App。
接下来我们看一下怎样编写Android JNI ,以及必要的流程。
NDK

NDK是什么

NDK(Native Development Kit缩写)一种基于原生步伐接口的软件开发工具包,可以让您在 Android 应用中使用 C 和 C++ 代码的工具。通过此工具开发的步伐直接在本地运行,而不是虚拟机。
在Android中,NDK是一系列工具的集合,主要用于扩展Android SDK。NDK提供了一系列的工具可以帮助开发者快速的开发C或C++的动态库,并能自动将so和Java应用一起打包成apk。同时,NDK还集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差别,开发职员只必要简朴修改mk文件(指出“哪些文件必要编译”、“编译特性要求”等),就可以创建出so文件。
NDK配置

创建NDK工程之前,请先包管本地已经搭建好了NDK的相干环境。依次选择【Preferences…】->【Android SDK】下载配置NDK,如下所示。

然后,新建一个Native C++工程,如下所示。

然后勾选【Include C++ support】选项,点击【下一步】,到达【Customize C++ Support】设置页,如下所示。

然后,点击【Finish】按钮即可。
NDK 项目目次

打开新建的NDK工程,目次如下图所示。

我们接下来看一下,Android的NDK工程和普通的Android应用工程有哪些不一样的地方。首先,我们来看下build.gradle配置。
apply plugin: ‘com.android.application’
android {
compileSdkVersion 30
buildToolsVersion “30.0.2”
defaultConfig {
applicationId “com.xzh.ndk”
minSdkVersion 16
targetSdkVersion 30
versionCode 1
versionName “1.0”
testInstrumentationRunner “androidx.test.runner.AndroidJUnitRunner”
externalNativeBuild {
cmake {
cppFlags “”
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(‘proguard-android-optimize.txt’), ‘proguard-rules.pro’
}
}
externalNativeBuild {
cmake {
path “src/main/cpp/CMakeLists.txt”
version “3.10.2”
}
}
}
dependencies {
// 省略引用的第三方库
}
可以看到,相比普通的Android应用,build.gradle配置中多了两个externalNativeBuild配置项。其中,defaultConfig里面的的externalNativeBuild主要是用于配置Cmake的命令参数,而外部的
externalNativeBuild的主要是界说了CMake的构建脚本CMakeLists.txt的路径。
然后,我们来看一下CMakeLists.txt文件,CMakeLists.txt是CMake的构建脚本,作用相当于ndk-build中的Android.mk,代码如下。
设置Cmake最小版本

cmake_minimum_required(VERSION 3.4.1)
编译library

add_library( # 设置library名称
native-lib
设置library模式

SHARED模式会编译so文件,STATIC模式不会编译

SHARED
设置原生代码路径

src/main/cpp/native-lib.cpp )
定位library

find_library( # library名称
log-lib
将library路径存储为一个变量,可以在其他地方用这个变量引用NDK库

在这里设置变量名称

log )
关联library

target_link_libraries( # 关联的library
native-lib
关联native-lib和log-lib

${log-lib} )
关于CMake的更多知识,可以查察CMake官方手册。
官方示例

默认创建Android NDK工程时,Android提供了一个简朴的JNI交互示例,返回一个字符串给Java层,方法名的格式为:Java_包名_类名_方法名 。首先,我们看一下native-lib.cpp的代码。
#include <jni.h>
#include
extern “C” JNIEXPORT jstring JNICALL
Java_com_xzh_ndk_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = “Hello from C++”;
return env->NewStringUTF(hello.c_str());
}
然后,我们在看一下Android的MainActivity.java 的代码。
package com.xzh.ndk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary(“native-lib”);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
public native String stringFromJNI();
}
初识Android JNI

1,JNI开发流程


  • 编写java类,声明了native方法;
  • 编写native代码;
  • 将native代码编译成so文件;
  • 在java类中引入so库,调用native方法;
2,native方法命名

extern “C”
JNIEXPORT void JNICALL
Java_com_xfhy_jnifirst_MainActivity_callJavaMethod(JNIEnv *env, jobject thiz) {
}
函数命名规则: Java_类全路径_方法名,涉及的参数的含义如下:


  • JNIEnv*是界说任意native函数的第一个参数,表示指向JNI环境的指针,可以通过它来访问JNI提供的接口方法。
  • jobject表示Java对象中的this,如果是静态方法则表示jclass。
  • JNIEXPORT和JNICALL: 它们是JNI中所界说的宏,可以在jni.h这个头文件中查找到。
3,JNI数据类型与Java数据类型的对应关系

首先,我们在Java代码里编写一个native方法声明,然后使用【alt+enter】快捷键让AS帮助我们创建一个native方法,如下所示。
public static native void ginsengTest(short s, int i, long l, float f, double d, char c,
boolean z, byte b, String str, Object obj, MyClass p, int[] arr);
//对应的Native代码
Java_com_xfhy_jnifirst_MainActivity_ginsengTest(JNIEnv *env, jclass clazz, jshort s, jint i, jlong l, jfloat f, jdouble d, jchar c,
jboolean z, jbyte b, jstring str, jobject obj, jobject p, jintArray arr) {
}
下面,我们整理下Java和JNI的类型对照表,如下所示。
Java 类型Native类型有无符合字长booleanjboolean无符号8字节bytejbyte有符号8字节charjchar无符号16字节shortjshort有符号16字节intjint有符号32字节longjlong有符号64字节floatjfloat有符号32字节doublejdouble有符号64字节 对应的引用类型如下表所示。
Java 类型Native类型java.lang.Classjclassjava.lang.Throwablejthrowablejava.lang.Stringjstringjjava.lang.Object[]jobjectArrayByte[]jbyteArrayChar[]jcharArrayShort[]jshortArrayint[]jintArraylong[]jlongArrayfloat[]jfloatArraydouble[]jdoubleArray 3.1基本数据类型

Native的基本数据类型实在就是将C/C++中的基本类型用typedef重新界说了一个新的名字,在JNI中可以直接访问,如下所示。
typedef uint8_t jboolean; /* unsigned 8 bits /
typedef int8_t jbyte; /
signed 8 bits /
typedef uint16_t jchar; /
unsigned 16 bits /
typedef int16_t jshort; /
signed 16 bits /
typedef int32_t jint; /
signed 32 bits /
typedef int64_t jlong; /
signed 64 bits /
typedef float jfloat; /
32-bit IEEE 754 /
typedef double jdouble; /
64-bit IEEE 754 */
3.2 引用数据类型

如果使用C++语言编写,则所有引用派生自jobject根类,如下所示。
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public _jobject {};
class _jarray : public _jobject {};
class _jobjectArray : public _jarray {};
class _jbooleanArray : public _jarray {};
class _jbyteArray : public _jarray {};
class _jcharArray : public _jarray {};
class _jshortArray : public _jarray {};
class _jintArray : public _jarray {};
class _jlongArray : public _jarray {};
class _jfloatArray : public _jarray {};
class _jdoubleArray : public _jarray {};
class _jthrowable : public _jobject {};
JNI使用C语言时,所有引用类型都使用jobject。
4,JNI的字符串处理

4.1 native操作JVM

JNI会把Java中所有对象当做一个C指针通报到本地方法中,这个指针指向JVM内部数据结构,而内部的数据结构在内存中的存储方式是不可见的.只能从JNIEnv指针指向的函数表中选择合适的JNI函数来操作JVM中的数据结构。
比如native访问java.lang.String 对应的JNI类型jstring时,不能像访问基本数据类型那样使用,由于它是一个Java的引用类型,所以在本地代码中只能通过类似GetStringUTFChars如许的JNI函数来访问字符串的内容。
4.2 字符串操作的示例

//调用
String result = operateString(“待操作的字符串”);
Log.d(“xfhy”, result);
//界说
public native String operateString(String str);
然后在C中进行实现,代码如下。
extern “C”
JNIEXPORT jstring JNICALL
Java_com_xfhy_jnifirst_MainActivity_operateString(JNIEnv *env, jobject thiz, jstring str) {
//从java的内存中把字符串拷贝出来 在native使用
const char *strFromJava = (char *) env->GetStringUTFChars(str, NULL);
if (strFromJava == NULL) {
//必须空检查
return NULL;
}
//将strFromJava拷贝到buff中,待会儿好拿去天生字符串
char buff[128] = {0};
strcpy(buff, strFromJava);
strcat(buff, " 在字符串后面加点东西");
//释放资源
env->ReleaseStringUTFChars(str, strFromJava);
//自动转为Unicode
return env->NewStringUTF(buff);
}
4.2.1 native中获取JVM字符串

在上面的代码中,operateString函数吸收一个jstring类型的参数str,jstring是指向JVM内部的一个字符串,不能直接使用。首先,必要将jstring转为C风格的字符串类型char*后才气使用,这里必须使用合适的JNI函数来访问JVM内部的字符串数据结构。
GetStringUTFChars(jstring string, jboolean* isCopy)对应的参数的含义如下:


  • string : jstring,Java通报给native代码的字符串指针。
  • isCopy : 一般环境下传NULL,取值可以是JNI_TRUE和JNI_FALSE,如果是JNI_TRUE则会返回JVM内部源字符串的一份拷贝,并为新产生的字符串分配内存空间。如果是JNI_FALSE则返回JVM内部源字符串的指针,意味着可以在native层修改源字符串,但是不推荐修改,由于Java字符串的原则是不能修改的。
Java中默认是使用Unicode编码,C/C++默认使用UTF编码,所以在native层与java层进行字符串交换的时候必要进行编码转换。GetStringUTFChars就刚好可以把jstring指针(指向JVM内部的Unicode字符序列)的字符串转换成一个UTF-8格式的C字符串。
4.2.2 异常处理

在使用GetStringUTFChars的时候,返回的值可能为NULL,这时必要处理一下,否则继承往下面走的话,使用这个字符串的时候会出现问题.由于调用这个方法时,是拷贝,JVM为新天生的字符串分配内存空间,当内存空间不敷分配的时候就会导致调用失败。调用失败就会返回NULL,并抛出OutOfMemoryError。JNI遇到未决的异常不会改变步伐的运行流程,还是会继承往下走。
4.2.3 释放字符串资源

native不像Java,我们必要手动释放申请的内存空间。GetStringUTFChars调用时会新申请一块空间用来装拷贝出来的字符串,这个字符串用来方便native代码访问和修改之类的。既然有内存分配,那么就必须手动释放,释放方法是ReleaseStringUTFChars。可以看到和GetStringUTFChars是逐一对应配对的。
4.2.4 构建字符串

使用NewStringUTF函数可以构建出一个jstring,必要传入一个char *类型的C字符串。它会构建一个新的java.lang.String字符串对象,而且会自动转换成Unicode编码。如果JVM不能为构造java.lang.String分配充足的内存,则会抛出一个OutOfMemoryError异常并返回NULL。
4.2.5 其他字符串操作函数


  • GetStringChars和ReleaseStringChars:这对函数和Get/ReleaseStringUTFChars函数功能类似,用于获取和释放的字符串是以Unicode格式编码的。
  • GetStringLength:获取Unicode字符串(jstring)的长度。 UTF-8编码的字符串是以0末端,而Unicode的不是,所以这里必要单独区分开。
  • 「GetStringUTFLength」: 获取UTF-8编码字符串的长度,就是获取C/C++默认编码字符串的长度.还可以使用尺度C函数「strlen」来获取其长度。
  • strcat: 拼接字符串,尺度C函数。如strcat(buff, "xfhy"); 将xfhy添加到buff的末尾。
  • GetStringCritical和ReleaseStringCritical: 为了增长直接传回指向Java字符串的指针的可能性(而不是拷贝).在这2个函数之间的地区,是绝对不能调用其他JNI函数大概让线程阻塞的native函数.否则JVM可能死锁. 如果有一个字符串的内容特别大,比如1M,且只必要读取里面的内容打印出来,此时比力得当用该对函数,可直接返回源字符串的指针。
  • GetStringRegion和GetStringUTFRegion: 获取Unicode和UTF-8字符串中指定范围的内容(如: 只必要1-3索引处的字符串),这对函数会将源字符串复制到一个预先分配的缓冲区(自己界说的char数组)内。
通常,GetStringUTFRegion会进行越界检查,越界会抛StringIndexOutOfBoundsException异常。GetStringUTFRegion实在和GetStringUTFChars有点相似,但是GetStringUTFRegion内部不会分配内存,不会抛出内存溢出异常。由于其内部没有分配内存,所以也没有类似Release如许的函数来释放资源。
4.2.6 小结



  • Java字符串转C/C++字符串: 使用GetStringUTFChars函数,必须调用ReleaseStringUTFChars释放内存。
  • 创建Java层必要的Unicode字符串,使用NewStringUTF函数。
  • 获取C/C++字符串长度,使用GetStringUTFLength大概strlen函数。
  • 对于小字符串,GetStringRegion和GetStringUTFRegion这2个函数是最佳选择,由于缓冲区数组可以被编译器提取分配,不会产生内存溢出的异常。当只必要处理字符串的部分数据时,也还是不错。它们提供了开始索引和子字符串长度值,复制的消耗也黑白常小
  • 获取Unicode字符串和长度,使用GetStringChars和GetStringLength函数。
数组操作

5.1 基本类型数组

基本类型数组就是JNI中的基本数据类型构成的数组,可以直接访问。比方,下面是int数组求和的例子,代码如下。
//MainActivity.java
public native int sumArray(int[] array);
extern “C”
JNIEXPORT jint JNICALL
Java_com_xfhy_jnifirst_MainActivity_sumArray(JNIEnv *env, jobject thiz, jintArray array) {
//数组求和
int result = 0;
//方式1 推荐使用
jint arr_len = env->GetArrayLength(array);
//动态申请数组
jint *c_array = (jint *) malloc(arr_len * sizeof(jint));
//初始化数组元素内容为0
memset(c_array, 0, sizeof(jint) * arr_len);
//将java数组的[0-arr_len)位置的元素拷贝到c_array数组中
env->GetIntArrayRegion(array, 0, arr_len, c_array);
for (int i = 0; i < arr_len; ++i) {
result += c_array;
}
//动态申请的内存 必须释放
free(c_array);
return result;
}
C层拿到jintArray之后首先必要获取它的长度,然后动态申请一个数组(由于Java层通报过来的数组长度是不定的,所以这里必要动态申请C层数组),这个数组的元素是jint类型的。malloc是一个经常使用的拿来申请一块一连内存的函数,申请之后的内存是必要手动调用free释放的。然后就是调用GetIntArrayRegion函数将Java层数组拷贝到C层数组中并进行求和。
接下来,我们来看另一种求和方式,代码如下。
extern “C”
JNIEXPORT jint JNICALL
Java_com_xfhy_jnifirst_MainActivity_sumArray(JNIEnv *env, jobject thiz, jintArray array) {
//数组求和
int result = 0;
//方式2
//此种方式比力危险,GetIntArrayElements会直接获取数组元素指针,是可以直接对该数组元素进行修改的.
jint *c_arr = env->GetIntArrayElements(array, NULL);
if (c_arr == NULL) {
return 0;
}
c_arr[0] = 15;
jint len = env->GetArrayLength(array);
for (int i = 0; i < len; ++i) {
//result += *(c_arr + i); 写成这种形式,大概下面一行那种都行
result += c_arr;
}
//有Get,一般就有Release
env->ReleaseIntArrayElements(array, c_arr, 0);
return result;
}
在上面的代码中,我们直接通过GetIntArrayElements函数拿到原数组元素指针,直接操作就可以拿到元素求和。看起来要简朴许多,但是这种方式我个人以为是有点危险,毕竟这种可以在C层直接进行源数组修改不是很保险的。GetIntArrayElements的第二个参数一般传NULL,通报JNI_TRUE是返回暂时缓冲区数组指针(即拷贝一个副本),通报JNI_FALSE则是返回原始数组指针。
5.2 对象数组

对象数组中的元素是一个类的实例或其他数组的引用,不能直接访问Java通报给JNI层的数组。操尴尬刁难象数组稍显复杂,下面举一个例子:在native层创建一个二维数组,且赋值并返回给Java层使用。
public native int[][] init2DArray(int size);
//交给native层创建->Java打印输出
int[][] init2DArray = init2DArray(3);
for (int i = 0; i < 3; i++) {
for (int i1 = 0; i1 < 3; i1++) {
Log.d(“xfhy”, “init2DArray[” + i + “][” + i1 + “]” + " = " + init2DArray[i1]);
}
}
extern “C”
JNIEXPORT jobjectArray JNICALL
Java_com_xzh_jnifirst_MainActivity_init2DArray(JNIEnv env, jobject thiz, jint size) {
//创建一个size
size大小的二维数组
//jobjectArray是用来装对象数组的 Java数组就是一个对象 int[]
jclass classIntArray = env->FindClass(“[I”);
自我介绍一下,小编13年上海交大结业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提拔技能,往往是自己探索发展大概是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易遇到天花板技能停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简朴,就是希望可以或许帮助到想自学提拔又不知道该从何学起的朋友,同时减轻各人的负担。





既有得当小白学习的零基础资料,也有得当3年以上履历的小伙伴深入学习提拔的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比力大,这里只是将部分目次截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、解说视频,而且会一连更新!
如果你以为这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后

本日关于口试的分享就到这里,还是那句话,有些东西你不但要懂,而且要可以或许很好地表达出来,可以或许让口试官认可你的理解,比方Handler机制,这个是口试必问之题。有些晦涩的点,或许它只活在口试当中,现实工作当中你压根不会用到它,但是你要知道它是什么东西。
最后在这里小编分享一份自己收录整理上述技能体系图相干的几十套腾讯、头条、阿里、美团等公司2021年的口试题,把技能点整理成了视频和PDF(现实上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给各人展示一部分。
另有 高级架构技能进阶脑图、Android开发口试专题资料,高级进阶架构资料 帮助各人学习提拔进阶,也节流各人在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
【Android核心高级技能PDF文档,BAT大厂口试真题剖析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】
Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网隆冬,实在无非就是你上错了车,且穿的少(技能),要是你上对车,自身技能能力够强,公司换掉的代价大,怎么可能会被裁掉,都是镌汰末了的业务Curd而已!现如今市场上低级步伐员泛滥,这套教程针对Android开发工程师1-6年的职员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂口试真题+项目实战源码》,点击传送门即可获取!

口试真题剖析】**
[外链图片转存中…(img-sN4qyJ6V-1712024123350)]
【算法合集】
[外链图片转存中…(img-l5xkkkpt-1712024123350)]
【延伸Android必备知识点】
[外链图片转存中…(img-vb2uqgaz-1712024123350)]
【Android部分高级架构视频学习资源】
Android精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网隆冬,实在无非就是你上错了车,且穿的少(技能),要是你上对车,自身技能能力够强,公司换掉的代价大,怎么可能会被裁掉,都是镌汰末了的业务Curd而已!现如今市场上低级步伐员泛滥,这套教程针对Android开发工程师1-6年的职员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!
《Android学习笔记总结+移动架构视频+大厂口试真题+项目实战源码》,点击传送门即可获取!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

大连全瓷种植牙齿制作中心

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表