Android——JNI On Android

来源:互联网 发布:php 按钮disable 编辑:程序博客网 时间:2024/05/23 13:36
1.为什么使用JNI
    很长时间我就想写关于这方面的东西,其实弄android已经有段时间了,虽然不太喜欢它,但是还是有然我兴奋的地方,那就是JNI,因为那里有我熟悉的C和LInux。JNI简单的说就是一种能够让你在Java里使用C/C++代码的一种技术。
    在Android里,Java代码运行在Java虚拟机里(应该是叫做Dalvik),这个虚拟机可以说是java世界的神,但同时也是拖android世界后退的家伙。但是android里你可以有另一种选择,那就是JNI技术,它可以让你在高速公路上尽情的玩耍,而不是乡间小路上。
   这种机制允许你将耗时的工作使用C/C++来完成,剩余的部分使用Java,使用JNI来完成Java对C/C++的调用。
2.如何使用JNI
    首先,你应该很明确,你为什么使用JNI,怎么学习JNI,当然互联网上有许多这样的教学,包括android NDK的实例。
    JNI的基本思想很简单,你可以写一个C/C++文件,然后导出里面的方法,然后在Java的文件里进行调用这些方法。虽然很简单,但是为了完成这样的任务,你需要遵守一些约定。
   一个好的开始就是通过一个简单的工程来演示这样技术:
首先是简单的java代码:(通用Activity在屏幕上写一些字符 )
package com.Hello;import android.app.Activity;import android.os.Bundle;import android.widget.TextView;import android.util.Log;public class Hello extends Activity {    static { try { Log.i("JNI", "Trying to load libHello.so"); System.loadLibrary("Hello"); } catch (UnsatisfiedLinkError ule) { Log.e("JNI", "WARNING: Could not load libHello.so"); } }    // Important part : this method is native, as in imported from C++     native private int add(int a, int b); @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState); // Call simple native method        int res = add(3,5); // Just print the result on screen TextView tv = new TextView(this); tv.setText("C++ tells you that 3+5 = " + res); setContentView(tv); }}代码很简洁,这里有两个新的东西一个是System.loadLibrary(“Hello”),它的意思就是加载Hello.so库;另个就是native关键字,这个关键字,告诉Java这里的add方法在额外的原生库中查找。将上面的文件保存为Hello.java文件。Android.mk文件内容如下:LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := samplesLOCAL_PACKAGE_NAME := HelloLOCAL_SRC_FILES := Hello.javaLOCAL_SDK_VERSION := currentinclude $(BUILD_PACKAGE)另外AndroidManifest.xml文件内容如下:<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.Hello"> <application android:label="Hello"> <activity android:name="Hello"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application></manifest>接下来放这3个文件到同一个目录,然后设置android编译环境如:(source build/envsetup.sh),之后在该目录下执行mm,这样你的Hello.apk就编译好了。接下来就是C++语言代码:#define LOG_TAG "Hello"#include "utils/Log.h"#include <stdlib.h>#include <string.h>#include <unistd.h>#include <assert.h>#include "jni.h"/* * Trivial method : add two numbers * */static jint Hello_add(JNIEnv* env, jobject thiz, jint a, jint b){    return (jint)(a + b);}/* * Array of methods. * * Each entry has three fields: the name of the method, the method * signature, and a pointer to the native implementation. */static const JNINativeMethod gMethods[] = { { "add", "(II)I", (void*)Hello_add }};/* * Explicitly register all methods for our class. * * Returns 0 on success. */static int registerMethods(JNIEnv* env) { static const char* const kClassName = "com/Hello/Hello"; jclass clazz;    /* look up the class */ clazz = env->FindClass(kClassName)if (clazz == NULL) { LOGE("Can't find class %s\n", kClassName)return -1} /* register all the methods */ if (env->RegisterNatives(clazz, gMethods,            sizeof(gMethods) / sizeof(gMethods[0])) != JNI_OK) { LOGE("Failed registering methods for %s\n", kClassName)return -1}    return 0;}/* * This is called by the VM when the shared library is first loaded. */jint JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { LOGE("ERROR: GetEnv failed\n")goto bail; } assert(env != NULL)if (registerMethods(env) != 0) {        LOGE("ERROR: native registration failed\n")goto bail; } /* success -- return valid version number */    result = JNI_VERSION_1_4;bail:    return result;}   这里关于add函数很简单,相加后返回和。JNI的约定意味着该函数的前两个参数(JNIEnv是一个与线程相关的变量,实际上就是提供一些JNI系统函数,通过这些函数可以调用Java的函数,操作jobject)隐示从Java中传过来,Java代码仅仅调用 add(a,b)。   然后你需要声明这个方法在这个数组里面,当然这里有些奇怪的标签相关的内容可以看这里:http://docs.oracle.com/javase/1.5.0/docs/guide/jni/spec/types.html#wp16432。JNINativeMethod类中,地一个变量为函数的名称,第二个参数为参数和返回值的说明,第三个是JNI层对应的函数指针。这个数组最后通过这个类型的注册方法RegisterNatives注册到JNI层中。注册的时候首先通过它的名字(kClassName),获得它的类型名(clazz)。   当然这里还有一个问题,就是add方法在什么时候,什么地方被动态调用注册的呢?答案是:当Java层通过System.loadLibary加载JNI动态库后,紧接着查找一个叫做JNI_OnLoad的函数。如果有,就调用它,而动态注册工作就是在这里完成的也就是上面源码中JNI_OnLoad的作用。同样,Android.mk文件的内容如下:LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE_TAGS := samplesLOCAL_MODULE:= libHelloLOCAL_SRC_FILES:= Hello.c# Additional libraries, maybe more than actually neededLOCAL_SHARED_LIBRARIES := \ libandroid_runtime \ libnativehelper \ libcutils \ libutils# JNI headersLOCAL_C_INCLUDES += $(JNI_H_INCLUDE)LOCAL_PRELINK_MODULE := falseinclude $(BUILD_SHARED_LIBRARY)将这两文件放在同一个文件夹下面,然后运行mm,就生成libHello.so文件。3.运行:  首先安装Hello.apk到系统中,然后复制libHello.so到/system/lib目录下,或者使用System.load()代替System.loadLibrary(),通过指定完全路径,你可以将库放到任何你想放的目录下。前提是玩过Android我想你懂得!后继:1.上面是通过动态方法进行的JNI的调用,当然相对于动态还有一种就是静态,静态方法大致就先编译java代码,生成.class文件,然后使用javac -o output packagename.classname,这样就生成一个output.h的JNI层头文件,只要实现里面的对应的函数即可     2.另外在编译的时候,所放的目录是在android源码的某一个目录下,当然make,mmm都是可以的,make就是全编译,比较慢,mmm会将依赖全部编译,当然也不错。注:代码翻译自http://www.upche.org/doku.php?id=wiki:jni