zoukankan      html  css  js  c++  java
  • Android-eclipse-NDK&JNI

    Android-eclipse-NDK&JNI

    1. NDK简介(★★)

    1.1 NDK产生的背景

         Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三方应用都必须使用Java语言。但这并不等同于“第三方应用只能使用Java”。在Android SDK首次发布时,Google就宣称其虚拟机Dalvik支持JNI编程方式,也就是第三方应用完全可以通过JNI调用自己的C动态库,即在Android平台上,“Java+C”的编程方式是一直都可以实现的。

      不过,Google也表示,使用原生SDK编程相比Dalvik虚拟机也有一些劣势,Android SDK文档里,找不到任何JNI方面的帮助。即使第三方应用开发者使用JNI完成了自己的C动态链接库(so)开发,但是so如何和应用程序一起打包成apk并发布?这里面也存在技术障碍。比如程序更加复杂,兼容性难以保障,无法访问Framework API,Debug难度更大等。开发者需要自行斟酌使用。

      于是NDK就应运而生了。NDK全称是Native Development Kit。

      NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。NDK将是Android平台支持C开发的开端。

    1.2 为什么使用NDK

     1.代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反编译难度较大。

       2.可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。

       3.提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。

       4.便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

    1.3 NDK简介

      1.NDK是一系列工具的集合

    NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

     2.NDK提供了一份稳定、功能有限的API头文件声明

    Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出,这些API支持的功能非常有限,包含有:C标准库(libc)、标准数学库(libm)、压缩库(libz)、Log库(liblog)。

    1.4 NDK的安装

     1.NDK的下载

    NDK的官方下载地址http://developer.android.com/tools/sdk/ndk/index.html,由于官方网址在国外,国内访问不了,必须得FQ。因此我提供了下载好的NDK工具放在百度网盘上供大家下载。http://pan.baidu.com/s/1jGpCDKi

     2.将NDK解压到一个不包含空格和中文的目录下

    本人将NDK解压在D:software dkr9android-ndk-r9b中。

    1.5 NDK目录结构说明

     build:该目录存放的使用NDK的mk脚本,mk脚本指定了编译参数

     docs:该目录存放的是NDK的使用帮助文档

     platforms:这里面存放的是与各个Android版本相关的平台(x86,arm,mips)相关C语言库和头文件

     prebuilt:预编译工作目录

     samples:存放的是演示程序

     sources:存放的是NDK工具链的C语言源码

     tests:测试相关的文件

     toolchains:工具链,存放了三种架构的静态库等文件

     ndk-build.cmd:Window平台使用NDK的命令

     ndk-build:Linux平台使用NDK的命令

    2. JNI入门(★★★)

    下面通过一个简单的JNI案例来演示如何使用JNI编程。

    1)创建一个新的Android工程《JNI入门》,工程的最终目录结构如下图所示。

    jni-|

         |-----Android.mk

         |-----Application.mk

         |-----jni.h

         |-----helloc.c

    2)在MainActivity.java类中定义一个native方法

    //定义一个native方法,意思是该方法的具体实现交给C语言实现
        public native String helloC();

    3)在工程跟目录下创建一个文件夹jni,该目录名称是约定(约定优于配置)好的,不能是其他名字。

    4)在jni目录下创建hello.c源文件,文件名可以按照见名知意的规则来创建。hello.c代码清单如下。

    #include<stdio.h>//引入头文件
    //引入jni.h jni.h文件里面定义了jni的规范,jni.h在ndk的目录中找到,然后放到当前工程中的jni目录下即可
    #include<jni.h>
    //定义在MainActivity.java类中的helloC对应的C语言函数
    jstring Java_com_itheima_jnihello_MainActivity_helloC(JNIEnv* env, jobject obj) {
        char* str = "hello from C";
    //调用jni.h中定义的创建字符串函数
        jstring string = (*(*env)).NewStringUTF(env, str);
        return string;

    主:上面的代码虽然简单但是关于jni.h头文件和方法名必须单独说明。

       a)jni.h头文件位于NDK安装目录下/platforms/android-*/(某平台)/usr/include目录中

      上面的某平台指CPU的三种架构如下图。我们选择任意一架构皆可,但是对于手机来说CPU用arm架构的最多,x86次之,mips架构最少。

       b)JNI中C源文件方法名的命名规则

      这里的命名规则指用于跟java文件中native方法对应的C语言方法,而C语言中的其他方法命名只要符合C语言规则就行。jstring Java_com_itheima_jnihello_MainActivity_helloC(JNIEnv* env, jobject obj) 中,jstring是方法返回值类型,

      我们可以把jstring看成是java中String跟C语言中char*类型的一个中间转换类型,java跟C语言的数据类型是不一样的,他们之间要想互相调用就必须通过一种中介来实现,这个中介就是在jni.h头文件中定义的。关于更多的转换类型,在本文档的第2章会有更详细的说明。

      方法名第一个字母必须是Java,首单词大写,然后下划线_,然后是将该方法所在的包、类、方法用“_”连接起来,比如com.itheima.jnihello.MainActivity类中的helloC方法,转变成C语言中的方法名为Java_com_itheima_jnihello_MainActivity_helloC

      方法的形参有两个是必须的也就是不管java中的方法是否有形参,但是C语言中对应的方法必须有JNIEnv* env,和jobject obj,如果java方法中还用其他形参,那么在C语言中严格按照顺序排在jobject obj参数的后面即可。

      上面的env代表指向JVM的指针,obj是调用该方法的java对象。

    5)使用NDK工具将hello.c编译成hello.so文件

       为了方便直接在控制台中使用NDK工具的ndk-build.cmd命令,我们首先将ndk-build.cmd所在的目录设置成系统环境变量。环境变量配置好以后,在命令行中输入ndk-build.cmd:

      将当前目录切换到hello.c所在的工程目录

      这时候如果直接输入ndk-build.cmd那么会出现如下异常:

      出现这种错误时因为我们并没有告诉ndk我们要将那个C语言源代码编译成目标文件。为了告诉ndk要将那个C源文件编译成目标文件,我们需要在工程中的jni目录中添加Android.mk配置文件。

    6)在当前工程的jni目录下添加Android.mk配置文件,该配置文件可以从ndk安装目录的实例代码中拷贝,然后修改。

      Android.mk文件清单如下,我们只需要修改LOCAL_MODULE和LOCAL_SRC_FILES两个参数即可。LOCAL_MODULE参数是指定编译后的目标文件的名称,其实编译好的目标文件名为libhello.so,LOCAL_SRC_FILES指定了要编译的源文件。 

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := hello
    LOCAL_SRC_FILES := hello.c
    include $(BUILD_SHARED_LIBRARY)

      在cmd中,将当前目录切换到hello.c所在目录,然后重新执行ndk-build.cmd命令,这次成功编译.

      查看项目目录结构,发现在libs目录中多了两个文件夹armeabi和x86,这两个文件夹下分别包含了一个libhello.so动态链接库。这也代表着当前工程中的动态库支持arm架构和x86架构的cpu。

      :可能你的并没有同时生成这两个文件,是因为我的工程中引入了Application.mk文件,因此你需要引入该文件。

      Application.mk文件清单:

    # Build both ARMv5TE and ARMv7-A machine code.
    APP_ABI := armeabi x86

      该清单其实只有一行内容,第一行是注释。APP_ABI参数指定要生成的目标文件支持的平台都有哪些,默认是armeabi如果想支持多个平台只需要空一格然后写出其他平台名字即可。

      在MainActivity.java中调用C语言

    public class MainActivity extends Activity {
        //加载libhello.so动态库,但是我们加载的时候必须去掉lib和后缀
        static{
            System.loadLibrary("hello");
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        //定义一个native方法,意思是该方法的具体实现交给C语言实现
        public native String helloC();
        //点击按钮调用C语言方法
        public void click(View view){
            Toast.makeText(this, helloC(), 1).show();    
        }
    }

      运行上面工程,即可完成本地方法的调用;

      注:如果我们编译的arm平台的so文件,但是却部署到了x86平台的模拟器上,那么运行的时候会报找不到libhello.so的异常。

    3. JNI规范(★)

    3.1 JNI数据类型和数据结构

     1)基本数据类型

    JNI基本类型和本地等效类型的对应表格如下:

    Java 类型

    本地 C 类型

    实际表示的 C 类型

    Win32

    说明

    boolean

    jboolean

    unsigned char

    无符号,8 位

    byte

    jbyte

    signed char

    有符号,8 位

    char

    jchar

    unsigned short

    无符号,16 位

    short

    jshort

    short

    有符号,16 位

    int

    jint

    long

    有符号,32 位

    long

    jlong

    __int64

    有符号,64 位

    float

    jfloat

    float

    32 位

    double

    jdouble

    double

    64 位

    void

    void

    N/A

    N/A

     2)引用类型,JNI还包含了很对对应于不同Java对象的引用类型。

    3.2 JNI接口函数命名方式

     1)类型签名

    Java虚拟机的类型签名如下:

    类型签名

    Java类型

    Z

    boolean

    B

    byte

    C

    char

    S

    short

    I

    int

    J

    long

    F

    float

    D

    double

    Lfully-qulitied-class;

    全限定类

    [type

    type[] 数组

    (argtypes)rettype

    方法类型

    例如,Java方法int feet(int n, String s,int [] arr)的类型签名如下:

    (ILJava/lang/String;[I)I

    圆括号里面为参数,I表示第一个参数int型,LJava/lang/String;表示第二个参数为全限定Java.lang.String类型,[I表示第三个参数为int型的数组,圆括号后面为返回值类型,I表示返回值为int型。

     2)一般函数的JNI接口函数命名方式

    一般JNI接口函数命名如下:

    Java_包名_类名_方法名。

    例如:某工程下com/itheima包下MainActivity类的int getIntFromC()方法的C语言实现函数命名如下:

    jint Java_com_itheima_MainActivity_getIntFromC(JNIEnv* env,jobject obj)

    其中,包名所包含的“/”应全部以下划线替代,其本地实现的参数和返回值也应转换为JNI类型。

     3) 重载函数的JNI接口函数命名方式

    重载函数的JNI实现在一般函数的JNI实现之外,还应添加上类型签名以作为同名函数之间的区别,其接口函数命名如下:Java_包名_类名_方法名_参数签名。

    例如:某工程下com/itheima包下MainActivity类的int getIntFromC(int n, String s,int [] arr)方法的C语言实现函数命名如下:

    jint Java_com_itheima_MainActivity_getIntFromC_ILJava_lang_String_2_3I

    (JNIEnv* env, jobject obj, jint n, jstring s, jintarray arr)。

    JNI在函数命名时采用名字扰乱方案,以保证所有的Unicode字符都能

    转换为有效的C函数名,所有的“/”,无论是包名中的还是全限定类名中的,均使用“_”代替,用_0,„,_9来代替转义字符,如下:

    转义字符序列

    表示

    _0XXXX

    Unicode字符XXXX

    _1

    字符“_”

    _2

    签名中的字符

    _3

    签名中的字符“[”

    3.3 JNI函数与API

    在本文档中我们所主要需要关心的是C/C++数据类型与JNI本地类型之间的转化过程,这个过程某些数据的转换需要使用JNIEnv对象的一系列方法来完成。

     1)jstring转换为C风格字符串

    char* test = (char*)(*env)->GetStringUTFChars(env,jstring,NULL);

    使用完毕后,应调用:

    (*env)->ReleaseStringUTFChars(env, jstring, test);

    释放资源。

     2)C风格字符串转换为jstring

    char charStr[50];

    jstring jstr;

    jstr = env -> NewStringUTF(charStr);

     3)C语言中获取的一段char*的buffer传递给Java

    在jni中new一个byte数组,然后使用

    (*env)->SetByteArrayRegion(env, bytearray, 0, len, buffer) 操作将buffer拷贝到数组中。

    这种方式主要是针对buffer中存在“”的情况,如果以C风格字符串的方式读入,就会损失“”之后的字符。

     4)数组操作

    JNI函数

    功能

    GetArrayLength

    返回数组中的元素数

    NewObjectArray

    创建一个指定长度的原始数据类型数组

    GetObjectArrayElement

    返回Object数组的元素

    SetObjectArrayElement

    设置Object数组的元素

    GetObjectArrayRegion

    将原始数据类型数组中的内容拷贝到预先分配好的内存缓存中

    SetObjectArrayRegion

    设置缓存中数组的值

    ReleaseObjectArrayRegion

    释放GetObjectArrayRegion分配的内存

    :对int,char等基本数据类型的数组操作,将相关Object名称替换为对应基本数据类型名称即为相关函数。

    数组操作的方法选择基于使用者的需求而定,如果使用者需要在内存中拷贝数组并对其进行操作那么一般使用GetObjectArrayRegion和SetObjectArrayRegion函数,否则一般使用SetObjectArrayElement和GetObjectArrayElement函数。

     

    4. 案例-银行登录系统(★★)

      需求:假设银行的登陆模块是用C语言来编写的,但是我们的Android应用想登陆银行系统,那么就需要通过JNI来实现了。

      创建一个新Android工程《建行客户端》,工程目录结构如下图。

      在工程中创建jni文件夹,然后将jni.h、Android.mk、Application.mk从JNI入门工程拷贝进去。

      在jni目录下创建login.c文件,在该文件中实现登录业务逻辑。

    #include<stdio.h>
    //系统在查找投文件的时候""中的文件会去本地搜索,<>中的文件会去系统目录中搜索,因为jni.h在当前目录中所以用""将jni.h引起来,可以加快搜索速度
    #include"jni.h"
    int login(int card,int pwd){
        //真实的业务逻辑要复杂的多,这里只简单的返回银行卡号和密码号
        return card+pwd;
    }

      

    jint Java_com_itheima_ccb_MainActivity_login(JNIEnv* env,jobject obj,jint card,jint pwd){
        return login(card,pwd);
    }

      是用ndk工具,将login.c编译成动态库文件。编译前修改Android.mk文件的LOCAL_SRC_FILES := login.c

      编写在MainActivity.java类

    public class MainActivity extends Activity {
        static{
            System.loadLibrary("login-jni");
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
        public native int login(int card,int pwd);
        public void login(View view){
            EditText et_card = (EditText)     findViewById(R.id.et_card);
            EditText et_pwd = (EditText) findViewById(R.id.et_pwd);
            int card = Integer.valueOf(et_card.getText().toString());
            int pwd = Integer.valueOf(et_pwd.getText().toString());
            int result = login(card, pwd);
            Toast.makeText(this, ""+result, 1).show();
        }
    }            

      布局文件比较简单,这里就不再给出。

      运行上面的代码。

       

    5. CDT插件的安装(★)

    5.1 CDT简介

    CDT 项目致力于为 Eclipse 平台提供功能完全的 C/C++ 集成开发环境(Integrated Development Environment,IDE)。CDT 是完全用 Java 实现的开放源码项目(根据 Common Public License 特许的),它作为 Eclipse SDK 平台的一组插件。这些插件将 C/C++ 透视图添加到 Eclipse 工作台(Workbench)中,现在后者可以用许多视图和向导以及高级编辑和调试支持来支持 C/C++ 开发。

    5.2 CDT的下载

    CDT插件可以通过eclipse的在线安装,但是受限于跨国家网络访问,一般不是很好用。因此这里我主要给大家说的是如何离线安装。

    下载CDT离线安装包。针对不同版本eclipse的cdt安装包如下,大家可以从我的百度网盘上直接下载。考虑到我们大部分都用的最新的ADT因此建议选择8.5.0版本的CDT。

    cdt-8.5.0-for-Eclipse-Luna http://pan.baidu.com/s/1c0m1k0w

    cdt-8.3.0-for-Eclipse-Kepler http://pan.baidu.com/s/1kT21QOf

    cdt-8.1.2-for-Eclipse-Juno http://pan.baidu.com/s/1qWAzjBI

    选择eclipse的Help->Install New Software...,弹出对话框

    点击Add按钮,在弹出的对话框中输入Name。在Location栏如果输入一个http地址是让eclipse自动从网络上下载安装,这里我们点击Archive按钮找到我们事先下载好的离线安装包。然后点击OK。

    将CDT所有的插件勾选上,同时将最下面的联网检查更新去掉勾选,然后点击Next,直到Finish。

    安装好以后在File->New->Other中会有C/C++选项

    在Open Perspective中也多了C/C++视图可选项

    安装好以后,我们就可以在eclipse中开发我们的C/C++工程了。不过对我们Android开发人员来说用到的机会不是很多。就算是开发C/C++工程,大多数程序员也不会选择在eclipse平台上进行开发。Eclipse更多的是专注于Java语言项目的开发,比如JavaEE、Android。

  • 相关阅读:
    浅析 c# Queue
    c# Stack操作类
    delegate,event, lambda,Func,Action以及Predicate
    推翻MMSOA与WEBService,使用MEMBRANE soap Monitor检查 wsdl文件。
    JS打印表格(HTML定义格式)
    坑爹的META 刷新页面 框架页面中TOP页面提示刷新 meta元素设置而不基于 JS 的坑爹写法。
    Silverlight载入动画(简易)
    WPF先开了再说
    跨域WEB Service 调用(摘自ASP.NET高级编程第三版)
    安装程序找不到Office.zhcn\OfficeMUI问题
  • 原文地址:https://www.cnblogs.com/syjhsgcc/p/4756569.html
Copyright © 2011-2022 走看看