android学习笔记之JNI

转载请注明出处:http://blog.csdn.net/droyon/article/details/8661672

在android中,存在很多Native代码调用,这些调用如何实现的,当初很好奇,终于在深入理解android中找到了答案,现在将自己看书的心得罗列出来,为自己复习之需。

1、MediaScanner.java,在这个方法中通过System.loadLibrary方法加载jni库。这样就可以调用native方法了。

static {
        System.loadLibrary("media_jni");
        native_init();
    }
其中native_init方法就是个native方法。

private static native final void native_init();
native方法的实现包含在我们加载的库里面,jni库相当于一个桥梁,连接起了java世界和native世界。在这里jni通过libmedia_jni.so库文件(linux系统会在库名字的前面加上lib),连接起了MediaScanner和libmedia.so两个世界。

具体看一下实现:关于实现在文件frameworks/base/media/jni/android_media_MediaScanner.cpp中。

static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
    LOGV("native_init");
    jclass clazz = env->FindClass(kClassMediaScanner);
    if (clazz == NULL) {
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
    if (fields.context == NULL) {
        return;
    }
}
native_init方法的主要作用就是初始化,但是如何认定这个方法就是我们native_init方法的实现,有什么规则?。

在jni中,使用JNINativeMethod结构体来保存java native方法和jni函数之间的一一对应关系。

static JNINativeMethod gMethods[] = {
    {
        "processDirectory",
        "(Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
        (void *)android_media_MediaScanner_processDirectory
    },

    {
        "processFile",
        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
        (void *)android_media_MediaScanner_processFile
    },

    {
        "setLocale",
        "(Ljava/lang/String;)V",
        (void *)android_media_MediaScanner_setLocale
    },

    {
        "extractAlbumArt",
        "(Ljava/io/FileDescriptor;)[B",
        (void *)android_media_MediaScanner_extractAlbumArt
    },

    {
        "native_init",
        "()V",
        (void *)android_media_MediaScanner_native_init
    },
......
}

这个结构体中,中间部分是函数签名,保存了java在调用native函数时的传入的参数以及返回值。

这是一种映射,将native函数映射到函数指针,但是通过这样映射,还不能保证当我们执行native_init方法时会执行到函数指针android_media_MediaScanner_native_init,因为我们还没有注册,注册实现:

int register_android_media_MediaScanner(JNIEnv *env)
{
    return AndroidRuntime::registerNativeMethods(env,
                kClassMediaScanner, gMethods, NELEM(gMethods));
}
这个注册实现会调用AndroidRunntime下的一个registerNativeMethods方法,关于这个方法的实现:AndroidRunntime.cpp中

/*static*/ int AndroidRuntime::registerNativeMethods(JNIEnv* env,
    const char* className, const JNINativeMethod* gMethods, int numMethods)
{
    return jniRegisterNativeMethods(env, className, gMethods, numMethods);
}
jniRegisterNativeMethods方法的实现在JNIHelp.c中实现。

既然调用register_android_media_MediaScanner这个方法可以实现我们native_init方法的注册,那么register_android_media_MediaScanner这个方法在那里调用的那?

解答:我们在System.loadLibrary加载jni库的时候,就会在这个jni类库的源文件中查找JNI_OnLoad方法。在这个方法中实现了这个方法的调用。

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("ERROR: GetEnv failed\n");
        goto bail;
    }
    assert(env != NULL);

    if (register_android_media_MediaPlayer(env) < 0) {
        LOGE("ERROR: MediaPlayer native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaRecorder(env) < 0) {
        LOGE("ERROR: MediaRecorder native registration failed\n");
        goto bail;
    }

    if (register_android_media_MediaScanner(env) < 0) {
        LOGE("ERROR: MediaScanner native registration failed\n");
        goto bail;
    }
.......
}
这样就注册了gMethods结构体数组到jni中,那么我们的nativie_init方法到android_media_MediaScanner_native_init之间的关联也就建立起来了。


jni可以实现让我们在java层调用c层实现的函数,同时也可以让我们在c层中调用java层的方法。实现如下:

在c层:

static const char* const kClassMediaScannerClient =
        "android/media/MediaScannerClient";
定义了kClassMediaScannerClient字符串,然后通过GetFieldID方法或者GetMethodID方法得到属性或者方法的id值。

jclass mediaScannerClientInterface =
                env->FindClass(kClassMediaScannerClient);
mScanFileMethodID = env->GetMethodID(
                                    mediaScannerClientInterface,
                                    "scanFile",
                                    "(Ljava/lang/String;JJZZ)V");
mScanFildMethodID就是上层java类MedisScannerClient中scanFile方法的jni层方法调用id,GetMethodID方法的第三个方法是签名,括号里面是传入的参数,外面是返回值。
public interface MediaScannerClient
{    
    public void scanFile(String path, long lastModified, long fileSize,
            boolean isDirectory, boolean noMedia);
......
}
我们拿到了这个mScanFieldMethodID,我们可以通过CallVoidMethod来调用。

mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,
                fileSize, isDirectory, noMedia);

对于属性GetFieldID也一样。


------------------------------------------------------------------------------------------------------

在android代码framework/base/core/jni下提供了大部分framework层native方法的实现,这些jni实现代码,会编译生成libandroid_runtime库,这个库中包含了一下文件:

LOCAL_SRC_FILES:= \
	ActivityManager.cpp \
	AndroidRuntime.cpp \
	Time.cpp \
	com_android_internal_content_NativeLibraryHelper.cpp \
	com_google_android_gles_jni_EGLImpl.cpp \
	com_google_android_gles_jni_GLImpl.cpp.arm \
	android_app_NativeActivity.cpp \
	android_opengl_GLES10.cpp \
	android_opengl_GLES10Ext.cpp \
	android_opengl_GLES11.cpp \
	android_opengl_GLES11Ext.cpp \
	android_opengl_GLES20.cpp \
	android_database_CursorWindow.cpp \
	android_database_SQLiteCompiledSql.cpp \
	android_database_SQLiteDebug.cpp \
	android_database_SQLiteDatabase.cpp \
	android_database_SQLiteProgram.cpp \
	android_database_SQLiteQuery.cpp \
	android_database_SQLiteStatement.cpp \
	android_emoji_EmojiFactory.cpp \
	android_view_Display.cpp \
	android_view_Surface.cpp \
	android_view_TextureView.cpp \
	android_view_InputChannel.cpp \
	android_view_InputQueue.cpp \
	android_view_KeyEvent.cpp \
	android_view_KeyCharacterMap.cpp \
	android_view_HardwareRenderer.cpp \
	android_view_GLES20Canvas.cpp \
	android_view_MotionEvent.cpp \
	android_view_PointerIcon.cpp \
	android_view_VelocityTracker.cpp \
	android_text_AndroidCharacter.cpp \
	android_text_AndroidBidi.cpp \
	android_os_Debug.cpp \
	android_os_FileUtils.cpp \
	android_os_MemoryFile.cpp \
	android_os_MessageQueue.cpp \
	android_os_ParcelFileDescriptor.cpp \
	android_os_Power.cpp \
	android_os_StatFs.cpp \
	android_os_SystemClock.cpp \
	android_os_SystemProperties.cpp \
	android_os_UEventObserver.cpp \
	android_net_LocalSocketImpl.cpp \
	android_net_NetUtils.cpp \
	android_net_TrafficStats.cpp \
	android_net_wifi_Wifi.cpp \
	android_nio_utils.cpp \
	android_nfc_NdefMessage.cpp \
	android_nfc_NdefRecord.cpp \
	android_text_format_Time.cpp \
	android_util_AssetManager.cpp \
	android_util_Binder.cpp \
	android_util_EventLog.cpp \
	android_util_Log.cpp \
	android_util_FloatMath.cpp \
	android_util_Process.cpp \
	android_util_StringBlock.cpp \
	android_util_XmlBlock.cpp \
	android/graphics/AutoDecodeCancel.cpp \
	android/graphics/Bitmap.cpp \
	android/graphics/BitmapFactory.cpp \
	android/graphics/Camera.cpp \
	android/graphics/Canvas.cpp \
	android/graphics/ColorFilter.cpp \
	android/graphics/DrawFilter.cpp \
	android/graphics/CreateJavaOutputStreamAdaptor.cpp \
	android/graphics/Graphics.cpp \
	android/graphics/HarfbuzzSkia.cpp \
	android/graphics/Interpolator.cpp \
	android/graphics/LayerRasterizer.cpp \
	android/graphics/MaskFilter.cpp \
	android/graphics/Matrix.cpp \
	android/graphics/Movie.cpp \
	android/graphics/NinePatch.cpp \
	android/graphics/NinePatchImpl.cpp \
	android/graphics/NinePatchPeeker.cpp \
	android/graphics/Paint.cpp \
	android/graphics/Path.cpp \
	android/graphics/PathMeasure.cpp \
	android/graphics/PathEffect.cpp \
	android_graphics_PixelFormat.cpp \
	android/graphics/Picture.cpp \
	android/graphics/PorterDuff.cpp \
	android/graphics/BitmapRegionDecoder.cpp \
	android/graphics/Rasterizer.cpp \
	android/graphics/Region.cpp \
	android/graphics/Shader.cpp \
	android/graphics/SurfaceTexture.cpp \
	android/graphics/TextLayout.cpp \
	android/graphics/TextLayoutCache.cpp \
	android/graphics/Typeface.cpp \
	android/graphics/Utils.cpp \
	android/graphics/Xfermode.cpp \
	android/graphics/YuvToJpegEncoder.cpp \
	android_media_AudioRecord.cpp \
	android_media_AudioSystem.cpp \
	android_media_AudioTrack.cpp \
	android_media_JetPlayer.cpp \
	android_media_ToneGenerator.cpp \
	android_hardware_Camera.cpp \
	android_hardware_SensorManager.cpp \
	android_hardware_UsbDevice.cpp \
	android_hardware_UsbDeviceConnection.cpp \
	android_hardware_UsbRequest.cpp \
	android_debug_JNITest.cpp \
	android_util_FileObserver.cpp \
	android/opengl/poly_clip.cpp.arm \
	android/opengl/util.cpp.arm \
	android_bluetooth_HeadsetBase.cpp \
	android_bluetooth_common.cpp \
	android_bluetooth_BluetoothAudioGateway.cpp \
	android_bluetooth_BluetoothSocket.cpp \
	android_bluetooth_c.c \
	android_server_BluetoothService.cpp \
	android_server_BluetoothEventLoop.cpp \
	android_server_BluetoothA2dpService.cpp \
	android_server_NetworkManagementSocketTagger.cpp \
	android_server_Watchdog.cpp \
	android_ddm_DdmHandleNativeHeap.cpp \
	com_android_internal_os_ZygoteInit.cpp \
	android_backup_BackupDataInput.cpp \
	android_backup_BackupDataOutput.cpp \
	android_backup_FileBackupHelperBase.cpp \
	android_backup_BackupHelperDispatcher.cpp \
	android_app_backup_FullBackup.cpp \
	android_content_res_ObbScanner.cpp \
	android_content_res_Configuration.cpp \
    android_animation_PropertyValuesHolder.cpp
以及一些共享库

LOCAL_SHARED_LIBRARIES := \
	libexpat \
	libnativehelper \
	libcutils \
	libutils \
	libbinder \
	libnetutils \
	libui \
	libgui \
	libcamera_client \
	libskia \
	libsqlite \
	libdvm \
	libEGL \
	libGLESv1_CM \
	libGLESv2 \
	libETC1 \
	libhardware \
	libhardware_legacy \
	libsonivox \
	libcrypto \
	libssl \
	libicuuc \
	libicui18n \
	libmedia \
	libwpa_client \
	libjpeg \
	libnfc_ndef \
	libusbhost \
	libharfbuzz \
	libz \
我们的framework层代码在调用native方法之前,是一定要通过System.loadLibrary(jni库名字)方法加载jni库的,不然后报错。这个jni库包含了
android_bluetooth_BluetoothSocket.cpp \
	android_bluetooth_c.c \
	android_server_BluetoothService.cpp \
蓝牙的几个native方法实现库,这么说,蓝牙要想正常工作,这个库是一定要在开机加载的,那么这个库在那里加载那?在蓝牙模块之前找不到,在系统中grep,终于找打了两个地方:

1、WithFramework.java

/**
 * Binds native framework methods and then invokes a main class with the
 * remaining arguments.
 */
class WithFramework {

    /**
     * Invokes main(String[]) method on class in args[0] with args[1..n].
     */
    public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            printUsage();
            return;
        }

        Class<?> mainClass = Class.forName(args[0]);

        System.loadLibrary("android_runtime");
        if (registerNatives() < 0) {
            throw new RuntimeException("Error registering natives.");
        }

        String[] newArgs = new String[args.length - 1];
        System.arraycopy(args, 1, newArgs, 0, newArgs.length);
        Method mainMethod = mainClass.getMethod("main", String[].class);
        mainMethod.invoke(null, new Object[] { newArgs });
    }
。。。。。。

}
2、LoadClass.java

/**
 * Loads a class, runs the garbage collector, and prints showmap output.
 *
 * <p>Usage: dalvikvm LoadClass [class name]
 */
class LoadClass {

    public static void main(String[] args) {
        System.loadLibrary("android_runtime");

        if (registerNatives() < 0) {
            throw new RuntimeException("Error registering natives.");    
        }

        Debug.startAllocCounting();

        if (args.length > 0) {
            try {
                long start = System.currentTimeMillis();
                Class.forName(args[0]);
                long elapsed = System.currentTimeMillis() - start;
                Log.i("LoadClass", "Loaded " + args[0] + " in " + elapsed
                        + "ms.");
            } catch (ClassNotFoundException e) {
                Log.w("LoadClass", e);
                return;
            }
        }
有可能是第一个方法,但不知道WithFramework.java怎么开机启动的?

留待以后研究。

javah可以得到java文件中native方法的jni c class框架。只需在c框架中实现函数功能即可。

javah -d ~/jnic -jni com.android.example.Test

javap 可以查看java函数的输入和返回参数。

javap -s com.android.example.Test

ps:

jni持久化对象的技巧


jni中声明一个结构体或者一个类,jni把这个对象当成一个int值传递给java端的object,在jni下次需要使用时,在引用到java端的int对象,把该int对象强制转换为jni存储前的对象。




你可能感兴趣的:(jni学习笔记)