JNI so库加载流程之System.loadLibrary流程分析
最近在学习jni相关的知识,很多博客都说,jni***动态注册*时调用System.loadLibrary或者System.load方法加载so库,System.loadLibrary或System.load会调用到so库中的JNI_OnLoad方法进行方法注册,但是这个说是这样说,对于读者依然很模糊,到底System.loadLibrary或System.load到底是怎样的一种流程进行加载的并且调用JNI_OnLoad方法进行注册的呢,为了解开自己的疑惑,在这里看了一Android源码,做一下记录,也希望对别人有所帮助!
下面的代码都来自于android7.1.1_r6源码,除了第一个代码片作为影子其他代码片的第一行都标注了代码片的来源。
System.loadLibrary(libName);
loadLibrary是System.java中的一个静态方法
//libcore/ojluni/src/main/java/java/lang/System.java
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname);
}
getRuntime是Runtime.java中的方法,调用该方法返回Runtime对象,loadLibrary0是Runtime.java中的方法,见下面代码:
//libcore/ojluni/src/main/java/java/lang/Runtime.java
private static Runtime currentRuntime = new Runtime();//单例模式
public static Runtime getRuntime() {
return currentRuntime;
}
synchronized void loadLibrary0(ClassLoader loader, String libname) {
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}//判断传入的库名称是否合法,比如我们的库是libxxx.so,我们只需要传入xxx就可以了
String libraryName = libname;
if (loader != null) {//如果类加载器不为空
String filename = loader.findLibrary(libraryName);//查找是否存在我们需要的库文件
if (filename == null) {
// It's not necessarily true that the ClassLoader used
// System.mapLibraryName, but the default setup does, and it's
// misleading to say we didn't find "libMyLibrary.so" when we
// actually searched for "liblibMyLibrary.so.so".
throw new UnsatisfiedLinkError(loader + " couldn't find \"" +
System.mapLibraryName(libraryName) + "\"");
}//不存在库文件则抛出异常
String error = doLoad(filename, loader);//如果库文件存在,就加载
if (error != null) {
throw new UnsatisfiedLinkError(error);
}//加载库文件失败,抛出异常
return;
}
//下面这些代码是类加载器为空的情况下才执行,正常情况下开发者app中开发者自己写的库文件加载时不会执行到这里,因为传入的类加载器不会为空,系统应用才有可能走这里,这时下面获取系统默认的库存放路径才是有用的
String filename = System.mapLibraryName(libraryName);
List<String> candidates = new ArrayList<String>();
String lastError = null;
for (String directory : getLibPaths()) {//getLibPaths()用来获取系统中存放so库的文件路径,下面有相关的实现代码和解释
String candidate = directory + filename;//拼我们要加载库的绝对路径
candidates.add(candidate);
if (IoUtils.canOpenReadOnly(candidate)) {//判断绝对路径上的文件是否存在
String error = doLoad(candidate, loader);//如果存在,并且是只可读,则加载该库
if (error == null) {
return; // We successfully loaded the library. Job done.
}//出现错误直接返回
lastError = error;
}
}
if (lastError != null) {
throw new UnsatisfiedLinkError(lastError);
}
throw new UnsatisfiedLinkError("Library " + libraryName + " not found; tried " + candidates);
}
private String[] getLibPaths() {
if (mLibPaths == null) {
synchronized(this) {
if (mLibPaths == null) {
mLibPaths = initLibPaths();//调用initLibPaths方法
}
}
}
return mLibPaths;
}
private static String[] initLibPaths() {
String javaLibraryPath = System.getProperty("java.library.path");//可以看出系统默认的库文件存放路径是在java.library.path属性中存储的
if (javaLibraryPath == null) {
return EmptyArray.STRING;
}
String[] paths = javaLibraryPath.split(":");
// Add a '/' to the end of each directory so we don't have to do it every time.
for (int i = 0; i < paths.length; ++i) {
if (!paths[i].endsWith("/")) {
paths[i] += "/";
}
}
return paths;
}
private String doLoad(String name, ClassLoader loader) {
// Android apps are forked from the zygote, so they can't have a custom LD_LIBRARY_PATH,
// which means that by default an app's shared library directory isn't on LD_LIBRARY_PATH.
// The PathClassLoader set up by frameworks/base knows the appropriate path, so we can load
// libraries with no dependencies just fine, but an app that has multiple libraries that
// depend on each other needed to load them in most-dependent-first order.
// We added API to Android's dynamic linker so we can update the library path used for
// the currently-running process. We pull the desired path out of the ClassLoader here
// and pass it to nativeLoad so that it can call the private dynamic linker API.
// We didn't just change frameworks/base to update the LD_LIBRARY_PATH once at the
// beginning because multiple apks can run in the same process and third party code can
// use its own BaseDexClassLoader.
// We didn't just add a dlopen_with_custom_LD_LIBRARY_PATH call because we wanted any
// dlopen(3) calls made from a .so's JNI_OnLoad to work too.
// So, find out what the native library search path is for the ClassLoader in question...
String librarySearchPath = null;
if (loader != null && loader instanceof BaseDexClassLoader) {
BaseDexClassLoader dexClassLoader = (BaseDexClassLoader) loader;
librarySearchPath = dexClassLoader.getLdLibraryPath();
}
// nativeLoad should be synchronized so there's only one LD_LIBRARY_PATH in use regardless
// of how many ClassLoaders are in the system, but dalvik doesn't support synchronized
// internal natives.
synchronized (this) {
return nativeLoad(name, loader, librarySearchPath);//调用本地方法nativeLoad,nativeLoad是一个本地方法
}
}
// TODO: should be synchronized, but dalvik doesn't support synchronized internal natives.
private static native String nativeLoad(String filename, ClassLoader loader,
String librarySearchPath);
下面进入nativeLoad实现方法中
//libcore/ojluni/src/main/native/Runtime.c
#define NATIVE_METHOD(className, functionName, signature) \
{ #functionName, signature, (void*)(className ## _ ## functionName) }//定义的宏,在gMethods数组赋值时有使用,如NATIVE_METHOD(Runtime, nativeExit, "(I)V")等价于{"nativeExit","(I)V",(void*)Runtime_nativeExit}
JNIEXPORT jstring JNICALL
Runtime_nativeLoad(JNIEnv* env, jclass ignored, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath)
{
return JVM_NativeLoad(env, javaFilename, javaLoader, javaLibrarySearchPath);//调用JVM_NativeLoad方法
}//nativeLoad方法在本地的实现
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(Runtime, freeMemory, "!()J"),
NATIVE_METHOD(Runtime, totalMemory, "!()J"),
NATIVE_METHOD(Runtime, maxMemory, "!()J"),
NATIVE_METHOD(Runtime, gc, "()V"),
NATIVE_METHOD(Runtime, nativeExit, "(I)V"),
NATIVE_METHOD(Runtime, nativeLoad,
"(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/String;)"
"Ljava/lang/String;"),//nativeLoad方法注册在本地的方法名是Runtime_nativeLoad
};
void register_java_lang_Runtime(JNIEnv* env) {
jniRegisterNativeMethods(env, "java/lang/Runtime", gMethods, NELEM(gMethods));
}//注册gMethods中的方法,这个函数是直接由虚拟机调用
JVM_NativeLoad方法申明在jvm.h中,实现在OpenjdkJvm.cc中
//libcore/ojluni/src/main/native/jvm.h
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env, jstring javaFilename,
jobject javaLoader, jstring javaLibrarySearchPath);
//这里只是对JVM_NativeLoad进行了申明,但是实现在哪里呢,继续向下
//art/runtime/openjdkjvm/OpenjdkJvm.cc
/* * This file implements interfaces from the file jvm.h. This implementation * is licensed under the same terms as the file jvm.h. The * copyright and license information for the file jvm.h follows. */
#include "../../libcore/ojluni/src/main/native/jvm.h"
//JVM_NativeLoad方法的实现
JNIEXPORT jstring JVM_NativeLoad(JNIEnv* env,
jstring javaFilename,
jobject javaLoader,
jstring javaLibrarySearchPath) {
ScopedUtfChars filename(env, javaFilename);
if (filename.c_str() == NULL) {
return NULL;
}
std::string error_msg;
{
art::JavaVMExt* vm = art::Runtime::Current()->GetJavaVM();//返回的是JavaVMExt对象,实现下面有
bool success = vm->LoadNativeLibrary(env,
filename.c_str(),
javaLoader,
javaLibrarySearchPath,
&error_msg);//调用JavaVMExt的LoadNativeLibrary方法
if (success) {
return nullptr;
}
}
// Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
env->ExceptionClear();
return env->NewStringUTF(error_msg.c_str());
}
//art/runtime/Runtime.h
static Runtime* instance_;
JavaVMExt* java_vm_;
class Runtime{
static Runtime* Current() {
return instance_;
}
JavaVMExt* GetJavaVM() const {
return java_vm_;
}
}
//下面是JavaVMExt的LoadNativeLibrary方法实现的实现比较长,请大家挑重点查看,重点地方我有备注
//art/runtime/java_vm_ext.cc
bool JavaVMExt::LoadNativeLibrary(JNIEnv* env,
const std::string& path,
jobject class_loader,
jstring library_path,
std::string* error_msg) {
error_msg->clear();
// See if we've already loaded this library. If we have, and the class loader
// matches, return successfully without doing anything.翻译成中文就是判断当前库有没有被加载,如果已经加载,则直接返回
// TODO: for better results we should canonicalize the pathname (or even compare
// inodes). This implementation is fine if everybody is using System.loadLibrary.
SharedLibrary* library;//创建SharedLibrary对象
Thread* self = Thread::Current();
{
// TODO: move the locking (and more of this logic) into Libraries.
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);//实例化library对象
}
void* class_loader_allocator = nullptr;
{
ScopedObjectAccess soa(env);
// As the incoming class loader is reachable/alive during the call of this function,
// it's okay to decode it without worrying about unexpectedly marking it alive.
mirror::ClassLoader* loader = soa.Decode<mirror::ClassLoader*>(class_loader);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();//获取ClassLinker对象
if (class_linker->IsBootClassLoader(soa, loader)) {
loader = nullptr;
class_loader = nullptr;
}
class_loader_allocator = class_linker->GetAllocatorForClassLoader(loader);
CHECK(class_loader_allocator != nullptr);
}
if (library != nullptr) {
// Use the allocator pointers for class loader equality to avoid unnecessary weak root decode.
if (library->GetClassLoaderAllocator() != class_loader_allocator) {
// The library will be associated with class_loader. The JNI
// spec says we can't load the same library into more than one
// class loader.
StringAppendF(error_msg, "Shared library \"%s\" already opened by "
"ClassLoader %p; can't open in ClassLoader %p",
path.c_str(), library->GetClassLoader(), class_loader);
LOG(WARNING) << error_msg;
return false;
}
VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
<< " ClassLoader " << class_loader << "]";
if (!library->CheckOnLoadResult()) {
StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
"to load \"%s\"", path.c_str());
return false;
}
return true;
}
// Open the shared library. Because we're using a full path, the system
// doesn't have to search through LD_LIBRARY_PATH. (It may do so to
// resolve this library's dependencies though.)
// Failures here are expected when java.library.path has several entries
// and we have to hunt for the lib.
// Below we dlopen but there is no paired dlclose, this would be necessary if we supported
// class unloading. Libraries will only be unloaded when the reference count (incremented by
// dlopen) becomes zero from dlclose.
Locks::mutator_lock_->AssertNotHeld(self);
const char* path_str = path.empty() ? nullptr : path.c_str();
void* handle = android::OpenNativeLibrary(env,
runtime_->GetTargetSdkVersion(),
path_str,
class_loader,
library_path);
bool needs_native_bridge = false;
if (handle == nullptr) {
if (android::NativeBridgeIsSupported(path_str)) {
handle = android::NativeBridgeLoadLibrary(path_str, RTLD_NOW);
needs_native_bridge = true;
}
}
VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_NOW) returned " << handle << "]";
if (handle == nullptr) {
*error_msg = dlerror();
VLOG(jni) << "dlopen(\"" << path << "\", RTLD_NOW) failed: " << *error_msg;
return false;
}
if (env->ExceptionCheck() == JNI_TRUE) {
LOG(ERROR) << "Unexpected exception:";
env->ExceptionDescribe();
env->ExceptionClear();
}
// Create a new entry.
// TODO: move the locking (and more of this logic) into Libraries.
bool created_library = false;
{
// Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
std::unique_ptr<SharedLibrary> new_library(
new SharedLibrary(env, self, path, handle, class_loader, class_loader_allocator));
MutexLock mu(self, *Locks::jni_libraries_lock_);
library = libraries_->Get(path);
if (library == nullptr) { // We won race to get libraries_lock.
library = new_library.release();
libraries_->Put(path, library);//将我们指定的库加载进来,保存在library对象中
created_library = true;
}
}
if (!created_library) {
LOG(INFO) << "WOW: we lost a race to add shared library: "
<< "\"" << path << "\" ClassLoader=" << class_loader;
return library->CheckOnLoadResult();
}
VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
bool was_successful = false;
void* sym;
if (needs_native_bridge) {
library->SetNeedsNativeBridge();
}
sym = library->FindSymbol("JNI_OnLoad", nullptr);//在我们要加载so库中查找JNI_OnLoad方法,如果没有系统就认为是静态注册方式进行的,直接返回true,代表so库加载成功,如果找到JNI_OnLoad就会调用JNI_OnLoad方法,JNI_OnLoad方法中一般存放的是方法注册的函数,所以如果采用动态注册就必须要实现JNI_OnLoad方法,否则调用java中申明的native方法时会抛出异常,下面有JNI_OnLoad的实现
if (sym == nullptrr) {
VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
was_successful = true;
} else {
// Call JNI_OnLoad. We have to override the current class
// loader, which will always be "null" since the stuff at the
// top of the stack is around Runtime.loadLibrary(). (See
// the comments in the JNI FindClass function.)
ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
self->SetClassLoaderOverride(class_loader);
VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
typedef int (*JNI_OnLoadFn)(JavaVM*, void*);//定义了一个
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);//sym是void*类型的,任何一个类型都可以用void*类型进行传递,但是void*类型是不能够调用的,所用在调用之前,需要将void*代表的类型转换为其原来的类型,在这里,把sym重新解释为JNI_OnLoadFn,sym指向的是JNI_OnLoad,JNI_OnLoad和JNI_OnLoadFn是相同的类型,其实在我的理解中就是让JNI_OnLoadFn指向JNI_OnLoad函数的地址,这样调用JNI_OnLoadFn就像调用JNI_OnLoad一样
int version = (*jni_on_load)(this, nullptr);//调用JNI_OnLoad函数,version为JNI_OnLoad函数的返回值
if (runtime_->GetTargetSdkVersion() != 0 && runtime_->GetTargetSdkVersion() <= 21) {
fault_manager.EnsureArtActionInFrontOfSignalChain();
}//判断SDK版本
self->SetClassLoaderOverride(old_class_loader.get());
if (version == JNI_ERR) {//判断返回值是否异常
StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
} else if (IsBadJniVersion(version)) {//判断返回值是否符合标准
StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
path.c_str(), version);
// It's unwise to call dlclose() here, but we can mark it
// as bad and ensure that future load attempts will fail.
// We don't know how far JNI_OnLoad got, so there could
// be some partially-initialized stuff accessible through
// newly-registered native method calls. We could try to
// unregister them, but that doesn't seem worthwhile.
} else {
was_successful = true;
}
VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
<< " from JNI_OnLoad in \"" << path << "\"]";
}
library->SetResult(was_successful);
return was_successful;
}
//下面这个函数告诉我们本地的JNI_OnLoad函数必须返回JNI_VERSION_1_2、JNI_VERSION_1_4或者JNI_VERSION_1_6这三个值才算成功否则都是失败
static bool IsBadJniVersion(int version) {
// We don't support JNI_VERSION_1_1. These are the only other valid versions.
return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
}
接下来我们来看一下JNI_OnLoad函数:
以android_media_MediaPlayer.cpp和 MediaPlayer.java和 为例:
//×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
//将需要注册的方法都放到gMethods数组中,关于JNINativeMethod网上有很多解释,可以自行百度,这里我们不关心
//×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
static const JNINativeMethod gMethods[] = {//以下这些方法在MediaPlayer.java中都有申明
{
"nativeSetDataSource",
"(Landroid/os/IBinder;Ljava/lang/String;[Ljava/lang/String;"
"[Ljava/lang/String;)V",
(void *)android_media_MediaPlayer_setDataSourceAndHeaders
},
{"_setDataSource", "(Ljava/io/FileDescriptor;JJ)V", (void *)android_media_MediaPlayer_setDataSourceFD},
{"_setDataSource", "(Landroid/media/MediaDataSource;)V",(void *)android_media_MediaPlayer_setDataSourceCallback },
{"_setVideoSurface", "(Landroid/view/Surface;)V", (void *)android_media_MediaPlayer_setVideoSurface},
{"_prepare", "()V", (void *)android_media_MediaPlayer_prepare},
{"prepareAsync", "()V", (void *)android_media_MediaPlayer_prepareAsync},
{"_start", "()V", (void *)android_media_MediaPlayer_start},
{"_stop", "()V", (void *)android_media_MediaPlayer_stop},
{"getVideoWidth", "()I", (void *)android_media_MediaPlayer_getVideoWidth},
{"getVideoHeight", "()I", (void *)android_media_MediaPlayer_getVideoHeight},
{"setPlaybackParams", "(Landroid/media/PlaybackParams;)V", (void *)android_media_MediaPlayer_setPlaybackParams},
{"getPlaybackParams", "()Landroid/media/PlaybackParams;", (void *)android_media_MediaPlayer_getPlaybackParams},
{"setSyncParams", "(Landroid/media/SyncParams;)V", (void *)android_media_MediaPlayer_setSyncParams},
{"getSyncParams", "()Landroid/media/SyncParams;", (void *)android_media_MediaPlayer_getSyncParams},
{"seekTo", "(I)V", (void *)android_media_MediaPlayer_seekTo},
{"_pause", "()V", (void *)android_media_MediaPlayer_pause},
{"isPlaying", "()Z", (void *)android_media_MediaPlayer_isPlaying},
{"getCurrentPosition", "()I", (void *)android_media_MediaPlayer_getCurrentPosition},
{"getDuration", "()I", (void *)android_media_MediaPlayer_getDuration},
{"_release", "()V", (void *)android_media_MediaPlayer_release},
{"_reset", "()V", (void *)android_media_MediaPlayer_reset},
{"_setAudioStreamType", "(I)V", (void *)android_media_MediaPlayer_setAudioStreamType},
{"_getAudioStreamType", "()I", (void *)android_media_MediaPlayer_getAudioStreamType},
{"setParameter", "(ILandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_setParameter},
{"setLooping", "(Z)V", (void *)android_media_MediaPlayer_setLooping},
{"isLooping", "()Z", (void *)android_media_MediaPlayer_isLooping},
{"_setVolume", "(FF)V", (void *)android_media_MediaPlayer_setVolume},
{"native_invoke", "(Landroid/os/Parcel;Landroid/os/Parcel;)I",(void *)android_media_MediaPlayer_invoke},
{"native_setMetadataFilter", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_setMetadataFilter},
{"native_getMetadata", "(ZZLandroid/os/Parcel;)Z", (void *)android_media_MediaPlayer_getMetadata},
{"native_init", "()V", (void *)android_media_MediaPlayer_native_init},
{"native_setup", "(Ljava/lang/Object;)V", (void *)android_media_MediaPlayer_native_setup},
{"native_finalize", "()V", (void *)android_media_MediaPlayer_native_finalize},
{"getAudioSessionId", "()I", (void *)android_media_MediaPlayer_get_audio_session_id},
{"setAudioSessionId", "(I)V", (void *)android_media_MediaPlayer_set_audio_session_id},
{"_setAuxEffectSendLevel", "(F)V", (void *)android_media_MediaPlayer_setAuxEffectSendLevel},
{"attachAuxEffect", "(I)V", (void *)android_media_MediaPlayer_attachAuxEffect},
{"native_pullBatteryData", "(Landroid/os/Parcel;)I", (void *)android_media_MediaPlayer_pullBatteryData},
{"native_setRetransmitEndpoint", "(Ljava/lang/String;I)I", (void *)android_media_MediaPlayer_setRetransmitEndpoint},
{"setNextMediaPlayer", "(Landroid/media/MediaPlayer;)V", (void *)android_media_MediaPlayer_setNextMediaPlayer},
};
//×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
// This function only registers the native methods
//对gMethods中的方法进行注册
//×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
static int register_android_media_MediaPlayer(JNIEnv *env)
{
return AndroidRuntime::registerNativeMethods(env,
"android/media/MediaPlayer", gMethods, NELEM(gMethods));
}
//×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
//JNI_OnLoad方法,调用registerNativeMethods方法
//×××××××××××××××××××××××××××××××××××××××××××××××××××××××××××
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;
}
//调用register_android_media_MediaPlayer方法进行注册
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;
}