Androd 中 NDK 编程详解(一)

上节讲解了NDK 开发环境搭建的方法,这节讲解下NDK编程的相关知识。

如何将.so文件打包到.APK

1、在你的项目根目录下建立libs/armeabi目录;

2、将libxxx.so文件copy到 libs/armeabi/下;

3、此时ADT插件自动编译输出的.apk文件中已经包括.so文件了;

4、安装APK文件,即可直接使用JNI中的方法;

我想还需要简单说明一下libxxx.so的命名规则,沿袭Linux传统,lib<something>.so是类库文件名称的格式,但在Java的System.loadLibrary(" something ")方法中指定库名称时,不能包括 前缀—— lib,以及后缀——.so。

当然,如果直接在模拟器上开发的话,将生成的.so 文件直接 adb remount, adb push *.so  /system/lib 即可.但是这个需要ROOT权限,感觉不实用,也不方便。

编写自己的NDK 应用

1、首先创建含有native方法的Java类:

public class MainActivity extends Activity {

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

	public native String stringFromJni();

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		TextView textView = new TextView(this);
		String tranString = stringFromJni();
		textView.setText(tranString);
		setContentView(textView);
	}
}


2、在C文件中直接实现Native 方法

当然,也可以采用javah 生成头文件,然后再根据相关函数名写C源代码文件,但是JNI的函数命名规则我们已经清楚了,直接自己写就OK!

具体做法:

在java 工程下建立 jni 文件夹, 下面加建立 test.c 和 Android.mk 文件即可。

test.c:

#include <string.h>
#include <jni.h>

jstring Java_com_sj_ndktest_MainActivity_stringFromJni(JNIEnv * env,
		jobject this) {
	return (*env)->NewStringUTF(env, "Hello from My-JNI !");
}

Android.mk:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myjni

LOCAL_SRC_FILES := myjni.c


include $(BUILD_SHARED_LIBRARY)


编译——两种不同的编译环境

1、Android NDK :全称是Native Developer Kit,是用于编译本地JNI源码的工具,为开发人员将本地方法整合到 Android 应用中提供了方便。事实上NDK和完整源码编译环境一样,都使用Android的编译系统——即通过Android.mk文件控制编译。NDK可以运行在Linux、Mac、Window(+cygwin)三个平台上。

2、完整源码编译环境 :Android平台提供有基于make的编译系统,为App编写正确的Android.mk文件就可使用该编译系统。该环境需要通过git从官方网站获取完整源码副本并成功编译,更多细节请参考:http://source.android.com/index.html

不管你选择以上两种方法的哪一个,都必须编写自己的Android.mk文件,有关该文件的编写请参考相关文档。

这里我们采用 NDK 环境编译,Cygwin 下 $NDK/ndk-build 即可。完成后会发现 文件夹下多了 libs 和 obj 文件夹,这样就对了。Eclipse 下直接Build  RUN工程,构建APK,其会将so 文件自动打包,安装,完成。

NDK命令介绍:

  ndk-build
  ndk-build clean --> 清空所编译出的二进制文件们。
  ndk-build -B V=1 --> 强制完全重新编译,并显示命令

JNI组件的常见函数——JNI_OnLoad()、JNI_OnUnload()

JNI组件被成功加载和卸载时,会进行函数回调,当VM执行到System.loadLibrary(xxx)函数时,首先会去执行JNI组件中的JNI_OnLoad()函数,而当VM释放该组件时会呼叫JNI_OnUnload()函数。

 JNI_OnLoad()有两个重要的作用:

(1)指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版),如果要使用新版本的JNI,例如JNI 1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4(该常量定义在jni.h中) 来告知VM。

(2)初始化设定,当VM执行到System.loadLibrary()函数时,会立即先调用JNI_OnLoad()方法,因此在该方法中进行各种资源的初始化操作最为恰当。

 JNI_OnUnload()作用:

JNI_OnUnload()的作用与JNI_OnLoad()对应,当VM释放JNI组件时会调用它,因此在该方法中进行善后清理,资源释放的动作最为合适。

本地方法注册——RegisterNatives方法

本地方法注册的作用:本地方法名不必固定按照 Java_包名_类名_方法名 的格式,我们完全可以自定义方法名,然后注册到虚拟机就可以。

当Java类别透过VM呼叫到本地函数时,通常是依靠VM去动态寻找.so中的本地函数(因此它们才需要特定规则的命名格式),如果某方法需要连续呼叫很多次,则每次都要寻找一遍,所以使用RegisterNatives将本地函数向VM进行登记,可以让其更有效率的找到函数。RegisterNatives方法的另一个重要用途是,运行时动态调整本地函数与Java函数值之间的映射关系,只需要多次调用RegisterNatives()方法,并传入不同的映射表参数即可。

本地方法注册实质是要建立本地方法和JAVA方法之间的映射关系,而建立c/c++方法和Java方法之间映射关系的关键是 JNINativeMethod 结构,该结构定义在jni.h中,具体定义如下:

/*
 * used in RegisterNatives to describe native method name, signature,
 * and function pointer.
 */

typedef struct {
    char *name;
    char *signature;
    void *fnPtr;
} JNINativeMethod;


定义映射关系数组:

static JNINativeMethod methods[] = { { "produceString",
		"(Ljava/lang/String;)Ljava/lang/String;", (void*) C_produceString } };

其中第一项即 name 项为java 方法名(不需要包含包名,直接写方法名即可);第三项为本地方法名;第二项看着比较复杂,其代表的是函数签名信息,括号内部是参数类型,后面是返回值类型,具体规则如下:

1)基本类型对应关系:
标识符  Jni 类型       C 类型
    void           void
    jboolean       boolean
    jint           int
    jlong          long
    jdouble        double
    jfloat         float
    jbyte          byte
    jchar          char
    jshort         short
2)基本类型数组:(则以 [ 开始,用两个字符表示)
标识串  Jni 类型        C 类型
  [Z   jbooleanArray  boolean[]
  [I   jintArray      int[]
  [J   jlongArray     long[]
  [D   jdoubleArray   double[]
  [F   jfloatArray    float[]
  [B   jbyteArray     byte[]
  [C   jcharArray     char[]
  [S   jshortArray    short[]

3)类(class):(则以 L 开头,以 ; 结尾,中间是用 / 隔开的 包 及 类名)
标识串        Java 类型  Jni 类型
L包1/包n/类名;     类名     jobject
例子:
Ljava/net/Socket; Socket      jobject

4)例外(String 类):
标识串               Java 类型  Jni 类型
Ljava/lang/String;  String    jstring

5)嵌套类(类位于另一个类之中,则用$作为类名间的分隔符)
标识串                         Java 类型  Jni 类型
L包1/包n/类名$嵌套类名;              类名      jobject
例子:
Landroid/os/FileUtils$FileStatus;  FileStatus  jobject

具体我觉得没比较死记硬背,碰到了去查就可以,看多了就记住了。也可以用 java提供的一个javap的工具帮助生成函数或变量的签名信息,用法如下:
javap -s -p xxx
xxx 为class文件,有了它,就不用记上面的类型表示了。

生成文件如下 eg:

  public com.sj.ndktest.MainActivity();
    Signature: ()V

  public native java.lang.String stringFromJni();
    Signature: ()Ljava/lang/String;

  public native java.lang.String produceString(java.lang.String);
    Signature: (Ljava/lang/String;)Ljava/lang/String;

  public void onCreate(android.os.Bundle);
    Signature: (Landroid/os/Bundle;)V

RegisterNatives具体用法代码示例:

//定义目标类名称
static const char *className = "com/sj/ndktest/MainActivity";
//定义方法隐射关系
static JNINativeMethod methods[] = { { "produceString",
		"(Ljava/lang/String;)Ljava/lang/String;", (void*) C_produceString } };

//onLoad方法,在System.loadLibrary()执行时被调用
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
	LOGI("JNI_OnLoad startup~~!");

	//声明变量
	jint result = JNI_ERR;
	JNIEnv* env = NULL;
	jclass clazz;
	int methodsLenght;

	//获取JNI环境对象
	if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
		LOGE("ERROR: GetEnv failed\n");
		return JNI_ERR;
	}
	assert(env != NULL);

	//注册本地方法.Load 目标类
	clazz = (*env)->FindClass(env, className);
	if (clazz == NULL) {
		LOGE("Native registration unable to find class '%s'", className);
		return JNI_ERR;
	}
	//建立方法隐射关系
	//取得方法长度
	methodsLenght = sizeof(methods) / sizeof(methods[0]);
	if ((*env)->RegisterNatives(env, clazz, methods, methodsLenght) < 0) {
		LOGE("RegisterNatives failed for '%s'", className);
		return JNI_ERR;
	}
	result = JNI_VERSION_1_4;
	return result;
}


NDK 日志和调试

上面的代码中看到LOGE 符号,这个是NDK 的日志输出,类似于 android 中的 log.e(...) ,其实质也是调用了 android 的日志输出。

NDK 使用日志输出必须引入头文件 #include <android/log.h>,如果是在完整源码环境下编译,引入 include <utils/Log.h> 即可。

具体使用方法:

#ifndef __JNILOGGER_H_
#define __JNILOGGER_H_
#include <android/log.h>
#ifdef _cplusplus
extern "C" {
#endif
#ifndef LOG_TAG
#define LOG_TAG   "MY_LOG_TAG"
#endif
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif
/* __JNILOGGER_H_ */

调试的话,方法有两种:一种就是上面的LOG日志输出,另一种利用 ndk-gdb 工具调试,使用方法与 gdb 工具一致。

Android MK 文件写法

示例文件:

# Copyright (C) 2009 The Android Open Source Project

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

#      http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myjni

LOCAL_SRC_FILES := myjni.c

LOCAL_LDLIBS    := -llog


include $(BUILD_SHARED_LIBRARY)

LOCAL_LDLIBS 指的是需要链接的库文件

 #宏函数'my-dir',编译系统提供,用于返回当前路径(即包含Android.mk文件的路径)                                                                 LOCAL_PATH -编译时的目录
$(call
目录,目录….)目录引入操作符
  
如该目录下有个文件夹名称src,则可以这样写$(call src),那么就会得到src目录的完整路径

include $(CLEAR_VARS) -清除之前的一些系统变量

# 当前模块的名称/编译的目标对象。编译系统会自动产生合适的前缀和后缀
LOCAL_MODULE
-编译生成的目标对象

# 包含将要编译打包进模块中的C或者C++源代码文件(无需列出头文件和包含文件)
LOCAL_SRC_FILES
-编译的源文件

LOCAL_C_INCLUDES
-需要包含的头文件目录
LOCAL_SHARED_LIBRARIES
-链接时需要的外部库
LOCAL_PRELINK_MODULE
-是否需要prelink处理

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

android.mk编译模块添加具体方法参考:http://blog.csdn.net/yili_xie/article/details/4906865

 

以上就是我的总结,附上完整源代码:

MainActivity.java:

package com.sj.ndktest;

import android.app.Activity;
import android.os.Bundle;
import android.os.Process;
import android.util.Log;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {

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

	public native String stringFromJni();

	public native String produceString(String string);

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		TextView textView = new TextView(this);
		String tranString = stringFromJni();
		String produString = produceString("hello ");
		textView.setText(tranString + "\n" + produString);
		setContentView(textView);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

	@Override
	public void onBackPressed() {
		super.onBackPressed();

		finish();
		Process.killProcess(Process.myPid());
	}

	@Override
	public void onDestroy() {
		super.onDestroy();
	}
}


myjni.c :

#include <string.h>
#include <jni.h>
#include <stdlib.h>
#include <assert.h>

#ifndef __JNILOGGER_H_
#define __JNILOGGER_H_
#include <android/log.h>
#ifdef _cplusplus
extern "C" {
#endif
#ifndef LOG_TAG
#define LOG_TAG   "MY_LOG_TAG"
#endif
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG,__VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
#define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG,__VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif
/* __JNILOGGER_H_ */



jstring Java_com_sj_ndktest_MainActivity_stringFromJni(JNIEnv * env,
		jobject this) {

	LOGE("123");
	return (*env)->NewStringUTF(env, "Hello from My-JNI !");
}

jstring C_produceString(JNIEnv * env,
		jobject this, jstring str) {

	//从jstring类型取得c语言环境下的char*类型
	const char* name = (*env)->GetStringUTFChars(env, str, 0);
	LOGE(name);
	//output:hello
	//本地常量字符串
	char* hello = "YEAH";
	//动态分配目标字符串空间
	char* result = malloc((strlen(name) + strlen(hello) + 1) * sizeof(char));
	memset(result, 0, sizeof(result));
	//字符串链接
	strcat(result, hello);
	strcat(result, name);
	//释放jni分配的内存
	(*env)->ReleaseStringUTFChars(env, str, name);
	//生成返回值对象
	str = (*env)->NewStringUTF(env, " JNI~!");
	//strcat(str, result);
	//释放动态分配的内存
	LOGE(result);
	//output:YEAHhello
	free(result);
	return str;
}

//定义目标类名称
static const char *className = "com/sj/ndktest/MainActivity";
//定义方法隐射关系
static JNINativeMethod methods[] = { { "produceString",
		"(Ljava/lang/String;)Ljava/lang/String;", (void*) C_produceString } };

//onLoad方法,在System.loadLibrary()执行时被调用
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
	LOGI("JNI_OnLoad startup~~!");

	//声明变量
	jint result = JNI_ERR;
	JNIEnv* env = NULL;
	jclass clazz;
	int methodsLenght;

	//获取JNI环境对象
	if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
		LOGE("ERROR: GetEnv failed\n");
		return JNI_ERR;
	}
	assert(env != NULL);

	//注册本地方法.Load 目标类
	clazz = (*env)->FindClass(env, className);
	if (clazz == NULL) {
		LOGE("Native registration unable to find class '%s'", className);
		return JNI_ERR;
	}
	//建立方法隐射关系
	//取得方法长度
	methodsLenght = sizeof(methods) / sizeof(methods[0]);
	if ((*env)->RegisterNatives(env, clazz, methods, methodsLenght) < 0) {
		LOGE("RegisterNatives failed for '%s'", className);
		return JNI_ERR;
	}
	result = JNI_VERSION_1_4;
	return result;
}

//onUnLoad方法,在JNI组件被释放时调用
void JNI_OnUnload(JavaVM* vm, void* reserved) {
	LOGE("call JNI_OnUnload ~~!!");
}


Android.mk :

# Copyright (C) 2009 The Android Open Source Project

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

#      http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

#

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := myjni

LOCAL_SRC_FILES := myjni.c

LOCAL_LDLIBS    := -llog


include $(BUILD_SHARED_LIBRARY)

 

参考地址,在此表示感谢:

http://mobile.51cto.com/android-267538_2.htm

http://blog.sina.com.cn/s/blog_4c451e0e0101339i.html

http://www.cnblogs.com/bastard/archive/2012/05/19/2508913.html

http://jykenan.iteye.com/blog/1140965

http://blog.csdn.net/gongyangyang100/article/details/7418436

http://www.rosoo.net/a/201204/15925.html

http://www.huidawang.info/?p=47

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值