android NDK开发之编译和自动化生成jni头文件

ndk全称native develp kit,原生开发套件,起始它是提供java调用c/c++代码的工具链。

众所周知,java编译后的class二进制文件的运行效率比c/c++编译的so或dll文件效率低的多,所以,当我们对某种运算的运行效率要求高时,需要使用ndk编译好so文件,然后使用java进行调用,比如,视频编解码库。除了考虑运行效率问题,使用jni调用,还可以保护我们的核心算法不被轻易破解。我们一些重要的数据也可以放到里面去哦。

本文的以下内容都假设,ndk开发环境已经搭建完毕。


1、单个java和jni头文件生成,并完成app调用


本节的目标是,通过android java代码调用jni接口暴露的getUrl和getSum接口,分别取得url字符串和两个整数相加的和。

那么,首先定义一个java接口文件吧,

(1)

创建一个Api.java文件

package cn.hugo.android.<span style="font-size:18px;">ndkdemo</span>;

public class Api {
	public native int getSum(int a, int b);

	public static native String getUrl();

}


native关键字,是告诉编译器,我这个是给jni程序调用的,不要把我当成abstract方法,这样,我们在其它地方就可以new API() 这个类了,abstract是不可以new的。这个我们必须清楚。


(2)利用命令生成jni头文件

该.h头文件是连接c++程序和java程序的纽带,和Api这个类的native方法是一一对应的。

打开命令行窗口,cd到工程根目录的bin\classes下,执行如下命令

javah -jni cn.hugo.android.<span style="font-size:18px;">ndkdemo</span>.Api

执行完后,在classes文件夹就会出现一个名为 cn_hugo_android_ndkdemo_Api.h的文件

在eclipse中,右键该工程,选择如下,接下来会提示你jni编译后生成的so文件的名字(编译后的so文件名是"lib名字".so),你可以随便写一个,点ok,生成jni文件夹。该文件夹中会有一个cpp和一个mk文件。mk文件是编译脚本。

android NDK开发之编译和自动化生成jni头文件_第1张图片

提示:如果打开xxx.cpp文件后,发现 #include <jni.h> 有下划波浪线提示:

Unresolved inclusion: <jni.h>

解决方法:

右键工程->property。。。。配置include头文件的库路径,由于我打算在arm架构和x86架构都编译一份so文件,所以同时包含了这两个库。

android NDK开发之编译和自动化生成jni头文件_第2张图片

包含它们有什么用呢?ctrl+/的自动提示就靠它了~!!


话说回来,现在把生成的cn_hugo_android_ndkdemo_Api.h文件拷贝到jni文件夹中。


应该学过c++吧?.c文件一般是声明文件和存放常量的。我们现在需要把

JNIEXPORT jint JNICALL Java_cn_hugo_android_ndkdemo_Api_getSum
  (JNIEnv *, jobject, jint, jint);

JNIEXPORT jstring JNICALL Java_cn_hugo_android_ndkdemo_Api_getUrl
  (JNIEnv *, jclass);

两个方法拷贝到c++中进行实现


现在实现的方法如下:

JNIEXPORT jint JNICALL Java_cn_hugo_android_ndkdemo_Api_getSum(JNIEnv *env,
		jobject, jint a, jint b) {
	return a + b;
}

JNIEXPORT jstring JNICALL Java_cn_hugo_android_ndkdemo_Api_getUrl(JNIEnv *env,
		jclass) {
	return env->NewStringUTF("http://baidu.com");
}


调用JNI。。。。

作者在MainActivity.java中加载了两个按钮,使它们分别触发调用getSum和getUrl方法,并打印

private void init() {
		findViewById(R.id.btn_getAddResult).setOnClickListener(
				new OnClickListener() {

					@Override
					public void onClick(View v) {
						System.out.println("..........url=" + Api.getUrl());
					}
				});

		findViewById(R.id.btn_getText).setOnClickListener(
				new OnClickListener() {

					@Override
					public void onClick(View v) {
						Api api = new Api();
						System.out.println("..........sum="
								+ api.getSum(120, 5));

					}
				});
	}

在运行app之前,我们需要在调用jni前使用静态块加载so库。这里我生成的so文件是libndk.so

	static {
		System.loadLibrary("ndk");
	}

这时,运行该app,就可以正常工作了。如果之前没有编译so,这里将会对so进行编译,控制台信息如下:

[armeabi] Compile++ thumb: ndk <= ndk.cpp
[armeabi] SharedLibrary  : libndk.so
[armeabi] Install        : libndk.so => libs/armeabi/libndk.so

假如你的测试机子是arm架构的,app是可以成功运行的。

如果该so文件运行在x86内核的机子中,会爆出这样的error信息:

default: Ignoring feature request because could not acquire PhoneService
之后会解决同时支持多种架构的问题。


2、使用ant脚本同时生成多个头文件


前面是用命令生成一个头文件,但是当我们的工程多n多个jni接口需要生成.c头文件时,不可能一个个的使用命令行进行生成。此时,我们就要借助ant脚本了。


(1) 为了生成多个头文件,需要在 cn.hugo.android.ndkdemo 包下新建一个Security.java文件,声明一个native方法 public static native String getDesKey();


(2)在工程根目录下新建一个 build_headers.xml 文件,然后用ant editor编辑器打开该文件,在文件中使用alt+/键盘组合,让eclipse进行代码提示,生成一个模板。

编辑后的文件内容如下:

<?xml version="1.0" encoding="UTF-8"?>

<project name="ndk_demo" default="buildHeaders">
	<description>
            description
    </description>

	<!-- ================================= 
          target: buildHeaders              
         ================================= -->
	<target name="buildHeaders">
		<antcall target="buildApiHeader">
		</antcall>
		<antcall target="buildSecurityHeader">
		</antcall>
	</target>

	<!-- ================================= 
	   destdir: 输出路径
	   class:要执行的类                         
	================================= -->
	<target name="buildApiHeader">
		<javah destdir="./jni" classpath="./bin/classes/" class="cn.hugo.android.ndkdemo.Api">
		</javah>
	</target>
	<target name="buildSecurityHeader">
		<javah destdir="./jni" classpath="./bin/classes/" class="cn.hugo.android.ndkdemo.Security">
		</javah>
	</target>

</project>

(3) 把build_headers.xml加入到ant执行目标中:

打开eclipse工具右边的ant widget窗口,把该文件加入。如下图

android NDK开发之编译和自动化生成jni头文件_第3张图片

双击该文件,现在在jni目录下生成了两个头文件。

(3)新建一个cpp文件security.cpp实现getDesKey方法:

#include <jni.h>
#include "cn_hugo_android_ndkdemo_Security.h"

JNIEXPORT jstring JNICALL Java_cn_hugo_android_ndkdemo_Security_getDesKey(
		JNIEnv *env, jclass) {
	return env->NewStringUTF("afvaihgbbrtjiweojvwer");
}

(4)在android.mk文件中加入新建的源文件,让工具链对该文件生成目标文件,并连接到同一个so文件中。

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := ndk
LOCAL_SRC_FILES := ndk.cpp <span style="color:#FF0000;">security.cpp</span>

include $(BUILD_SHARED_LIBRARY)

这时,重新运行程序,又可以happy了。是不是很方便呢?


3、同时编译多种架构CPU的so文件、

在jni目录下,新建一个 Application.mk  文件,添加如下

<span style="font-size:18px;">APP_ABI := armeabi x86</span>

编译后,控制台输出:

<span style="font-size:18px;">[armeabi] Compile++ thumb: mylibrary <= api.cpp
[armeabi] Compile++ thumb: mylibrary <= security.cpp
[armeabi] SharedLibrary  : libmylibrary.so
[armeabi] Install        : libmylibrary.so => libs/armeabi/libmylibrary.so
[x86] Compile++      : mylibrary <= api.cpp
[x86] Compile++      : mylibrary <= security.cpp
[x86] SharedLibrary  : libmylibrary.so
[x86] Install        : libmylibrary.so => libs/x86/libmylibrary.so</span>

可以发现,它同时编译了两个版本。






你可能感兴趣的:(android,NDK)