• Andorid——ubuntu下的 NDK / JNI


         之前一直有接触源代码里面的JNI体系,知道个大概,仅仅管调进了哪个C/C++的接口,如今记录学习下。

                      

                                                     撰写不易,转载请注明出处: http://blog.csdn.net/jscese/article/details/39645485


    概念:

    NDK - Native Development Kit ,类似SDK性质,能够看作为一个编译工具的集合。

    在android开发中经常使用于将C/C++代码打包编译成android 应用程序可以载入使用的模块。像动态静态库 .a ,.so.

    来自百科。NDK包含了
    • 从C / C++生成原生代码库所须要的工具和build files。

    • 将一致的原生库嵌入能够在Android设备上部署的应用程序包文件(application packages files ,即.apk文件)中。
    • 支持全部未来Android平台的一些列原生系统头文件和库

    JNI - Java Native Interface , android的应用层都是java写的。都是交给dalvik进行转码成二进制再执行的,在执行效率上远低与C/C++程序,

    所以就有了JNI机制。通过JNI 我们能够把一些复杂讲究效率的操作用C/C++语言来实现,让 java层来调用运行,提高效率!JNI 有一套自己的代码写法。


    我们一般使用NDK编译的动态库来为JNI服务的。


    NDK:

            能够到google上去下载NDK包,也可在http://www.androiddevtools.cn/中选择下载,下载样式类似 android-ndk-r9d-linux-x86_64.tar.bz2

    r9 代表版本号,下载之后 解压到想安装的位置,ubuntu下 直接解压之后,配置环境变量就可以,我的例如以下:

    #set android NDK environment
    export NDK_HOME=/usr/local/android-ndk-r9d
    export PATH=$PATH:$NDK_HOME

    source 之后,可在终端执行 ndk-build,出现例如以下:

    jscese@jscese-H61M-S2P:~$ ndk-build
    Android NDK: Could not find application project directory !    
    Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.    
    /usr/local/android-ndk-r9d/build/core/build-local.mk:148: *** Android NDK: Aborting    .  Stop.
    

    就代表已经成功安装。能够用它进行编译了!


    JNI:

          java本地的接口,能够用C 写 也能够用C++ 。仅仅是会有一些细微的区别,在C 或者 C++ 本地接口函数中是有  JNIEnv *env 这个指针參数的。

    C 调用;(*env)->

    C++ 调用: env->

    由于 jni.h 定义的不同,

    还有就是C++ 的源文件是须要引入相应的头文件的。

    从我写的一个样例来分析记录,一个 实现加减法的apk。算术操作放到C程序中运行。

    eclipse 中新建 androidproject,主java 文件:

    package com.jscese.test;
    
    import android.R.integer;
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.RemoteException;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    public class JNI extends Activity implements OnClickListener {
    	private static final String LOG_TAG = "JSCESE_JNI";
    	/** Called when the activity is first created. */
    
    	static {
    		// The runtime will add "lib" on the front and ".o" on the end of
    		// the name supplied to loadLibrary.
    		System.loadLibrary("jscesejni");
    	}
    
    	private EditText valueText1, valueText2 = null;
    	private Button addButton = null;
    	private Button subButton = null;
    	private Button clearButton = null;
    	private TextView vlaueText = null;
    
    	@Override
    	public void onCreate(Bundle savedInstanceState) {
    		super.onCreate(savedInstanceState);
    		setContentView(R.layout.main);
    
    		valueText1 = (EditText) findViewById(R.id.edit_value1);
    		valueText2 = (EditText) findViewById(R.id.edit_value2);
    		addButton = (Button) findViewById(R.id.button_add);
    		subButton = (Button) findViewById(R.id.button_sub);
    		clearButton = (Button) findViewById(R.id.button_clear);
    		vlaueText = (TextView) findViewById(R.id.value);
    
    		addButton.setOnClickListener(this);
    		subButton.setOnClickListener(this);
    		clearButton.setOnClickListener(this);
    	}
    
    	@Override
    	public void onClick(View v) {
    		String text1 = valueText1.getText().toString();
    		String text2 = valueText2.getText().toString();
    
    		if (v.equals(addButton)) {
    			int value = add(Integer.parseInt(text1), Integer.parseInt(text2));
    			vlaueText.setText(String.valueOf(value));
    		} else if (v.equals(subButton)) {
    			int value = sub(Integer.parseInt(text1), Integer.parseInt(text2));
    			vlaueText.setText(String.valueOf(value));
    		} else if (v.equals(clearButton)) {
    			String text = "";
    			valueText1.setText(text);
    			valueText2.setText(text);
    			vlaueText.setText(text);
    		}
    	}
    
    	public native int add(int a, int b);
    
    	public native int sub(int a, int b);
    }

    非常easy的一个apk 主Activity,须要注意的是这个apk启动的时候 会调用开头的 static中的 System.loadLibrary("jscesejni");

    这就是载入C/C++的动态库了,动态库原型应该是 libjscesejni.so 这里仅仅要写名字就好!

    另外还定义了加减法的两个本地方法。以关键词 native 修饰。将在 libjscesejni.so中实现。


    资源文件main.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical"
        android:weightSum="1" >
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:orientation="vertical" >
    
            <EditText
                android:id="@+id/edit_value1"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:hint="@string/hint" >
            </EditText>
    
            <EditText
                android:id="@+id/edit_value2"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:hint="@string/hint" >
            </EditText>
    
            <TextView
                android:id="@+id/value"
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:gravity="center_horizontal"
                android:hint="@string/value"
                android:textSize="25sp" />
        </LinearLayout>
    
        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:gravity="center"
            android:orientation="horizontal" >
    
            <Button
                android:id="@+id/button_add"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/add" >
            </Button>
    
            <Button
                android:id="@+id/button_sub"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:text="@string/sub" >
            </Button>
    
            <Button
                android:id="@+id/button_clear"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:text="@string/clear" >
            </Button>
        </LinearLayout>
    
    </LinearLayout>

    标准方法:

    首先在eclipse 里面编译这个androidproject。终端进入project文件夹bin/classes下。这个文件夹往下就是编译的java文件的class文件。层次是从包名到类名

    我这里就是 com/jscese/test/JNI.class  这是通过eclipse编译好的,也能够手动 javac JNI.java编译。

    生成 jni 类型的 头文件:

    须要用到 javah 命令,这个环节easy出错,由于路径參数等原因,贴出 javah 的help:

    使用方法:javah [选项] <类>
    
    当中 [选项] 包含:
    
    	-help                 输出此帮助消息并退出
    	-classpath <路径>     用于装入类的路径
    	-bootclasspath <路径> 用于装入引导类的路径
    	-d <文件夹>             输出文件夹
    	-o <文件>             输出文件(仅仅能使用 -d 或 -o 中的一个)
    	-jni                  生成 JNI样式的头文件(默认)
    	-version              输出版本号信息
    	-verbose              启用具体输出
    	-force		      始终写入输出文件
    
    使用全限定名称指定 <类>(例
    如,java.lang.Object)。
    

    非常明了。进入到 bin/class文件夹 ,那么当前文件夹就是 classpath ,运行:

    javah -classpath . -d ../../jni com.jscese.test.JNI

    会在project文件夹下的jni文件夹下生成 com_jscese_test_JNI.h  命令规则是 包名+类名

    内容例如以下:

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_jscese_test_JNI */
    
    #ifndef _Included_com_jscese_test_JNI
    #define _Included_com_jscese_test_JNI
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     com_jscese_test_JNI
     * Method:    add
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_jscese_test_JNI_add
      (JNIEnv *, jobject, jint, jint);
    
    /*
     * Class:     com_jscese_test_JNI
     * Method:    sub
     * Signature: (II)I
     */
    JNIEXPORT jint JNICALL Java_com_jscese_test_JNI_sub
      (JNIEnv *, jobject, jint, jint);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    函数定义比較奇怪。刚開始接触肯定不适应,这就是JNI 机制的特殊写法了! 以 JNIEXPORT jint JNICALL Java_com_jscese_test_JNI_add
      (JNIEnv *, jobject, jint, jint); 为例。 当中

    JNIEXPORT JNICALL keyword 代表这是JNI 调用的函数,

    jint 代表返回值,int 整型的变形

    函数名以Java开头然后是包名+类名+native方法名


    我们的C/C++的实现必须依据这个头文件定义的来实现。

    jni下新建一个 com_jscese_test_JNI.c 这个名字任意,写入内容:

    #include <jni.h>
    #include <assert.h>
    
    JNIEXPORT jint JNICALL Java_com_jscese_test_Jscese_add
      (JNIEnv *env, jobject thiz, jint a, jint b)
    {
    return (a+b);
    }
    
    JNIEXPORT jint JNICALL Java_com_jscese_test_Jscese_sub
      (JNIEnv *env, jobject thiz, jint a, jint b)
    {
    return (a-b);
    }

    同文件夹下新建Android.mk :


    LOCAL_PATH:= $(call my-dir)
    include $(CLEAR_VARS)
    
    LOCAL_MODULE:= libjscesejni
    LOCAL_SRC_FILES:= com_jscese_test_JNI.c
    LOCAL_SHARED_LIBRARIES := 
    	libutils
    LOCAL_LDLIBS :=-llog
    # Also need the JNI headers.
    LOCAL_C_INCLUDES += 
    	$(JNI_H_INCLUDES)
    
    include $(BUILD_SHARED_LIBRARY)

    不加JNI_ONLoad函数,默认会觉得JNI 版本号为1.1 ,少了一些特性。就这个样例不影响,这样code 已经完毕了,

    接下来就是编译so 然后打包进apk 里面了,在Android.mk 文件夹下 使用 ndk-build 命令编译

    正确例如以下:

    Android NDK: WARNING: APP_PLATFORM android-17 is larger than android:minSdkVersion 8 in /home/jscese/product_code/Mstar_Android/android/JB4.2/jb4.2/device/mstar/common/apps/Jscese_Jni/AndroidManifest.xml    
    [armeabi] Compile thumb  : jscesejni <= com_jscese_test_JNI.c
    [armeabi] SharedLibrary  : libjscesejni.so
    [armeabi] Install        : libjscesejni.so => libs/armeabi/libjscesejni.so
    

    能够看到生成了 mk中定义的 libjscesejni.so 而且安装到了libs/armeabi 文件夹下,最后再次eclipse编译整个 project,apk执行应该是OK 的。


    经常使用办法:

        避过上面的 javah 命令的运行,不參照生成的 jni 头文件来写 C/C++

    com_jscese_test_JNI.c内容例如以下:

    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #include <jni.h>
    #include <assert.h>
    
    #include<android/log.h>
    
    #define TAG "JsceseDemo-jni" 
    #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
    
    
     int Jscese_add(JNIEnv *env, jobject thiz, jint a, jint b)
    {
    return (a+b);
    }
     int Jscese_sub(JNIEnv *env, jobject thiz, jint a, jint b)
    {
    return (a-b);
    }
    #define JNIREG_CLASS "com/jscese/test/JNI"
    
    /**
    * Table of methods associated with a single class.
    */
    static JNINativeMethod gMethods[] = {
        { "add", "(II)I", (void*)Jscese_add },
        { "sub", "(II)I", (void*)Jscese_sub },
    };
    
    
    
    /*
    * Register several native methods for one class.
    */
    static int registerNativeMethods(JNIEnv* env, const char* className,
            JNINativeMethod* gMethods, int numMethods)
    {
    
        LOGW("jscese test jni in registerNativeMethods 0 ");
        jclass clazz;
        clazz = (*env)->FindClass(env, className);
        if (clazz == NULL) {
            LOGW("clazz NULL 0 ");
            return JNI_FALSE;
        }
        if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
            LOGW("RegisterNatives error 0 ");
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }
    /*
    * Register native methods for all classes we know about.
    */
    static int registerNatives(JNIEnv* env)
    {
        LOGW("jscese test jni in registerNatives ");
        if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
                                     sizeof(gMethods) / sizeof(gMethods[0])))
            return JNI_FALSE;
    
        return JNI_TRUE;
    }
    
    
    
    
    
     jint  JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = NULL;
        jint result = -1;
    
        if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            return -1;
        }
        assert(env != NULL);
    
        if (!registerNatives(env)) {//注冊
            return -1;
        }
        /* success -- return valid version number */
        result = JNI_VERSION_1_4;
    
        return result;
    }


    JNI_OnLoad函数作为动态库的入口函数。所以非常多初始化的操作都能够在这里,返回JNI 版本号,眼下基本都是1.4

    由于不採用标准方法 的头文件形式的函数命名,所以须要另外注冊映射关系。将java本地的接口函数跟 C/C++中的实现函数一一相应上。

    能够看到  gMethods 数组中的相应关系。中间的为參数说明。

    引入了 log.h 定义了LOGW 所以须要在Android.mk 中加入 include : LOCAL_LDLIBS :=-llog


    下面为网上对类型的一些资料

    參数类型的转换和表述方法例如以下:


    Java类型 本地类型 描写叙述
    boolean jboolean C/C++8位整型
    byte jbyte C/C++带符号的8位整型
    char jchar C/C++无符号的16位整型
    short jshort C/C++带符号的16位整型
    int jint C/C++带符号的32位整型
    long jlong C/C++带符号的64位整型e
    float jfloat C/C++32位浮点型
    double jdouble C/C++64位浮点型
    Object jobject 不论什么Java对象。或者没有相应java类型的对象
    Class jclass Class对象
    String jstring 字符串对象
    Object[] jobjectArray 不论什么对象的数组
    boolean[] jbooleanArray 布尔型数组
    byte[] jbyteArray 比特型数组
    char[] jcharArray 字符型数组
    short[] jshortArray 短整型数组
    int[] jintArray 整型数组
    long[] jlongArray 长整型数组
    float[] jfloatArray 浮点型数组
    double[] jdoubleArray 双浮点型数组
                                              ※     JNI类型映射


    JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的。还有一个是操作对象类型数组的。


    函数 Java数组类型 本地类型
    GetBooleanArrayElements jbooleanArray jboolean
    GetByteArrayElements jbyteArray jbyte
    GetCharArrayElements jcharArray jchar
    GetShortArrayElements jshortArray jshort
    GetIntArrayElements jintArray jint
    GetLongArrayElements jlongArray jlong
    GetFloatArrayElements jfloatArray jfloat
    GetDoubleArrayElements jdoubleArray jdouble

                                                   ※   JNI数组存取函数



    JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数。你能够创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。

    JNI通过ID识别域和方法。一个域或方法的ID是不论什么处理域和方法的函数的必须參数


    函数 描写叙述
    GetFieldID 得到一个实例的域的ID
    GetStaticFieldID 得到一个静态的域的ID
    GetMethodID 得到一个实例的方法的ID
    GetStaticMethodID 得到一个静态方法的ID

                                                ※   域和方法的函数



    Java 类型 符号
    boolean Z
    byte B
    char C
    short S
    int I
    long L
    float F
    double D
    void V
    objects对象 Lfully-qualified-class-name;L类名
    Arrays数组 [array-type [数组类型
    methods方法 (argument-types)return-type(參数类型)返回类型

                                               ※   确定域和方法的符号






  • 相关阅读:
    python的参数传递
    django的objects级别的权限控制
    django如何将mysql数据库转化为model
    django的orm查询使用in的保序
    多用户OFDM系统资源分配研究
    第一代到第四代多址技术:从FDMA、TDMA、CDMA到OFDMA
    Kaggle比赛总结
    4 二维数组中的查找 JavaScript
    5 替换空格 JavaScript
    简单的HTTP协议
  • 原文地址:https://www.cnblogs.com/tlnshuju/p/7152160.html
Copyright © 2020-2023  润新知