声明
- 前阶段在项目中使用了Android的JNI技术,在此文中做些技术知识总结。
- 本文参考了一些书籍的若干章节,比如《Android进阶解密-第9章-JNI原理》、《深入理解Android虚拟机-第4章-分析JNI》、《深入理解Android系统-第2章-分析JNI》、《Android NDK Beginner_'s Guide》等
- 之所以会参考这么多书籍的原因是我在看某一技术问题时,习惯参考不同书籍作者的看法,毕竟不同的书都有自己的侧重点,而且不同的作者对同一技术问题的理解深度也不同。
- 本文使用的代码时LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客:如何下载Nexus5的LineageOS14.1(cm-14.1)系统源码并编译、刷机
1 JNI的原理是什么?
JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性,但是同时JNI会提升程序的性能。
通常情况下,下面几种情况下才会使用JNI技术:
- 需要调用Java语言不支持但依赖于操作系统平特性的一些功能。例如,需要调用当前Linux系统的某功能而Java却不支持。
- 整合以前以非Java语言开发的系统,毕竟重复利用这些Native语言库也符合代码复用的编程原则。例如,要用到早期实现的C/C++语言开发的一些功能或系统,将这些功能或系统整合到新的系统或版本中。
- 节省运行时间,提高运行效率(需借用C/C++)。例如,游戏、音视频开发涉及到图像、音频解码这种要求效率的功能。
JNI在Android里的使用很广泛,主要是音视频开发、热修复和插件化、逆向开发、系统源码调用等。涉及到的工具那就是NDK(Native Development Kit)了,这个大家一定是听过的,后面在写一篇关于NDK的文章,现在先不对其做详细介绍了。
本文侧重在系统源码中对JNI原理进行分析,就现实来看如果你是一个Android APP开发者,不理解JNI原理的话,视野会局限在Framework层;热修复和插件化原理需要JNI的知识储备;
既然要分析Android源码中的JNI,那就选择使用cm-14.1中的MediaRecorder框架作为例子。
2 MediaRecorder框架里的JNI
MediaRecorder在Framework中用于录音/录像,其中Java-JNI-Native三层的代码对应关系如下图所示:
2.1 Framework层中的MediaRecorder.java
进入到源码路径:vim ~/LineageOS/frameworks/base/media/java/android/media/MediaRecorder.java
截取一段JNI相关的代码:
public class MediaRecorder
{
//对于Framework层,只要其类中加载了对应的JNI库,在其类中直接声明native方法即可,其余工作会由#JNI层#完成
static {
System.loadLibrary("media_jni");//用来加载名为libmedia_jni.so的动态库。
native_init();//调用Native方法完成JNI的注册。
}
private final static String TAG = "MediaRecorder";
...省略n行代码...
public native void start() throws IllegalStateException;//native开头的方法代表其为Java类中的一个native方法。
...省略n行代码...
private static native final void native_init();
2.2 JNI层中的android_media_MediaRecorder.cpp
进入到源码路径:vim ~/LineageOS/frameworks/base/media/jni/android_media_MediaRecorder.cpp
上一节2.1所述的MediaRecorder.java中的native方法native_init()、start()的JNI层实现就在这里了:
//MediaRecorder.java中的native方法native_init()在JNI层的实现
// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in MediaRecorder, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaRecorder_native_init(JNIEnv *env)
{
jclass clazz;
clazz = env->FindClass("android/media/MediaRecorder");
if (clazz == NULL) {
return;
}
fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
if (fields.context == NULL) {
return;
}
fields.surface = env->GetFieldID(clazz, "mSurface", "Landroid/view/Surface;");
if (fields.surface == NULL) {
return;
}
jclass surface = env->FindClass("android/view/Surface");
if (surface == NULL) {
return;
}
fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
if (fields.post_event == NULL) {
return;
}
}
//MediaRecorder.java中的native方法start()在JNI层的实现
static void
android_media_MediaRecorder_start(JNIEnv *env, jobject thiz)
{
ALOGV("start");
sp<MediaRecorder> mr = getMediaRecorder(env, thiz);
process_media_recorder_call(env, mr->start(), "java/lang/RuntimeException", "start failed.");
}
进入到源码路径:vim ~/LineageOS/frameworks/base/media/jni/Android.mk
就能发现这个android_media_MediaRecorder.cpp和同目录下的其它cpp文件共同编译出了动态库libmedia_jni.so
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
android_media_AmrInputStream.cpp \
android_media_ExifInterface.cpp \
android_media_ImageWriter.cpp \
android_media_ImageReader.cpp \
android_media_MediaCrypto.cpp \
android_media_MediaCodec.cpp \
android_media_MediaCodecList.cpp \
android_media_MediaDataSource.cpp \
android_media_MediaDrm.cpp \
android_media_MediaExtractor.cpp \
android_media_MediaHTTPConnection.cpp \
android_media_MediaMetadataRetriever.cpp \
android_media_MediaMuxer.cpp \
android_media_MediaPlayer.cpp \
android_media_MediaProfiles.cpp \
android_media_MediaRecorder.cpp \
android_media_MediaScanner.cpp \
android_media_MediaSync.cpp \
android_media_ResampleInputStream.cpp \
android_media_SyncParams.cpp \
android_media_Utils.cpp \
android_mtp_MtpDatabase.cpp \
android_mtp_MtpDevice.cpp \
android_mtp_MtpServer.cpp \
LOCAL_SHARED_LIBRARIES := \
libandroid_runtime \
libnativehelper \
libutils \
libbinder \
libmedia \
libmediadrm \
libskia \
libui \
liblog \
libcutils \
libgui \
libstagefright \
libstagefright_foundation \
libcamera_client \
libmtp \
libusbhost \
libexif \
libpiex \
libstagefright_amrnb_common
LOCAL_STATIC_LIBRARIES := \
libstagefright_amrnbenc
LOCAL_C_INCLUDES += \
external/libexif/ \
external/piex/ \
external/tremor/Tremor \
frameworks/base/core/jni \
frameworks/base/libs/hwui \
frameworks/av/media/libmedia \
frameworks/av/media/libstagefright \
frameworks/av/media/libstagefright/codecs/amrnb/enc/src \
frameworks/av/media/libstagefright/codecs/amrnb/common \
frameworks/av/media/libstagefright/codecs/amrnb/common/include \
frameworks/av/media/mtp \
frameworks/native/include/media/openmax \
$(call include-path-for, libhardware)/hardware \
$(PV_INCLUDES) \
$(JNI_H_INCLUDE)
LOCAL_CFLAGS += -Wall -Werror -Wno-error=deprecated-declarations -Wunused -Wunreachable-code
#在这里编译出libmedia_jni.so
LOCAL_MODULE:= libmedia_jni
include $(BUILD_SHARED_LIBRARY)
# build libsoundpool.so
# build libaudioeffect_jni.so
include $(call all-makefiles-under,$(LOCAL_PATH))
2.3 Native方法的注册
上一节遗留了个疑问,native_init方法是如何找对自己所对应的android_media_MediaRecorder_native_init方法呢?这就涉及到了本节所述JNI方法的注册问题了。
Native方法的注册分为静态注册(常用于应用侧的NDK开发)和动态注册(常用于系统中Framework开发),
2.3.1 Native静态注册
1. 在Android Studio中创建一个Empty Project,参照MediaRecorder.java代码写一个简单的demo:
2. 利用Android Studio自带的Terminal,进入到我的项目路径F:\Android_Projects\booksource-master\chapter2\MyApplication3\app\src\main\java中输入下面命令:
"C:\Program Files\Java\jdk1.7.0_80\bin\javac.exe" com\example\myapplication\MediaRecorder.java
将会生成MediaRecorder.class文件,如下图所示:
3. 继续执行下面命令:
"C:\Program Files\Java\jdk1.7.0_80\bin\javah.exe" com.example.myapplication.MediaRecorder
将会生成com_example_myapplication_MediaRecorder.h文件,如下图所示:
生成的com_example_myapplication_MediaRecorder.h文件内容为:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_myapplication_MediaRecorder */
#ifndef _Included_com_example_myapplication_MediaRecorder
#define _Included_com_example_myapplication_MediaRecorder
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_myapplication_MediaRecorder
* Method: native_init
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_myapplication_MediaRecorder_native_1init
(JNIEnv *, jclass);
/*
* Class: com_example_myapplication_MediaRecorder
* Method: start
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_myapplication_MediaRecorder_start
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
可以看出来nativeinit方法声明为以Java_com_example_myapplication_
MediaRecorder_native_1init命名的方法:
当在Java中调用init_native方法时,Java虚拟机就会去JNI中寻找Java_com_example_myapplication_MediaRecorder_native_1init函数,如果没找到会报错,如果找到了就会为这两者建立关联(通过保存JNI的函数指针,这样在下次调用native_init方法时直接使用这个函数指针即可)
所以,静态注册的要义所在就是通过方法名将Java方法和JNI函数建立关联。但是虽然静态注册操作简单,适合在Android Studio中写APP时使用,却是有明显缺点的:
- JNI层的函数名太长;
- 声明Native方法的类需要用javah工具生成头文件;
- 第一次调用Native方法需要建立关联,影响运行效率;
那么问题来了,怎样克服这些缺点呢???就是下面的动态注册方式 !
2.3.2 动态注册
在Android系统jni.h中,有专门的数据结构JNINativeMethod用来记录Java的Native方法和JNI函数的关联关系。
进入到源码路径:vim ~/LineageOS/libnativehelper/
include/nativehelper/jni.h
typedef struct {
const char* name; //Java方法名
const char* signature; //Java方法名的签名信息
void* fnPtr; //JNI中对应的方法指针
} JNINativeMethod;
依然以MediaRecorder为例,其采用的注册方式就是动态注册。进入到源码路径:
vim ~/LineageOS/frameworks/base/media/jni/android_media_MediaRecorder.cpp
static const JNINativeMethod gMethods[] = {
{"setCamera", "(Landroid/hardware/Camera;)V", (void *)android_media_MediaRecorder_setCamera},
{"setVideoSource", "(I)V", (void *)android_media_MediaRecorder_setVideoSource},
{"setAudioSource", "(I)V", (void *)android_media_MediaRecorder_setAudioSource},
{"setOutputFormat", "(I)V", (void *)android_media_MediaRecorder_setOutputFormat},
{"setVideoEncoder", "(I)V", (void *)android_media_MediaRecorder_setVideoEncoder},
{"setAudioEncoder", "(I)V", (void *)android_media_MediaRecorder_setAudioEncoder},
{"setParameter", "(Ljava/lang/String;)V", (void *)android_media_MediaRecorder_setParameter},
{"_setOutputFile", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaRecorder_setOutputFileFD},
{"setVideoSize", "(II)V", (void *)android_media_MediaRecorder_setVideoSize},
{"setVideoFrameRate", "(I)V", (void *)android_media_MediaRecorder_setVideoFrameRate},
{"setMaxDuration", "(I)V", (void *)android_media_MediaRecorder_setMaxDuration},
{"setMaxFileSize", "(J)V", (void *)android_media_MediaRecorder_setMaxFileSize},
{"_prepare", "()V", (void *)android_media_MediaRecorder_prepare},
{"getSurface", "()Landroid/view/Surface;", (void *)android_media_MediaRecorder_getSurface},
{"getMaxAmplitude", "()I", (void *)android_media_MediaRecorder_native_getMaxAmplitude},
{"start", "()V", (void *)android_media_MediaRecorder_start},
{"stop", "()V", (void *)android_media_MediaRecorder_stop},
{"pause", "()V", (void *)android_media_MediaRecorder_pause},
{"resume", "()V", (void *)android_media_MediaRecorder_resume},
{"native_reset", "()V", (void *)android_media_MediaRecorder_native_reset},
{"release", "()V", (void *)android_media_MediaRecorder_release},
{"native_init", "()V", (void *)android_media_MediaRecorder_native_init},
{"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;)V",
(void *)android_media_MediaRecorder_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaRecorder_native_finalize},
{"native_setInputSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaRecorder_setInputSurface },
};
该gMethods[]数组中存放的就是MediaRecorder的Native方法与JNI层函数的对应关系。依然以native_init为例:
{"native_init", "()V", (void *)android_media_MediaRecorder_native_init},
- “native_init” 代表Java层的Native方法;
- “()V” 代表native_initve_init方法的签名信息;
- (void *)android_media_MediaRecorder_native_init 代表对应JNI层函数;
注册函数register_android_media_MediaRecorder的功能就是gMethods[]数组中Java方法和JNI函数的对应关系动态注册到系统中。
// This function only registers the native methods, and is called from
// JNI_OnLoad in android_media_MediaPlayer.cpp
int register_android_media_MediaRecorder(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaRecorder", gMethods, NELEM(gMethods));
}
注释中说在android_media_MediaPlayer.cpp的JNI_OnLoad函数中会调用register_android_media_MediaRecorder函数,那就先到android_media_MediaPlayer.cpp中去看一下这个JNI_OnLoad函数:
进入到源码路径: vim ~/LineageOS/frameworks/base/media/jni/
android_media_MediaPlayer.cpp
extern int register_android_media_ExifInterface(JNIEnv *env);
extern int register_android_media_ImageReader(JNIEnv *env);
extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_Crypto(JNIEnv *env);
extern int register_android_media_Drm(JNIEnv *env);
extern int register_android_media_MediaCodec(JNIEnv *env);
extern int register_android_media_MediaExtractor(JNIEnv *env);
extern int register_android_media_MediaCodecList(JNIEnv *env);
extern int register_android_media_MediaHTTPConnection(JNIEnv *env);
extern int register_android_media_MediaMetadataRetriever(JNIEnv *env);
extern int register_android_media_MediaMuxer(JNIEnv *env);
extern int register_android_media_MediaRecorder(JNIEnv *env);
extern int register_android_media_MediaScanner(JNIEnv *env);
extern int register_android_media_MediaSync(JNIEnv *env);
extern int register_android_media_ResampleInputStream(JNIEnv *env);
extern int register_android_media_MediaProfiles(JNIEnv *env);
extern int register_android_media_AmrInputStream(JNIEnv *env);
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
JNIEnv* env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
if (register_android_media_ImageWriter(env) != JNI_OK) {
ALOGE("ERROR: ImageWriter native registration failed");
goto bail;
}
if (register_android_media_ImageReader(env) < 0) {
ALOGE("ERROR: ImageReader native registration failed");
goto bail;
}
if (register_android_media_MediaPlayer(env) < 0) {
ALOGE("ERROR: MediaPlayer native registration failed\n");
goto bail;
}
if (register_android_media_MediaRecorder(env) < 0) {
ALOGE("ERROR: MediaRecorder native registration failed\n");
goto bail;
}
if (register_android_media_MediaScanner(env) < 0) {
ALOGE("ERROR: MediaScanner native registration failed\n");
goto bail;
}
if (register_android_media_MediaMetadataRetriever(env) < 0) {
ALOGE("ERROR: MediaMetadataRetriever native registration failed\n");
goto bail;
}
if (register_android_media_AmrInputStream(env) < 0) {
ALOGE("ERROR: AmrInputStream native registration failed\n");
goto bail;
}
if (register_android_media_ResampleInputStream(env) < 0) {
ALOGE("ERROR: ResampleInputStream native registration failed\n");
goto bail;
if (register_android_media_MediaProfiles(env) < 0) {
ALOGE("ERROR: MediaProfiles native registration failed");
goto bail;
}
if (register_android_mtp_MtpDatabase(env) < 0) {
ALOGE("ERROR: MtpDatabase native registration failed");
goto bail;
}
if (register_android_mtp_MtpDevice(env) < 0) {
ALOGE("ERROR: MtpDevice native registration failed");
goto bail;
}
if (register_android_mtp_MtpServer(env) < 0) {
ALOGE("ERROR: MtpServer native registration failed");
goto bail;
}
if (register_android_media_MediaCodec(env) < 0) {
ALOGE("ERROR: MediaCodec native registration failed");
goto bail;
}
if (register_android_media_MediaSync(env) < 0) {
ALOGE("ERROR: MediaSync native registration failed");
goto bail;
}
if (register_android_media_MediaExtractor(env) < 0) {
ALOGE("ERROR: MediaCodec native registration failed");
goto bail;
}
if (register_android_media_MediaMuxer(env) < 0) {
ALOGE("ERROR: MediaMuxer native registration failed");
goto bail;
}
if (register_android_media_MediaCodecList(env) < 0) {
ALOGE("ERROR: MediaCodec native registration failed");
goto bail;
}
if (register_android_media_Crypto(env) < 0) {
ALOGE("ERROR: MediaCodec native registration failed");
goto bail;
}
if (register_android_media_Drm(env) < 0) {
ALOGE("ERROR: MediaDrm native registration failed");
goto bail;
}
if (register_android_media_MediaHTTPConnection(env) < 0) {
ALOGE("ERROR: MediaHTTPConnection native registration failed");
goto bail;
}
if (register_android_media_ExifInterface(env) < 0) {
ALOGE("ERROR: ExifInterface native registration failed");
goto bail;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
bail:
return result;
}
可以发现多媒体框架中有很多框架都需要进行JNINativeMethod数组的注册,所以,统一在android_media_MediaPlayer.cpp中的JNI_OnLoad函数中,而JNI_OnLoad的调用会在System.loadLibrary函数调用之后被调用。
追踪一下register_android_media_MediaRecorder函数实际调用的是~/LineageOS/frameworks/base/core/jni/AndroidRuntime.cpp中的AndroidRuntime::registerNativeMethods函数:
/*
* Register native methods using JNI.
*/
/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
const char* className, const JNINativeMethod* gMethods, int numMethods)
{
return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
AndroidRuntime::registerNativeMethods函数中又调用了帮助类~/LineageOS/libnativehelper/JNIHelp.cpp中的jniRegisterNativeMethods函数。
extern "C" int jniRegisterNativeMethods(C_JNIEnv* env, const char* className,
const JNINativeMethod* gMethods, int numMethods)
{
JNIEnv* e = reinterpret_cast<JNIEnv*>(env);
ALOGV("Registering %s's %d native methods...", className, numMethods);
scoped_local_ref<jclass> c(env, findClass(env, className));
if (c.get() == NULL) {
char* tmp;
const char* msg;
if (asprintf(&tmp,
"Native registration unable to find class '%s'; aborting...",
className) == -1) {
// Allocation failed, print default warning.
msg = "Native registration unable to find class; aborting...";
} else {
msg = tmp;
}
e->FatalError(msg);
}
//最终调用了JNIEnv的RegisterNatives函数完成JNI的注册!!!
if ((*env)->RegisterNatives(e, c.get(), gMethods, numMethods) < 0) {
char* tmp;
const char* msg;
if (asprintf(&tmp, "RegisterNatives failed for '%s'; aborting...", className) == -1) {
// Allocation failed, print default warning.
msg = "RegisterNatives failed; aborting...";
} else {
msg = tmp;
}
e->FatalError(msg);
}
return 0;
JNI的动态注册最终调用了JNIEnv的RegisterNatives函数去完成JNI的动态注册!!!
避免博客拖太长,后续参见下篇《[日更-2019.4.7]Android系统的JNI原理分析(二)》