zoukankan      html  css  js  c++  java
  • 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。

  • 相关阅读:
    阿里P8架构师谈:阿里双11秒杀系统如何设计?
    秒杀系统设计的知识点
    秒杀系统架构优化思路
    秒杀系统解决方案
    Entity Framework Code First (七)空间数据类型 Spatial Data Types
    Entity Framework Code First (六)存储过程
    Entity Framework Code First (五)Fluent API
    Entity Framework Code First (四)Fluent API
    Entity Framework Code First (三)Data Annotations
    Entity Framework Code First (二)Custom Conventions
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6070442.html
Copyright © 2011-2022 走看看