Android NDK开发(二)——从Hello 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方法:
public class MainActivity extends Activity {

	public native String javaFromJNI();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		findViewById(R.id.button).setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				Toast.makeText(MainActivity.this, javaFromJNI(),
						Toast.LENGTH_SHORT).show();
			}
		});
	}

}
2,创建jni目录,编写c代码,方法名字要对应在c代码中导入jni.h头文件
#include<stdio.h>
#include<jni.h>

jstring Java_com_example_ndk_MainActivity_javaFromJNI(JNIEnv* env, jobject obj) {
	return (*(*env)).NewStringUTF(env, "hello jni!");
}

关于这个本地的C代码怎么写,还是需要一些C语言的基础的。没有也可以,我们可以参考一下ndk解压目录下的platforms\android-19\arch-arm\usr\include目录下的jni.h文件,也就是本地C代码需要include的那个,用记事本打开看看里面的内容。先来说一下JNI代码的简单格式:

方法签名规则:返回值类型 Java_包名_类名_native方法名(JNIEnv* env, jobject obj)

返回值类型就是JNI头文件中事先定义好的自定义C类型,直接拿来使用即可:

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

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

  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修改成我们自己的名称:

    LOCAL_PATH := $(call my-dir)

    include $(CLEAR_VARS)

    LOCAL_MODULE    := Hello
    LOCAL_SRC_FILES := Hello.c

    include $(BUILD_SHARED_LIBRARY)
4,ndk编译生成动态库

然后在cygwin中编译一下:

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

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

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

public class MainActivity extends Activity {

	static {
		System.loadLibrary("Hello");
	}
	public native String javaFromJNI();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		findViewById(R.id.button).setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				Toast.makeText(MainActivity.this, javaFromJNI(),
						Toast.LENGTH_SHORT).show();
			}
		});
	}

}

System.loadLibrary(String 文件名);是用来加载动态库的方法,其中参数类型是字符串,参数是Android.mk文件中LOCAL_MODULE定义的名称。

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

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

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

public native String java_From_JNI();

假如我们按照上述的规则,在C代码中套用,定义出这样的C函数:

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-mime\NDKHelloWorld\bin\classes

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

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

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

用记事本打开这个文件

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_ndk_MainActivity */

#ifndef _Included_com_example_ndk_MainActivity
#define _Included_com_example_ndk_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_ndk_MainActivity
 * Method:    javaFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_javaFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_example_ndk_MainActivity
 * Method:    java_From_JNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_java_1From_1JNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

上面就是我们需要的方法签名了,这就是javah工具自动为我们生成的native头文件,下面我们需要引用这个头文件到工程中去。将这个头文件直接剪切,粘贴到工程的jni的目录下,然后重写一个Hello.c的C代码,将#include"com_example_ndk_MainActivity.h"放在代码的头部,表示引入刚刚生成好的头文件,注:在C语言中#include<xx.h>表示引用C语言环境(编译)自带的头文件,#include"xx.h"表示引用当前自定义的头文件。引用好头文件之后,将头文件中的两个方法签名拷贝进来,实现逻辑:

#include<stdio.h>
#include<jni.h>
#include"com_example_ndk_MainActivity.h"

JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_javaFromJNI(
		JNIEnv* env, jobject obj) {
	return (*env)->NewStringUTF(env, "hello jni!");
}

/*
 * Class:     com_example_ndk_MainActivity
 * Method:    java_From_JNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_ndk_MainActivity_java_1From_1JNI(
		JNIEnv* env, jobject obj) {
	return (*env)->NewStringUTF(env, "hello_jni__");
}

重新编译:

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

Android.mk简介

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

#交叉编译器在编译C/C++代码所依赖的配置文件,linux下makefile的语法子集

    #获取当前Android.mk的路径
    LOCAL_PATH := $(call my-dir)
    #变量的初始化操作 特点:不会重新初始化LOCAL_PATH的变量
    include $(CLEAR_VARS)
    #指定编译后生成的.so文件名,makefile语法约定文件名加前缀lib和后缀.so
    LOCAL_MODULE    := Hello
    #指定native代码文件
    LOCAL_SRC_FILES := Hello.c
    #指定native代码编译成动态库.so或者指定编译成静态库.a
    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。

时间: 2024-08-25 09:54:49

Android NDK开发(二)——从Hello World学起的相关文章

Android ndk开发swig编译jni接口配置文件(二)

之前写过一篇Android ndk开发swig编译jni接口.看这篇看不懂,看以去看看.c++与Java有些语言结构还是有一定区别,比如c++结构体,一些函数的返回值等都是有所不同,进行swig编译要进行一些预处理,也就是配置一下就行.下面说说几种情况. 一.一般情况下string,数组,枚举类型等配置Unix.i %module Survey %include "std_string.i" %include "arrays_java.i" %include &qu

跟我学Android NDK开发(一)

Android NDK 开发跟其它开发一样,首先需要配置好开发环境,本文以 Ubuntu系统为例介绍如何进行 Android NDK 开发环境的配置. 1. 简介 什么是 Android NDK 呢? NDK(Native Development Kit) 是一个允许开发者用一些本地语言(C/C++)编写 Android App 的部分功能的工具集.对于一些特定的 App,NDK 非常有利于我们直接使用现成的用 C/C++ 编写的代码库(但对于大多数 App 来说,NDK 是没有必要的).使用

android NDK开发在本地C/C++源码中设置断点单步调试具体教程

近期在学android NDK开发,折腾了一天,最终可以成功在ADT中设置断点单步调试本地C/C++源码了.网上关于这方面的资料太少了,并且大都不全,并且调试过程中会出现各种各样的问题,真是非常磨人.程序员就得有耐心. 把自己的调试过程记录下来.希望对须要的朋友有帮助. 在看本文之前,请先确保你已经成功编译了一个android NDKproject,而且可以在模拟器或者真机上执行.至于怎么编译NDKproject,包含配置.生成.so文件等等.可以參考我的前一篇博客:http://blog.cs

Android NDK开发初识

神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理,通过在线视频教育网站,我初步了解了NDK的神秘面纱,好东西自然要分享,接下来我们就一起来认识一下Android NDK开发. 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三

android NDK开发在本地C/C++源代码中设置断点单步调试详细教程

最近在学android NDK开发,折腾了一天,终于能够成功在ADT中设置断点单步调试本地C/C++源代码了.网上关于这方面的资料太少了,而且大都不全,并且调试过程中会出现各种各样的问题,真是很磨人,程序猿就得有耐心.把自己的调试过程记录下来,希望对需要的朋友有帮助. 在看本文之前,请先确保你已经成功编译了一个android NDK工程,并且能够在模拟器或者真机上运行.至于怎么编译NDK工程,包括配置.生成.so文件等等,可以参考我的前一篇博客:http://blog.csdn.net/u013

Android NDK开发篇(五):Java与原生代码通信(数据操作)

尽管说使用NDK能够提高Android程序的运行效率,可是调用起来还是略微有点麻烦.NDK能够直接使用Java的原生数据类型,而引用类型,由于Java的引用类型的实如今NDK被屏蔽了,所以在NDK使用Java的引用类型则要做对应的处理. 一.对引用数据类型的操作 尽管Java的引用类型的实如今NDK被屏蔽了,JNI还是提供了一组API,通过JNIEnv接口指针提供原生方法改动和使用Java的引用类型. 1.字符串操作 JNI把Java的字符串当作引用来处理,在NDK中使用Java的字符串,须要相

Android NDK 开发

NDK全称:Native Development Kit. 1.NDK是一系列工具的集合. * NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk.这些工具对开发者的帮助是巨大的. * NDK集成了交叉编译器,并提供了相应的mk文件隔离平台.CPU.API等差异,开发人员只需要简单修改mk文件(指出"哪些文件需要编译"."编译特性要求"等),就可以创建出so. * NDK可以自动地将so和Java应用一起

Android NDK开发常见错误

错误一: make: *** No rule to make target `/cygdrive/d/1-workspace/showmap-android-opengles/jni/showmap_opengles_OpenGLESRenderer.c', needed by `/cygdrive/d/1-workspace/showmap-android-opengles/obj/local/armeabi/objs/OpenGLESMap/showmap_opengles_OpenGLES

Android NDK开发(七)——现代化开发方式

本专栏的前面几篇博客写了一些Android下通过JNI进行NDK开发的基础知识,主要步骤在里面也写的很清晰,但是看起来比较麻烦,为什么呢?可能细心的朋友会发现我每次在进行交叉编译的时候,是通过cygwin模拟Linux环境用ndk-build命令进行编译的,而且每次新建JNI工程的时候,都会在工程目录下手动的新建一个jni的目录,在该目录下新建C源码文件和Android.mk配置文件,这一系列的操作显得特别繁琐,尤其是cygwin,首先其下载安装是个非常耗时耗力的操作,其次使用cygwin还需要