• Android中JNI的使用方法


    Android中JNI的使用方法

    首先看一下Android平台的框架图:

        

      可以看到Android上层的ApplicationApplicationFramework都是使用Java编写,

    底层包括系统和使用众多的LIiraries都是C/C++编写的。

      所以上层Java要调用底层的C/C++函数库必须通过JavaJNI来实现。

    下面将学习Android是如何通过Jni来实现JavaC/C++函数的调用。以HelloWorld程序为例:

    第一步:

    使用Java编写HelloWorld  Android应用程序:

    复制代码
    package com.lucyfyr;
    import android.app.Activity;
    import android.os.Bundle;
    import android.util.Log;
    
    public class HelloWorld extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
      Log.v("dufresne", printJNI("I am HelloWorld Activity"));
    }
      static
      {
        //加载库文件
        System.loadLibrary("HelloWorldJni");
      }
       //声明原生函数 参数为String类型 返回类型为String
      private native String printJNI(String inputStr);
    }
    复制代码

      这一步我们可以使用eclipse来生成一个App

    因为eclipse会自动为我们编译此Java文件,后面要是用到。

    第二步:

      生成共享库的头文件:

      进入到eclipse生成的Android Project中 :/HelloWorld/bin/classes/com/lucyfyr/ 下:

      可以看到里面后很多后缀为.class的文件,就是eclipse为我们自动编译好了的java文件,其中就有:

        HelloWorld.class文件。

      退回到classes一级目录:/HelloWorld/bin/classes/ 

    执行如下命令:

      javah com.lucyfyr.HelloWorld

      生成文件:com_lucyfyr_HelloWorld.h

     

    复制代码
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class com_lucyfyr_HelloWorld */
    #ifndef _Included_com_lucyfyr_HelloWorld
    #define _Included_com_lucyfyr_HelloWorld
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
    * Class: com_lucyfyr_HelloWorld
    * Method: printJNI
    * Signature: (Ljava/lang/String;)Ljava/lang/String;
    */
    JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI
    (JNIEnv *, jobject, jstring);
    #ifdef __cplusplus
    }
    #endif
    #endif
    复制代码

      可以看到自动生成对应的函数:Java_com_lucyfyr_HelloWorld_printJNI

        Java_ + 包名(com.lucyfyr+ 类名(HelloWorld) + 接口名(printJNI):必须要按此JNI规范来操作;

      java虚拟机就可以在com.simon.HelloWorld类调用printJNI接口的时候自动找到这个C实现的Native函数调用。

      当然函数名太长,可以在.c文件中通过函数名映射表来实现简化。

    第三步:

    实现JNI原生函数源文件:

    新建com_lucyfyr_HelloWorld.c文件:

     

    复制代码
    #include <jni.h>
    #define LOG_TAG "HelloWorld"
    #include <utils/Log.h>
    /* Native interface, it will be call in java code */
    JNIEXPORT jstring JNICALL Java_com_lucyfyr_HelloWorld_printJNI(JNIEnv *env, jobject obj,jstring inputStr)
    {
      LOGI("dufresne Hello World From libhelloworld.so!");
      // 从 instring 字符串取得指向字符串 UTF 编码的指针
      const char *str =
      (const char *)(*env)->GetStringUTFChars( env,inputStr, JNI_FALSE );
      LOGI("dufresne--->%s",(const char *)str);
      // 通知虚拟机本地代码不再需要通过 str 访问 Java 字符串。
      (*env)->ReleaseStringUTFChars(env, inputStr, (const char *)str );
      return (*env)->NewStringUTF(env, "Hello World! I am Native interface");
    } 
    /* This function will be call when the library first be load.
    * You can do some init in the libray. return which version jni it support.
    */
    jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
      void *venv;
      LOGI("dufresne----->JNI_OnLoad!");
      if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
        LOGE("dufresne--->ERROR: GetEnv failed");
        return -1;
      }   return JNI_VERSION_1_4;
    }
    复制代码

      OnLoadJava_com_lucyfyr_HelloWorld_printJNI

    函数里面做一些log输出 注意JNI中的log输出的不同。

      JNI_OnLoad函数JNI规范定义的,当共享库第一次被加载的时候会被回调,

    这个函数里面可以进行一些初始化工作,比如注册函数映射表,缓存一些变量等,

    最后返回当前环境所支持的JNI环境。本例只是简单的返回当前JNI环境。

     

    第四步:

    编译生成so

      编译com_lucyfyr_HelloWorld.cso库可以和app一起编译,也可以都单独编译。

    在当前目录下建立jni文件夹:HelloWorld/jni/

      下建立Android.mk ,并将com_lucyfyr_HelloWorld.c com_lucyfyr_HelloWorld.h 拷贝到进去

    编写编译生成so库的Android.mk文件:

    复制代码
    LOCAL_PATH:= $(call my-dir)
    # 一个完整模块编译
    include $(CLEAR_VARS)
    LOCAL_SRC_FILES:=com_lucyfyr_HelloWorld.c
    LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
    LOCAL_MODULE := libHelloWorldJni
    LOCAL_SHARED_LIBRARIES := libutils
    LOCAL_PRELINK_MODULE := false
    LOCAL_MODULE_TAGS :=optional
    include $(BUILD_SHARED_LIBRARY)
    复制代码

    系统变量解析:

      LOCAL_PATH - 编译时的目录   

      $(call 目录,目录….) 目录引入操作符     如该目录下有个文件夹名称 src,则可以这样写 $(call src),那么就会得到 src 目录的完整路径

      include $(CLEAR_VARS) -清除之前的一些系统变量  

      LOCAL_MODULE - 编译生成的目标对象  

      LOCAL_SRC_FILES - 编译的源文件  

      LOCAL_C_INCLUDES - 需要包含的头文件目录  

      LOCAL_SHARED_LIBRARIES - 链接时需要的外部库  

      LOCAL_PRELINK_MODULE - 是否需要prelink处理   i

      nclude$BUILD_SHARED_LIBRARY- 指明要编译成动态库

      android.mk编译模块添加具体方法参考:http://blog.csdn.net/yili_xie/article/details/4906865

    编译此模块:输入编译命令

      ./makeMtk mm packages/apps/HelloWorld/jni/

      上面是我的工程根目录编译命令。具体编译方式根据自己系统要求执行。

    编译输出: libHelloWorldJni.so (system/lib中视具体而定)

      此时库文件编译好了可以使用,如果在eclipse中模拟器上使用,需要将 libHelloWorldJni.so导入到system/lib 下,

      或者对应appdata/data/com.lucyfyr/lib/下;

    但是在导入system/lib 下的时候,却始终提示out of memory。于是网上说使用emulator去搞,

      但是此命令使用起来却有问题,始终没有解决。但是可以导入到data/data/com.lucyfyr/lib/下使用。但是我这里直接在真机上弄的。

    看一下HelloWorldAndroid.mk文件的配置

      其中存在:

        include $(LOCAL_PATH)/jni/Android.mk 表示编译库文件

        LOCAL_JNI_SHARED_LIBRARIES := libHelloWorldJni 表示app依赖库,打包的时候会一起打包。

    第五步:

    验证执行

      将编译好的apk安装到手机上

      使用adb push到手机上去需要自己去导入库文件libHelloWorldJni.sodata/data/com.lucyfyr/lib/

      使用adb install方式安装则会自动导入。

    启动HelloWorld :输入命令 adb logcat |grep dufresne

    输出log如下:

      I/HelloWorld(28500): dufresne Hello World From libhelloworld.so!

      I/HelloWorld(28500): dufresne--->I am HelloWorld Activity

      V/dufresne(28500): Hello World! I am Native interface

    符合调用打印顺序正确。

     

    以上通过一个简单的例子学习了Android如何编写编译C库文件,以及如何使用库文件。

    对于JNI的使用其中还涉及到其他方面的一些知识:C++接口如何调用,函数名注册表怎么回事,

    参数类型如何匹配,JNI如何调用Java中的方法java<--->JNI等。

    (用于参考)

    //---------------教程2------------------------------------------

    我使用GNUStep,下载地址http://www.gnustep.org/experience/Windows.html。

    下载安装后,验证是否成功。打开GNUstep->Shell,输入make -v 和 gcc -v命令,如图所示。

    下载NDK,地址http://developer.android.com/tools/sdk/ndk/index.html。下载完后解压即可。

    配置ndk环境变量,gnustep是模拟linux的环境的,打开gnustep的安装目录下的G:softinstallGNUstepGNUstepGNUstep.conf文件,添加以下内容:

    NDK=/g/softinstall/Android/android-ndk-r8b

    export=NDK

    说明如果不知道ndk目录在linux下应该是在哪里,你可以打开gnustep的命令窗口,输入mount,就可以找到对应的盘符。

    验证环境变量,如下图。

    以上就配置成功了。

    下载进入正题啦。Jni的开发步骤。

    打开eclipse,新建工程名为testJni。在activity中添加以下代码

    package com.xzw.jni;    
            
    import android.os.Bundle;    
    import android.app.Activity;    
    import android.view.Menu;    
    import android.view.MenuItem;    
    import android.widget.TextView;    
    import android.support.v4.app.NavUtils;    
    /**    
     *     
     * @author XuZhiwei (xuzhiwei@gmail.com)    
     * sina:http://weibo.com/xzw1989    
     *     
     * Create at 2012-8-30 上午10:49:45    
     */
    public class MainActivity extends Activity {     
        @Override
        public void onCreate(Bundle savedInstanceState) {    
            super.onCreate(savedInstanceState);     
        }    
            
        public native String hello();    
               
        static{    
            System.loadLibrary("testJni");    
        }    
                
    }

    编译后的文件在bin目录下,通过javah命令生成c/c++的文件头。如下图  

    会在项目目录下生成jni/com_xzw_jni_TestJni.h。

    头文件代码如下:

    /* DO NOT EDIT THIS FILE - it is machine generated */    
    #include <jni.h>    
    /* Header for class com_xzw_jni_TestJni */    
            
    #ifndef _Included_com_xzw_jni_TestJni    
    #define _Included_com_xzw_jni_TestJni    
    #ifdef __cplusplus    
    extern "C" {    
    #endif    
    /*    
     * Class:     com_xzw_jni_TestJni    
     * Method:    hello    
     * Signature: ()Ljava/lang/String;    
     */
    JNIEXPORT jstring JNICALL Java_com_xzw_jni_TestJni_hello    
      (JNIEnv *, jobject);    
            
    #ifdef __cplusplus    
    }    
    #endif    
    #endif

    根据头文件编写c代码

    #include <string.h>    
    #include <jni.h>    
             
    jstring    
    Java_com_xzw_jni_TestJni_hello    
      (JNIEnv* env, jobject thiz){    
          return (*env)->NewStringUTF(env, "哈哈完成自动化编译 !");    
    }

    接下来编写 Android.mk,该文件可以直接从NDK的samples下的hello-jni的jni文件下直接靠过来改改就可以了。也贴下代码哈。

    # Copyright (C) 2009 The Android Open Source Project    
    #    
    # Licensed under the Apache License, Version 2.0 (the "License");    
    # you may not use this file except in compliance with the License.    
    # You may obtain a copy of the License at    
    #    
    #      http://www.apache.org/licenses/LICENSE-2.0    
    #    
    # Unless required by applicable law or agreed to in writing, software    
    # distributed under the License is distributed on an "AS IS" BASIS,    
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.    
    # See the License for the specific language governing permissions and    
    # limitations under the License.    
    #    
    LOCAL_PATH := $(call my-dir)    
            
    include $(CLEAR_VARS)    
            
    LOCAL_MODULE    := testJni    
    LOCAL_SRC_FILES := testJni.c    
            
    include $(BUILD_SHARED_LIBRARY)

    其中你只需要该LOCAL_MODULE和LOCAL_SRC_FILES就可以了。

    说明:LOCAL_MODULE是描述模块的,用来给java调用的模块名,会生成对应的libtestJni.so

    LOCAL_SRC_FILES就是源文件啦,多个文件空格隔开即可。

    接下来,我们要开始编译生成so文件咯。

    打开gnustep的命令窗口,进入到项目底下,输入$NDK/ndk-build命令,即可自动生成libs/armeabi/libtestJni.so文件。

    接下来就是java调用来。直接上代码

    package com.xzw.jni;    
            
    import android.os.Bundle;    
    import android.app.Activity;    
    import android.view.Menu;    
    import android.view.MenuItem;    
    import android.widget.TextView;    
    import android.support.v4.app.NavUtils;    
    /**    
     *     
     * @author XuZhiwei (xuzhiwei@gmail.com)    
     * sina:http://weibo.com/xzw1989    
     *     
     * Create at 2012-8-30 上午10:49:45    
     */
    public class TestJni extends Activity {     
        @Override
        public void onCreate(Bundle savedInstanceState) {    
            super.onCreate(savedInstanceState);    
            TextView tv = new TextView(this);    
            tv.setText(hello());    
            setContentView(tv);    
        }    
            
        public native String hello();    
               
        static{    
            System.loadLibrary("testJni");    
        }    
                
    }

    运行结果如下

    本文出自 “Mr.Xu” 博客,请务必保留此出处http://xuzhiwei.blog.51cto.com/978424/976839

  • 相关阅读:
    Linux 重新挂载分区的方法
    SQL复习三(子查询)
    SQL复习四(完整性约束)
    SQL 复习二(数据查询语言)
    SQL复习一(基础知识)
    在windos 环境下安装
    tt程序分析(一)
    单例模式Singleton
    用命令行使用soot反编译生成jimple
    在win10环境下安装eclipse mars版本
  • 原文地址:https://www.cnblogs.com/navy-wang/p/3272626.html
Copyright © 2020-2023  润新知