Android的NDK开发(1):不一样的HelloWorld

发表于2017-09-14
评论0 1.5k浏览

       通过之前的两篇文章,我们已经对JNI技术有了一个了解-能够让java与其他的语言进行交互。

       android的应用同样也是用java开发,所以也可以使用JNI技术来进行其他语言的调用,比如C\C ,大名顶顶的cocos2d-x就是用C来做开发语言的。


      为什么要在Android中使用C\C ?

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

2. 在NDK中调用第三方C/C 库,因为大部分的开源库都是用C/C 代码编写的。

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


       Android的NDK提供了一些交叉编译工具链和Android自带的库,这些Android的库可以让开发者在编写本地语言的程序时调用。而NDK提供的交叉编译工具链就对已经编写好的C&C 代码进行编译,生成库。

      当然了,也可以自己搭建交叉编译环境,而不用NDK的工具和库。然后生成库,只要规范操作,一样可以生成能让JAVA层成功调用的库文件的,不过这个属于高端玩家了,NDK还是比较容易上手一些。

     注:交叉编译器(英语:Cross compiler)是指一个在某个系统平台下可以产生另一个系统平台可执行文件的编译器。交叉编译器在目标系统平台(开发出来的应用程序所运行的平台)难以或不容易编译时非常有用。

     下面用两种方式在NDK下实现HelloWorld。(确保已配置好NDK


1、先编写C库,再创建项目


     1)创建库

新建一个目录,命名为HelloWorld,然后在里面新建一个名为jni的目录(名称一定要是jni,因为ndk-build的时候会在HelloWorld目录下寻找jni的目录,然后进行build),在jni目录下新建如下文件HelloWorld.c和Android.mk。

     

  1. /* 
  2. FileName:HelloWorld.c 
  3. Description:realise of HelloWorld 
  4. */  
  5. #include     
  6. #include   
  7. jstring  Java_com_empty_helloworld_HelloWorldActivity_helloWorldFromJNI( JNIEnv* env, jobject thiz )    
  8. {    
  9.     return (*env)->NewStringUTF(env, "HelloWorld! I am from JNI !");    
  10. }  


注意函数的命名规则是:

Java Android工程的包名 Android工程的Activity名 方法名,点号用下划线表示,这个写法很严格。 
包名:com_empty_helloworld 
Activity名:HelloWorldActivity 
方法名:helloWorldFromJNI

注:.mk文件中不要有空格

  1. #FileName:Android.mk  
  2. #Description:makefile of Helloworld  
  3. LOCAL_PATH := $(call my-dir)  
  4.   
  5. include $(CLEAR_VARS)  
  6.   
  7. LOCAL_MODULE    := HelloWorld   
  8. LOCAL_SRC_FILES := HelloWorld.c  
  9.     
  10. include $(BUILD_SHARED_LIBRARY)  

关于.mk

LOCAL_PATH := $(call my-dir)

一个Android.mk 文件首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。

include $( CLEAR_VARS)

CLEAR_VARS由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),
除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。

LOCAL_MODULE := HelloWorld

编译的目标对象,LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。

注意:编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'HelloWorld'的共享库模块,将会生成'libHelloWorld.so'文件。

重要注意事项:

如果你把库命名为‘libhello-jni’,编译系统将不会添加任何的lib前缀,也会生成 'HelloWorld.so',这是为了支持来源于Android平台的源代码的Android.mk文件,如果你确实需要这么做的话。

LOCAL_SRC_FILES :=HelloWorld.c

LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C 源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。

注意,默认的C 源码文件的扩展名是’.cpp’. 指定一个不同的扩展名也是可能的,只要定义LOCAL_DEFAULT_CPP_EXTENSION变量,不要忘记开始的小圆点(也就是’.cxx’,而不是’cxx’)

include $(BUILD_SHARED_LIBRARY)

BUILD_SHARED_LIBRARY表示编译生成共享库,是编译系统提供的变量,指向一个GNU Makefile脚本,负责收集自从上次调用'include $(CLEAR_VARS)'以来,定义在LOCAL_XXX变量中的所有信息,并且决定编译什么,如何正确地去做。还有 BUILD_STATIC_LIBRARY变量表示生成静态库:lib$(LOCAL_MODULE).a, BUILD_EXECUTABLE 表示生成可执行文件。


    在终端进入到HelloWorld目录,运行命令:

[plain] view plain copy
  1. $NDK_ROOT/ndk-build  

编译结果如下


编译成功后,会在在目录生成libs和obj两个文件夹,libs里面有刚刚编译成的libHelloWorld.so库则相应的可运行在arm平台上的HelloWorld库编译成功了.


2)创建Android项目

打开Eclipse创建一个Android项目,将刚才生成的armeabi文件夹拷贝到项目下libs文件夹内。

项目结构如下:


主Activity代码如下:

[java] view plain copy
  1. package com.empty.helloworld;  
  2.   
  3. import com.example.ndktest1.R;  
  4.   
  5. import android.os.Bundle;  
  6. import android.app.Activity;  
  7. import android.view.Menu;  
  8. import android.widget.TextView;  
  9.   
  10. public class HelloWorldActivity extends Activity {  
  11.     // statement of native function.Means it has been realized by native layer.  
  12.     public native String helloWorldFromJNI();  
  13.         
  14.     static {    
  15.         // Load library,No Prefix and Suffix.  
  16.         System.loadLibrary("HelloWorld");  
  17.     }  
  18.     @Override  
  19.     protected void onCreate(Bundle savedInstanceState) {  
  20.         super.onCreate(savedInstanceState);  
  21.         setContentView(R.layout.activity_main);  
  22.         TextView myTextView=(TextView)findViewById(R.id.myTextView);  
  23.         myTextView.setText(helloWorldFromJNI());   
  24.           
  25.     }  
  26.     @Override  
  27.     public boolean onCreateOptionsMenu(Menu menu) {  
  28.         // Inflate the menu; this adds items to the action bar if it is present.  
  29.         getMenuInflater().inflate(R.menu.activity_main, menu);  
  30.         return true;  
  31.     }  
  32.   
  33. }  

在模拟器里面跑一下


2、先创建项目,再写native实现


1)创建工程

在Eclipse中创建一个Android工程,主Activity内容如下(基本没变):

[java] view plain copy
  1. package com.empty.ndktest2;  
  2.   
  3. import android.os.Bundle;  
  4. import android.app.Activity;  
  5. import android.view.Menu;  
  6. import android.widget.TextView;  
  7.   
  8. public class HelloWorldActivity extends Activity {  
  9.     // statement of native function.Means it has been realized by native layer.  
  10.     public native String helloWorldFromJNI();  
  11.       
  12.     // Load library,No Prefix and Suffix.  
  13.     static {    
  14.         System.loadLibrary("HelloWorld");  
  15.     }  
  16.     @Override  
  17.     protected void onCreate(Bundle savedInstanceState) {  
  18.         super.onCreate(savedInstanceState);  
  19.         setContentView(R.layout.activity_hello_world);  
  20.         TextView myTextView=(TextView)findViewById(R.id.myTextView);  
  21.         myTextView.setText(helloWorldFromJNI());   
  22.     }  
  23.   
  24.     @Override  
  25.     public boolean onCreateOptionsMenu(Menu menu) {  
  26.         // Inflate the menu; this adds items to the action bar if it is present.  
  27.         getMenuInflater().inflate(R.menu.activity_hello_world, menu);  
  28.         return true;  
  29.     }  
  30.   
  31. }  

保存之后Eclipse会自动编译成class文件,一般在项目目录/bin/classes下面,如果没有自动编译,钩选Project->Build Automatically.


2)编写Native实现

首先利用javah这个工具生成相应的.h文件。

在Android工程项目文件夹下创建jni文件夹,Terminal进入项目目录,运行:

[plain] view plain copy
  1. javah -classpath bin/classes -d jni com.empty.helloworld.HelloWorldActivity   

-classpath是指定class文件的位置,-d是制定生成头文件的位置,最后则是完整的类名。

执行成功之后jni文件中就会多出一个.h文件。

  1. /* DO NOT EDIT THIS FILE - it is machine generated */  
  2. #include   
  3. /* Header for class com_empty_helloworld_HelloWorldActivity */  
  4.   
  5. #ifndef _Included_com_empty_helloworld_HelloWorldActivity  
  6. #define _Included_com_empty_helloworld_HelloWorldActivity  
  7. #ifdef __cplusplus  
  8. extern "C" {  
  9. #endif  
  10. /* 
  11.  * Class:     com_empty_helloworld_HelloWorldActivity 
  12.  * Method:    helloWorldFromJNI 
  13.  * Signature: ()Ljava/lang/String; 
  14.  */  
  15. JNIEXPORT jstring JNICALL Java_com_empty_helloworld_HelloWorldActivity_helloWorldFromJNI  
  16.   (JNIEnv *, jobject);  
  17.   
  18. #ifdef __cplusplus  
  19. }  
  20. #endif  
  21. #endif  

这里面包含了.java文件中声明的native方法,我们再根据这个头文件去写函数实现。

  1. /* 
  2. FileName:HelloWorld.c 
  3. Description:realise of HelloWorld 
  4. */  
  5. #include     
  6. #include   
  7. JNIEXPORT jstring JNICALL Java_com_empty_helloworld_HelloWorldActivity_helloWorldFromJNI  
  8.   (JNIEnv *env, jobject thiz)   
  9. {    
  10.     return (*env)->NewStringUTF(env, "HelloWorld! I am from JNI too!");    
  11. }  


还需要在目录中添加Android.mk文件,内容和上面的一样。

3)编译库,在应用中调用

Terminal进入工程目录,终端运行

[plain] view plain copy
  1. $NDK_ROOT/ndk-build  

运行玩之后,libHelloWorld.so已经生成在/libs/armeabi文件夹里了。.

Eclipse中运行项目。



两种方法大同小异,开发的时候应当灵活的选择。


http://blog.csdn.net/silangquan/article/details/8495019

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

标签: