基于Eclipse的Android JNI层測试应用开发过程记录

前言

  本文记录一个Java层与JNI层參数与数据交互的应用程序开发过程。为实现一个功能完整的带Java与JNI的应用程序打下基础。

本文如果读者已搭建好Android的Eclipse与NDK开发环境,包含通过ADB连接手机的配置。

1. 构建主要的Android应用程序

1.1 引导界面配置

  打开Eclipse,"File"->"New"->"Android
Application Project",在弹出界面,配置例如以下(红色框表示不是默认,是作者改动过的地方,下同):

  之后,一路点击"Next >"。直到"Finish"。点击"Finish"之后,自己主动回打开例如以下界面:



watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2xvdWRfZGVza3RvcA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

  

1.2 XML文件配置

  我们能够看到一个虚拟的应用界面,当中有一行文字"Hello
world!"。

点击上图右下角的activity_main.xml,当中有例如以下定义:

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

  当中的字符串就定义在res目录下values目录中的strings.xml中。我们对hello_world的值做改动,例如以下:

<?

xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">app</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Napolean: Hello Android Application!</string>

</resources>

  依据须要,能够将上图中模拟应用界面的左边栏中的控件拖入模拟应用界面上放置。对应地,XML文件里就会有该控件的布局描写叙述。

1.3 执行应用程序

  能够再src文件夹下有个MainActivity.java,这是上面引导界面配置完毕后自己主动生成的代码。

package com.napolean.app;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

}

  插入手机连接上ADB,或者配置一个Android模拟器,点击执行程序,就可以看到应用程序执行并显示我们所改动的字符串。关于手机怎样通过ADB连接以及Android模拟器的配置。在此不作展开。

2. 构建Java与C/C++交互的JNI层接口

  Java与C/C++交互分为两边:一边是Java层:在Java层的类中声明本地接口函数,并在公共接口函数调用,使得须要訪问JNI层函数的类调用这些公共函数就可以;还有一边是JNI层:JNI层有相应于Java层中本地函数的相应函数,也有其他普通函数,在Java层本地函数被调用时,实际上终于会运行相应的C/C++层函数。

2.1 Java层包括JNI本地函数的类

  

  右键点击src文件夹下的com.napolean.app,选择"New"->"Class",在弹出的界面中输入类名"NativeInterface","Finish"就可以完毕NativeInterface类的创建。在生成的NativeInterface.java中的空类NativeInterface中加入Native函数及其封装的公共接口,详细代码例如以下:

package com.napolean.app;

public class NativeInterface {

	private native void native_init();
	private native void native_exit();

	private native byte[] native_process(byte[] in_buffer, int width, int height);

	public void NativeInit() {
		native_init();
	}

	public void NativeExit() {
		native_exit();
	}

	public void NativeProcess(byte[] in_buffer, int width, int height)
	{
		native_process(in_buffer, width, height);
	}

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

}

  所创建的Native函数有初始化函数、退出函数和处理函数。

当中处理函数包括输入字节流、Buffer宽度、Buffer高度,返回处理后的字节流。接下来三个函数各自是三个Native函数的公共接口。

  最后在static中调用System.loadLibrary()。当中引號内为后面JNI层动态库库名。实际生成的动态库名会在Android.mk中指定的库名前加入lib之后加入.so,即声明为xxx的库,终于生成libxxx.so。值得注意的是:在上述载入函数中必须使用xxx而不能使用全名。而且在Android中指定的库名不能带lib前缀,否则无法载入。

2.2 本地函数在JNI层的实现

  首先创建jni目录:右键点击project名,"New"->"Folder"。输入"jni"。创建jni目录。

  接着。在终端输入javah命令,创建依据NativeInterface中声明native函数相应的JNI层函数接口,在project根文件夹下详细命令为

javah -classpath bin/classes -d jni com.napolean.app.NativeInterface

  执行命令后。在jni目录下就会生成一个名为com_napolean_app_NativeInterface.h的头文件。里面包括有的接口函数例如以下:

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

#ifndef _Included_com_napolean_app_NativeInterface
#define _Included_com_napolean_app_NativeInterface
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_napolean_app_NativeInterface
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_napolean_app_NativeInterface_native_1init
  (JNIEnv *, jobject);

/*
 * Class:     com_napolean_app_NativeInterface
 * Method:    native_exit
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_napolean_app_NativeInterface_native_1exit
  (JNIEnv *, jobject);

/*
 * Class:     com_napolean_app_NativeInterface
 * Method:    native_process
 * Signature: ([BII)[B
 */
JNIEXPORT jbyteArray JNICALL Java_com_napolean_app_NativeInterface_native_1process
  (JNIEnv *, jobject, jbyteArray, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

  生成这个头文件的目的是免去手动编写JNI层的接口函数,并无其他用途,使用后可删除。在jni目录下创建NativeInterface.cpp,将上述头文件里全部内容拷入,经过改造,例如以下:

#include <jni.h>
#include <android/log.h>

#define LOG_TAG "APP"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

#ifndef _Included_com_napolean_app_NativeInterface
#define _Included_com_napolean_app_NativeInterface

#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     com_napolean_app_NativeInterface
 * Method:    native_init
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_napolean_app_NativeInterface_native_1init
  (JNIEnv *, jobject)
{
	LOGI("APP native init.");
}

/*
 * Class:     com_napolean_app_NativeInterface
 * Method:    native_exit
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_napolean_app_NativeInterface_native_1exit
  (JNIEnv *, jobject)
{
	LOGI("APP native exit.");
}

/*
 * Class:     com_napolean_app_NativeInterface
 * Method:    native_process
 * Signature: ([BII)[B
 */
JNIEXPORT jbyteArray JNICALL Java_com_napolean_app_NativeInterface_native_1process
  (JNIEnv *, jobject, jbyteArray, jint, jint)
{
	LOGI("APP native process.");

}

#ifdef __cplusplus
}
#endif
#endif

  上述改造的代码主要是将函数声明改为函数定义,而且加入了信息打印的支持。

2.3 JNI层动态库编译配置

  假设你亲手创建project。就会发现头文件和函数定义的字符以下有黄色下划线。这是由于Eclipse找不到jni.h和android/log.h的头文件。所以须要在Eclipse配置JNI层特有的头文件路径。

  第一步,加入C/C++特性。右键点击jni目录,"New"->"Other"。在弹出界面例如以下选择:

在下一个界面中的指定project类型中选择"Shared
Library"(这一步并不重要,由于后面须要改动编译工具),点击"Finish",Eclipse就開始转换工作。

  第二步,改动编译工具。右键点击project名,"Properties"。弹出例如以下界面,当中左边栏中的"C/C++
Build"与"C/C++ General"是上一步转换工作完毕后新的。选择"C/C++ Build"。在"Builder Settings"标签中,去掉"Use default build command"前的勾,输入"ndk-build",点击"Apply"应用,例如以下所看到的:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvY2xvdWRfZGVza3RvcA==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >

  第三步,配置头文件路径。在"C/C++
General"->"Paths and Symbols"->"Includes"->"GNU C++"中加入头文件路径。

点击右边的"Add...",加入四个路径。然后点击"Apply"应用。

当中,四个头文件路径为:

/home/work/android/android-ndk-r9/platforms/android-18/arch-arm/usr/include/
/home/work/android/android-ndk-r9/sources/cxx-stl/gnu-libstdc++/4.6/include/
/home/work/android/android-ndk-r9/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/include/
/home/work/android/android-ndk-r9/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/lib/gcc/arm-linux-androideabi/4.6/include/

  第四步。创建Android.mk。右键点击jni目录,"New"->"File",输入Android.mk。

加入例如以下代码:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := NativeInterface
LOCAL_SRC_FILES := NativeInterface.cpp

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

  第五步。编译生成动态库。点击Eclipse菜单条中的"Project"->"Build Project",就可以完毕编译。在libs/armeabi/目录下生成了"libNativeInterface.so"动态库。

2.4 Java层中调用本地函数

  Java层调用JNI层本地函数的方法,例如以下所看到的。

package com.napolean.app;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class MainActivity extends Activity {

	private NativeInterface myJNI;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		byte[] in_buffer = new byte[1920*1080*3/2];
		int width = 1920;
		int height = 1080;

		myJNI = new NativeInterface();

		myJNI.NativeInit();
		myJNI.NativeProcess(in_buffer, width, height);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	protected void onDestroy() {
		myJNI.NativeExit();
		super.onDestroy();
	}

}

附录:很多其它内容

  參见我的ChinaUnix博客文章:http://blog.chinaunix.net/uid-20746260-id-3910616.html

时间: 2024-10-09 17:33:02

基于Eclipse的Android JNI层測试应用开发过程记录的相关文章

基于Eclipse的Android JNI层测试应用开发过程记录

前言 本文记录一个Java层与JNI层参数与数据交互的应用程序开发过程,为实现一个功能完整的带Java与JNI的应用程序打下基础.本文假设读者已搭建好Android的Eclipse与NDK开发环境,包括通过ADB连接手机的配置. 1. 构建基本的Android应用程序 1.1 引导界面配置 打开Eclipse,"File"->"New"->"Android Application Project",在弹出界面,配置如下(红色框表示不是

Android Jni层 创建 linux socket 出错问题解决

问题: 想在Jni层创建 udp socket 与服务端通信,但是没有成功,最后发现竟然是创建socket失败(代码如下) // create socket g_sd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == g_sd) { perror("socket()"); goto err_socket; } 解决办法: 在 AndroidManifest.xml 文件中,添加访问网络的权限: <uses-permission android

利用Continuous Testing实现Eclipse环境自己主动单元測试

当你Eclipse环境中改动项目中的某个方法时,你可能因为各种原因没有执行单元測试,结果代码提交,悲剧就可能随之而来. 所幸infinitest(http://infinitest.github.io/)提供了一个Continuous Testing插件,以及时自己主动执行单元測试.尽管会多占一些CPU资源,但开发者的硬件谁会不留一点余地呢?大不了,音乐.视频.360卸载就OK了.安装方法有两种: (1)使用"Install new software",输入地址:http://infi

基于eclipse搭建android开发环境-win7 32bit

基于eclipse搭建android开发环境-win7 32bit 前言:在使用朋友已搭建的Android开发环境时,发现朋友的开发环境版本较低且在update SDk时失败,便决定根据网上文章提示从头搭建一全新的Android开发环境. 1.准备工作 下载Eclipse.JDK(7).Android SDK.ADT插件. 地址: eclipse:http://www.eclipse.org/downloads/ 版本:Luna Service Release 1 (4.4.1) jdk:htt

Android 进行单元測试难在哪-part3

原文链接 : HOW TO MAKE OUR ANDROID APPS UNIT TESTABLE (PT. 1) 原文作者 : Matthew Dupree 译文出自 : 开发技术前线 www.devtf.cn 译者 : chaossss 校对者: tiiime 状态 : 完毕 在 Android 应用中进行单元測试非常困难.有时候甚至是不可能的.在之前的两篇博文中,我已经向大家解释了在 Android 中进行单元測试如此困难的原因.而上一篇博文我们通过分析得到的结论是:正是 Google 官

香蕉派路由功Openwrt、Android功耗对照測试

路由这个东西是要长期通电使用的,所以功耗也是须要关注的.如今香蕉派路由已经有了openwrt和android两个 系统,这两个系统的功耗是否一样呢? 測试工具:QUIGG的德国产功耗測试仪一个.手机充电器一个 香蕉派路由:除网线.调试串口外,没有接不论什么外部设备,当然TF卡不可缺少.没有开启无线 手机充电器自身的功耗: 电流: 功耗: 一.openwrt稳定后的数据: 电流: 功耗: 二.android稳定后的数据: 电流: 功耗: 因为openwrt眼下功能不完好,至少HDMI没有驱动起来,

软件測试相关简要记录

软件測试 编码和測试统称为实现. 通常在编写出每一个模块之后就对程序做必要的測试,这叫做单元測试. 模板的编写者和測试者是同一个人. 之后会进行其它综合測试.由专门的測试人员承担这份工作.也就是软件測试project师. 软件測试的工作量往往占软件开发总工作量的40%以上. 编码 对于编码有例如以下要求: 1)程序内部的文档 2)数据说明 3)语句构造 4)输入输出 5)效率:程序执行时间.存储器效率.输入输出的效率 软件測试基础 一.软件測试的目标 1)測试是为了发现程序中的错误而执行程序的过

Mac OS下基于Eclipse的Android调试环境搭建

1.安装Eclipse:http://www.eclipse.org/downloads/,网页会自动检测适用的版本(Mac OS x64),下载“Eclipse IDE for java Developers”并安装: 2.尝试运行Eclipse,若提示“您需要安装旧 Java SE 6 运行环境才能打开”,则点击弹出对话框里面的“详情”,跳转到苹果官网的指定页面(如:https://support.apple.com/kb/DL1572?locale=zh_CN),下载需要的文件并安装: 3

Android JNI层确保只有一个进程的一个实现

有时需要在Android的C层中创建一个新的进程比如myServer,但是我们又不希望,同一时间有多个myServer存在. 本文介绍个方法,相当于在在后台执行"ps myServer",获取结果,进行分析,在主线程中调用. 查询一个名为myServer的进程是否存在可以这么使用: getMyPid(); 查询失败返回-1,没有这个进程返回0,如果进程存在就返回该进程的pid. 以下是代码实现. int pidIndex(const char* szData) {      char