• Android NDK 开发(二) -- 从Hlello World学起【转】


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

            上篇文章讲述了Android NDK开发的一些基本概念,以及NDK的环境搭建,相信看过的朋友NDK开发环境搭建应该是没有问题了,还没有搭建或者不知道怎么搭建的朋友请点击这里。那么这篇文章,我们跟刚学Java编程语言一样,从世界知名程序“Hello World!”开始,开发出我们的第一个NDK程序。

    NDK目录简单介绍  

            在进行NDK开发之前,我们有必须熟悉一下NDK目录下包含哪些东西,以及这些东西对开发来说有什么作用?那么现在打开NDK的解压目录,查看一下解压目录下的文件:

    1,samples目录。这个目录包含了Google为NDK开发撰写的一些小例子,包括本地JNI开发,图片处理,多个库文件开发等等,这些例子虽小但面面俱到,能看懂samples目录下的小例子程序,那么对于NDK开发来说,就很好应付了。

    2,docs目录。这个目录下存放的都是Google给开发者提供的文档,指导开发者怎样在Android环境下进行NDK开发,这个非常重要。

    3,sources目录。由于Android是开源操作系统,作为Android的一部分的NDK,同样也是开源的,这个目录下存放的是NDK源码。

    4,platforms目录。里面存放的是当前ndk版本所支持的所有android平台的版本,做NDK开发的C代码也是可以指定由某个特定版本平台下编译,该platforms目录下存放的是不同版本所包含的C的库文件和头文件,不同版本有些微小的变化。

    5,prebuilt目录。这是提供给在Windows下开发ndk程序的一些工具集。

    6,build目录。里面存放大量的Linux编程脚本和Windows下的批处理文件,用来完成ndk开发中的交叉编译。  

    具体开发

    1,NDK开发步骤

            首先,我先列出NDK开发的简单步骤,然后再以此为大纲,用一个Hello World的实例讲述一下NDK开发:

    (1)创建一个android工程

    (2)JAVA代码中写声明native 方法 public native String helloFromJNI();

    (3)创建jni目录,编写c代码,方法名字要对应在c代码中导入jni.h头文件

    (4)编写Android.mk文件

    (5)Ndk编译生成动态库

    (6)Java代码load 动态库.调用native代码

    2,NDK开发具体实践

         下面就按照上述的步骤建立一个HelloWorld小案例来一步一步实现NDK开发

    1,创建一个Android工程,并且在Java代码中声明一个native方法:
    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class MainActivity extends Activity {  
    2.   
    3.     public native String javaFromJNI();  
    4.   
    5.     @Override  
    6.     protected void onCreate(Bundle savedInstanceState) {  
    7.         super.onCreate(savedInstanceState);  
    8.         setContentView(R.layout.activity_main);  
    9.         findViewById(R.id.button).setOnClickListener(new OnClickListener() {  
    10.   
    11.             @Override  
    12.             public void onClick(View v) {  
    13.                 Toast.makeText(MainActivity.this, javaFromJNI(),  
    14.                         Toast.LENGTH_SHORT).show();  
    15.             }  
    16.         });  
    17.     }  
    18.   
    19. }  
    2,创建jni目录,编写c代码,方法名字要对应在c代码中导入jni.h头文件
    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #include<stdio.h>  
    2. #include<jni.h>  
    3.   
    4. jstring Java_com_example_ndk_MainActivity_javaFromJNI(JNIEnv* env, jobject obj) {  
    5.     return (*(*env)).NewStringUTF(env, "hello jni!");  
    6. }  
             关于这个本地的C代码怎么写,还是需要一些C语言的基础的。没有也可以,我们可以参考一下ndk解压目录下的platformsandroid-19arch-armusrinclude目录下的jni.h文件,也就是本地C代码需要include的那个,用记事本打开看看里面的内容。先来说一下JNI代码的简单格式:

    方法签名规则:返回值类型 Java_包名_类名_native方法名(JNIEnv* env, jobject obj)
    返回值类型就是JNI头文件中事先定义好的自定义C类型,直接拿来使用即可:

    其后的参数列表是固定的(JNIEnv* env, jobject obj)形式,关于JNIEnv请在下面的定义:

    可以看到啊,这个JNIEnv原来是一个名作JNINativeInterface的结构体,这个结构体定义了很多的数据类型,那么我们返回字符串的类型或者方法是哪一个呢?

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. jstring     (*NewStringUTF)(JNIEnv*, const char*);  
    以上就是我在JNINativeInterface结构体找到的返回字符串的方法,参数为JNINativeInterface指针和一个字符串,正如上面JNI代码使用的那样调用即可。

    好,以上我们创建好了JNI本地代码,我们编译一下试试吧!打开cygwin,切换到工程目录下,执行ndk-build命令:

    仔细看一下报错的日志,告诉我们/jni目录下缺少了一个叫Android.mk的文件,所以导致无法编译。

    3,编写Android.mk文件

    这个Android.mk文件怎么写呢?这时候我们得打开NDK的文档来看看了,位置E:/NDK/android-ndk-r10d/docs/Start_Here.html,找到

    好,我们就先在jni目录下创建一个Android.mk的文件,将上面的这段话复制粘贴进去,将LOCAL_MODULE和LOCAL_SRC_FILES修改成我们自己的名称:

    [javascript] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. LOCAL_PATH := $(call my-dir)  
    2.   
    3. include $(CLEAR_VARS)  
    4.   
    5. LOCAL_MODULE    := Hello  
    6. LOCAL_SRC_FILES := Hello.c  
    7.   
    8. include $(BUILD_SHARED_LIBRARY)  
    4,ndk编译生成动态库
    然后在cygwin中编译一下:

    可以看到编译通过了,下面刷新一下工程,就可以看到工程libs目录下多了个libHello.so的文件,这个就是Android认识的动态库了。

    5,Java代码load 动态库.调用native代码

    编译出来这个libHello.so文件后,就需要在Java代码中加载这个.so的库文件了,代码很简单,然后Toast一下看看效果:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public class MainActivity extends Activity {  
    2.   
    3.     static {  
    4.         System.loadLibrary("Hello");  
    5.     }  
    6.     public native String javaFromJNI();  
    7.   
    8.     @Override  
    9.     protected void onCreate(Bundle savedInstanceState) {  
    10.         super.onCreate(savedInstanceState);  
    11.         setContentView(R.layout.activity_main);  
    12.         findViewById(R.id.button).setOnClickListener(new OnClickListener() {  
    13.   
    14.             @Override  
    15.             public void onClick(View v) {  
    16.                 Toast.makeText(MainActivity.this, javaFromJNI(),  
    17.                         Toast.LENGTH_SHORT).show();  
    18.             }  
    19.         });  
    20.     }  
    21.   
    22. }  
    System.loadLibrary(String 文件名);是用来加载动态库的方法,其中参数类型是字符串,参数是Android.mk文件中LOCAL_MODULE定义的名称。

    运行效果上图所示,到这里,一个简单的ndk开发的Hello World就完成了。友情提示:本示例程序不支持x86架构的cpu,测试请开启arm模拟器!

    使用javah命令帮助生成方法签名

          已知native代码中的方法签名规则是这样的:返回值类型 Java_包名_类名_native方法名(JNIEnv* env, jobject obj);但是有如以下特殊情况,Java的方法名中是可以带下划线“_”的,例如如下这样的定义native方法:

    [java] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. public native String java_From_JNI();  
    假如我们按照上述的规则,在C代码中套用,定义出这样的C函数:
    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. jstring Java_com_example_ndk_MainActivity_java_From_JNI(JNIEnv* env, jobject obj)  
            这样定义的方法签名显然是不合适的,这样会造成编译环境误以为MainActivity类下有个java内部类,其中又包含From内部类,From内部类下有个叫JNI的方法,实际上并没有这个方法,所以编译的时候肯定是会报错的。那么这个例子是个个例而已,其实按照上述的方法签名规则来看,C语言中定义native方法比较麻烦,很容易让人手敲失误,导致程序运行不了,其实我们可以用JDK提供好的javah工具来自动为我们生成方法签名,步骤如下:

    1,在windows命令模式中,切换到工程包下class字节码文件所在的目录下,本示例的路径是D:workspace-mimeNDKHelloWorldinclasses

    先执行“ cd /d D:workspace-mimeNDKHelloWorldinclasses ”命令进入到class字节码文件的包名根目录下

    然后执行“ javah com.example.ndk.MainActivity ”

    会得到如下图的一个.h文件:

    用记事本打开这个文件

    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. /* DO NOT EDIT THIS FILE - it is machine generated */  
    2. #include <jni.h>  
    3. /* Header for class com_example_ndk_MainActivity */  
    4.   
    5. #ifndef _Included_com_example_ndk_MainActivity  
    6. #define _Included_com_example_ndk_MainActivity  
    7. #ifdef __cplusplus  
    8. extern "C" {  
    9. #endif  
    10. /* 
    11.  * Class:     com_example_ndk_MainActivity 
    12.  * Method:    javaFromJNI 
    13.  * Signature: ()Ljava/lang/String; 
    14.  */  
    15. JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_javaFromJNI  
    16.   (JNIEnv *, jobject);  
    17.   
    18. /* 
    19.  * Class:     com_example_ndk_MainActivity 
    20.  * Method:    java_From_JNI 
    21.  * Signature: ()Ljava/lang/String; 
    22.  */  
    23. JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_java_1From_1JNI  
    24.   (JNIEnv *, jobject);  
    25.   
    26. #ifdef __cplusplus  
    27. }  
    28. #endif  
    29. #endif  
            上面就是我们需要的方法签名了,这就是javah工具自动为我们生成的native头文件,下面我们需要引用这个头文件到工程中去。将这个头文件直接剪切,粘贴到工程的jni的目录下,然后重写一个Hello.c的C代码,将#include"com_example_ndk_MainActivity.h"放在代码的头部,表示引入刚刚生成好的头文件,注:在C语言中#include<xx.h>表示引用C语言环境(编译)自带的头文件,#include"xx.h"表示引用当前自定义的头文件。引用好头文件之后,将头文件中的两个方法签名拷贝进来,实现逻辑:
    [cpp] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #include<stdio.h>  
    2. #include<jni.h>  
    3. #include"com_example_ndk_MainActivity.h"  
    4.   
    5. JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_javaFromJNI(  
    6.         JNIEnv* env, jobject obj) {  
    7.     return (*env)->NewStringUTF(env, "hello jni!");  
    8. }  
    9.   
    10. /* 
    11.  * Class:     com_example_ndk_MainActivity 
    12.  * Method:    java_From_JNI 
    13.  * Signature: ()Ljava/lang/String; 
    14.  */  
    15. JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_java_1From_1JNI(  
    16.         JNIEnv* env, jobject obj) {  
    17.     return (*env)->NewStringUTF(env, "hello_jni__");  
    18. }  
    重新编译:

    重新编译之后,我们clean一下工程,然后refresh一下工程,在libs目录下就可以找到我们重新编译的新的libHello.so文件,最后在Java代码中实现操作(省略)。

    Android.mk简介

            一个Android.mk file用来向编译系统描述你的源代码。具体来说:该文件是GNU Makefile的一小部分,会被编译系统解析一次或多次。你可以在每一个Android.mk file中定义一个或多个模块,你也可以在几个模块中使用同一个源代码文件。编译系统为你处理许多细节问题。例如,你不需要在你的Android.mk中列出头文件和依赖文件。NDK编译系统将会为你自动处理这些问题。这也意味着,在升级NDK后,你应该得到新的toolchain/platform支持,而且不需要改变你的Android.mk文件。

    [html] view plain copy
     
     print?在CODE上查看代码片派生到我的代码片
    1. #交叉编译器在编译C/C++代码所依赖的配置文件,linux下makefile的语法子集  
    2.       
    3.     #获取当前Android.mk的路径  
    4.     LOCAL_PATH := $(call my-dir)  
    5.     #变量的初始化操作 特点:不会重新初始化LOCAL_PATH的变量  
    6.     include $(CLEAR_VARS)  
    7.     #指定编译后生成的.so文件名,makefile语法约定文件名加前缀lib和后缀.so  
    8.     LOCAL_MODULE    := Hello  
    9.     #指定native代码文件  
    10.     LOCAL_SRC_FILES := Hello.c  
    11.     #指定native代码编译成动态库.so或者指定编译成静态库.a  
    12.     include $(BUILD_SHARED_LIBRARY)  
    参数介绍:

    LOCAL_MODULE: 就是你要生成的库的名字,这个名字要是唯一的.不能有空格.
                                          编译后系统会自动在前面加上lib的头, 比如说我们的Hello 就编译成了libHello.so
                                          还有个特点就是如果你起名叫libHello 编译后ndk就不会给你的module名字前加上lib了
                                          但是你最后调用的时候 还是调用Hello这个库

    LOCAL_SRC_FILES:这个是指定你要编译哪些文件
                                             不需要指定头文件 ,引用哪些依赖, 因为编译器会自动找到这些依赖 自动编译

    include $(BUILD_SHARED_LIBRARY)  BUILD_STATIC_LIBRARY
                                             .so 编译后生成的库的类型,如果是静态库.a 配置include $(BUILD_STATIC_LIBRARY)

    LOCAL_CPP_EXTENSION := cc :指定c++文件的扩展名
    LOCAL_MODULE    := ndkfoo 
    LOCAL_SRC_FILES := ndkfoo.cc

    LOCAL_LDLIBS += -llog -lvmsagent -lmpnet -lmpxml -lH264Android
                                             指定需要加载一些别的什么库. 

    另:关于Android.mk文件的介绍和用法可以参考Google NDK提供的文档,位置是ndk解压目录下的docs目录下,Programmers_Guide/html/md_3__key__topics__building__chapter_1-section_8__android_8mk.html。

  • 相关阅读:
    request内置对象
    JSP页面、包含
    HTTP协议
    html简介
    数据访问层工具类
    数据运算
    可变于不可变对象分类
    有序 无序 的区别
    字符串方法
    day01_final
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6070442.html
Copyright © 2020-2023  润新知