Android Native jni 编程入门
在某些情况下,java编程已经不能满足我们的需要,比如一个复杂的算法处理,这时候就需要用到jni(java native interface)技术;
- jni 其实就是java和c/cpp之间进行通信的一个接口规范,java可以调用c/cpp里面的函数,同样,c/cpp也可以调用java类的方法;
jni开发工具ndk的安装:
在最新的ndk版本中,安装ndk很简单,只需要装ndk的路径配置到系统环境变量中即可;
在编译的时候,进入工程根目录;执行命令 ndk-build 即可完成编译;
下面就通过一个例子一步一步的来初步学习jni
一、HelloWorld
新建一个工程,你甚至不需要其它额外的设置,然后在工程中添加一个jni目录,然后就可以开始了;
1.新建一个java类HelloWorld.java
package com.jni; public class HelloWorld {
static {
System.loadLibrary("helloworld");
} public native String helloworld();
}
在HelloWorld中,定义了一个方法helloworld(),只不过这个方法被申明成了native的,并没有具体的实现,具体功能我们在接下来的cpp文件中实现;
2.在jni目录下添加一个helloworld.cpp
#include <jni.h>
#include <android/log.h>
#include <string.h> #ifndef _Included_com_jni_HelloWorld // 1
#define _Included_com_jni_HelloWorld #ifdef __cplusplus // 2
extern "C" {
#endif // 2
JNIEXPORT jstring JNICALL Java_com_jni_HelloWorld_helloworld(JNIEnv *, jobject);
#ifdef __cplusplus // 3
}
#endif // 3
#endif // 1 JNIEXPORT jstring JNICALL Java_com_jni_HelloWorld_helloworld(JNIEnv * env,
jobject obj) {
return env->NewStringUTF("helloworld");
}
从上面这个cpp文件中可以很明白的看出,它有一个方法,具体包括方法申明和方法实现两个部分;但是相信大家也都看出来了,方法的命令很怪异,怎么这么长的方法名?
我们在这里先思考一个问题,java类中的方法是如何调用c++中的方法的呢?要解决这个问题,就得先来看这个长长的方法名;
其实这是jni的一个规范之一,用于映射java方法和c/c++中的方法对应;
再来看在cpp中定义的函数名:Java_com_jni_HelloWorld_helloworld
其实不难看出,java文件与cpp文件中函数名的配对定义方式为Java + 包名 + java类名 + 方法/函数名,中间用_分隔;其中两个参数分别是:
- env:当前该线程的内容,包含线程里面全部内容;
- obj:当前类的实例,指.java文件的内容(在该例子中即是HelloWorld类);
这里的helloworld方法,其实就只是返回了一个单词"helloworld";
3.在jni目录下添加一个Android.mk文件
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS)
LOCAL_MODULE := helloworld
LOCAL_SRC_FILES := helloworld.cpp
include $(BUILD_SHARED_LIBRARY)
4.在命令行下进入工程目录执行 ndk-build 命令,然后运行程序,调用HelloWorld实例的helloworld方法就可以得到它的返回字符串了;
二、jni调用Java类的方法(1)
通过上面的helloworld练手之后,我们来看一下jni调用java类里面的方法的实现;
1.新建设一个MethodCall.java文件如下
public class MethodCall {
final String tag = "MethodCall";
static {
System.loadLibrary("methodcall");
} public native String jniCallMethod1(); public native String jniCallMethod2(); public native String jniCallStaticMethod(); public void javaMethod1() {
Log.e(tag, "javaMethod1");
} public String javaMethod2() {
Log.e(tag, "javaMethod2");
return "javaMethod2";
} public static void javaStaticMethod(String input) {
Log.e("MethodCall", "" + input);
}
}
该类有6个方法,其中有3个是java类的方法,另外3个是native方法,3个native方法分别去调用3个java方法;
2.添加三个native方法具体实现 methodcall.cpp
#include <jni.h>
#include <android/log.h>
#include <string.h> #ifndef _Included_com_jni_MethodCall
#define _Included_com_jni_MethodCall #ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod1(JNIEnv *,
jobject);
JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod2(JNIEnv *,
jobject);
JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallStaticMethod(JNIEnv *,
jobject);
#ifdef __cplusplus
}
#endif
#endif JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod1(JNIEnv * env,
jobject obj) {
jmethodID mid; // 方法标识id
jclass cls = env->GetObjectClass(obj); // 类的对象实例
mid = env->GetMethodID(cls, "javaMethod1", "()V");
env->CallVoidMethod(obj, mid);
return env->NewStringUTF("jniCallMethod1");
} JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallMethod2(JNIEnv * env,
jobject obj) {
jmethodID mid; // 方法标识id
jclass cls = env->GetObjectClass(obj); // 类的对象实例
mid = env->GetMethodID(cls, "javaMethod2", "()Ljava/lang/String;");
jstring js = (jstring) env->CallObjectMethod(obj, mid);
return js;
} JNIEXPORT jstring JNICALL Java_com_jni_MethodCall_jniCallStaticMethod(
JNIEnv * env, jobject obj) {
jmethodID mid; // 方法标识id
jclass cls = env->GetObjectClass(obj); // 类的对象实例
mid = env->GetStaticMethodID(cls, "javaStaticMethod",
"(Ljava/lang/String;)V");
jstring input = env->NewStringUTF("jniCallStaticMethod->>javaStaticMethod");
env->CallStaticVoidMethod(cls, mid, input);
return env->NewStringUTF("jniCallStaticMethod");
}
该cpp文件中有3个方法(我这里把方法名都写得很明白直观,相信不需要注释都知道是调用的哪一个java方法)
我们知道,在java编程中,对一个类的调用,其实是先创建一个类的对象实例,然后再调用它的方法(这里指的是非static方法) ,那么我们是如何在c/c++文件中调用java方法的呢?
回到上面的HelloWorld,我们讲方法名的时候,下边有随便提到的方法的参数,其中,第二个参数obj其实就是我们在java中使用的类的实例,到这里,相信是如何调用java方法的大家都明白了吧;
在java中,每一个方法其实都有一个id,我们在c/c++中不能直接通过obj来调用一个java方法,我们要先获取方法的id,通过GetMethodID()来获取,需要传入类的类型,方法名,方法的签名(方法签名在文章后面会讲到签名规则);然后再在线程里面调用java方法,通过env->Call****Method();需要传入对象实例,方法id,或者其它参数;(上面只展示了几个这种方法,其它的方法如果大家有需要用到可以自行查找资料解决);
3.编写Android.mk文件,在Android.mk文件后面添加如下内容
include $(CLEAR_VARS)
LOCAL_MODULE := methodcall
LOCAL_SRC_FILES := methodcall.cpp
include $(BUILD_SHARED_LIBRARY)
4.执行ndk-build 命令,下面是分别执行3个jniCall****方法的结果
三、jni调用Java类的方法(1)
上面是c++调用java方法的例子,下面再帖一个c调用java方法的例子
1.Java文件 MethodCall1.java
package com.jni; public class MethodCall1 {
static {
System.loadLibrary("methodcall1");
} public static int value = 0; public static void javaMethod() {
value = 12;
} public native int jniCalljavaMethod();
}
2.methodcall.c
#include <string.h>
#include <jni.h> jint Java_com_jni_MethodCall1_jniCalljavaMethod(JNIEnv* env, jobject thiz)
//env:当前该线程的内容,包含线程全部的东西;thiz:当前类的实例,指.java文件的内容
{
jint si;
jfieldID fid; // 一个字段,实际上对应java类里面的一个字段或属性;
jclass cls = (*env)->GetObjectClass(env, thiz); // 类的对象实例
jmethodID mid = (*env)->GetStaticMethodID(env, cls, "javaMethod", "()V"); // 一个方法的id
//(I)V (I)I
if (mid == NULL) {
return -1;
}
(*env)->CallStaticVoidMethod(env, cls, mid); //调用callback方法
fid = (*env)->GetStaticFieldID(env, cls, "value", "I"); //取出value字段
if (fid == NULL) {
return -2;
}
si = (*env)->GetStaticIntField(env, cls, fid); //取出字段对应的值(fid字段对应的值)
return si;
// return (*env)->NewStringUTF(env, "init success");
}
3.完善Android.mk文件,参照二里面第四步;
4.运行代码
MethodCall1 mc1 = new MethodCall1();
Log.e(tag, MethodCall1.value + "->" + mc1.jniCalljavaMethod());
四、方法签名规则
Java类型 | 类型签名 | Java类型 | 类型签名 |
boolean | Z | long | J |
byte | B | float | F |
char | C | double | D |
short | S | 类 | L全限定类名; |
int | I | 数组 | [元素类型签名 |
上面是各种数据类型对应的签名字符
- 基本数据类型的签名很简单,只是一个选定的字母;
- 类的签名规则是:"L" + 全限定类名+";"三部分组成,其中全限定类名以"/"分隔;
方法的签名组成:"(参数签名)" + "返回值签名"
例如Java方法long fun(int n, String str, int[] arr);
根据上面的签名规则可以得到其签名为:(ILjava/lang/String;[I)J
上面的签名分为两部分:括号里面为函数的参数,参数的内容分三部分"I","Ljava/lang/String;","[I",之间没有空格;括号外边是函数的返回类型签名。需要注意的是如果函数返回类型为void则其中返回类型签名为V;
五、动态注册函数
前面二和三都是c/c++里面方法的名称来映射函数,其实jni还为我们提供了动态注册函数的功能;
1.添加java文件 DynamicRegisterMethod.java
package com.jni; public class DynamicRegisterMethod {
static {
System.loadLibrary("dynamicregistermethod");
} public native String dynamicRegisterMethod();
}
2.添加 c 文件
#include <string.h>
#include <jni.h> #ifndef _Included_org_spring_SpringUtils
#define _Included_org_spring_SpringUtils jstring JNICALL java_dynamicRegisterMethod(JNIEnv * env, jobject obj) {
return (*env)->NewStringUTF(env, "dynamicRegisterMethod");
} static JNINativeMethod gmethods[] = { { "dynamicRegisterMethod",
"()Ljava/lang/String;", (void*) java_dynamicRegisterMethod } }; static int registerNativeMethods(JNIEnv * env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL)
return JNI_FALSE;
if (((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0)) {
return JNI_FALSE;
}
return JNI_TRUE;
} static int registerNatives(JNIEnv* env) {
if (!registerNativeMethods(env, "com/jni/DynamicRegisterMethod", gmethods,
sizeof(gmethods) / sizeof(gmethods[0]))) {
return JNI_FALSE;
}
return JNI_TRUE;
} jint JNI_OnLoad(JavaVM* vm, void* reserved) {
jint result = -1;
JNIEnv* env = NULL;
if ((*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4)) {
goto fail;
}
if (registerNatives(env) != JNI_TRUE) {
goto fail;
}
result = JNI_VERSION_1_4;
fail: return result;
} #endif
3.在Android.mk文件中进行编译的配置(省略,参考前面的例子)
4.ndk-build编译项目
DynamicRegisterMethod drm = new DynamicRegisterMethod();
Log.e(tag, drm.dynamicRegisterMethod());
执行结果:
可以看到通过动态注册方法的方式,也是成功的调用了 native 方法;
六、加入链接库
在程序开发过程中,会频繁的用到调试,方式有很多种,下面要讲的这一种是通过log打印信息来打印程序运行时的一些状态数值;
修改Android.mk文件,添加一句代码
include $(CLEAR_VARS)
LOCAL_LDLIBS += -llog //LDLIBS:连接libs,后面跟的参数为需要链接的libs,-llog表示Android中的Log库;
include $(BUILD_SHARED_LIBRARY)
加入了log库之后,即可通过c/c++文件直接打印log信息;
在c/c++中调用log打印输出信息:
#include <android/log.h>
__android_log_print(ANDROID_LOG_ERROR, "hello", "livingstone");
__android_log_print(ANDROID_LOG_DEBUG, "hello", "livingstone %d" ,23);
例子拖管地址:
https://github.com/a284628487/JniSample
Android Native jni 编程入门的更多相关文章
- Android Native jni 编程 Android.mk 文件编写
LOCAL_PATH 必须位于Android.mk文件的最开始.它是用来定位源文件的位置,$(call my-dir)的作用就是返回当前目录的路径. LOCAL_PATH := $(call my-d ...
- Android Studio JNI开发入门教程
Android Studio JNI开发入门教程 2016-08-29 14:38 3269人阅读 评论(0) 收藏 举报 分类: JNI(3) 目录(?)[+] 概述 在Andorid ...
- Android中JNI编程的那些事儿(1)
转:Android中JNI编程的那些事儿(1)http://mobile.51cto.com/android-267538.htm Android系统不允许一个纯粹使用C/C++的程序出现,它要求必须 ...
- Android手机摄像头编程入门
本讲内容:Android手机摄像头编程入门智能手机中的摄像头和普通手机中的摄像头最大的区别在于,智能机上的摄像头可以由程序员写程序控制, 做一些有趣的应用譬如,画中画,做一些有用的应用譬如二维码识别, ...
- Android jni 编程入门
本文将介绍如何使用eclipse和ndk-build来编写一个基于Android4.4版本的包含有.so动态库的安卓程序. 前提是已经安装和配置好了诸如SDK,NDK等编译环境.下面开始编程! 1 程 ...
- Android中JNI编程详解
前几天在参加腾讯模拟考的时候,腾讯出了一道关于JNI的题,具体如下: JNI本身是一个非常复杂的知识,但是其实对于腾讯的这道题而言,如果你懂JNI,那么你可能会觉得这道题非常简单,就相当于C语言中的h ...
- Android Native 程序逆向入门(一)—— Native 程序的启动流程
八月的太阳晒得黄黄的,谁说这世界不是黄金?小雀儿在树荫里打盹,孩子们在草地里打滚.八月的太阳晒得黄黄的,谁说这世界不是黄金?金黄的树林,金黄的草地,小雀们合奏着欢畅的清音:金黄的茅舍,金黄的麦屯,金黄 ...
- android studio jni调用入门
一.开发环境配置: 1.Android Studio 2.3.3 2.android-ndk-r14b-windows-x86_64 二.创建项目 1.新建android项目 2.新建文件 3.编译生 ...
- 【转】Android JNI编程—JNI基础
原文网址:http://www.jianshu.com/p/aba734d5b5cd 最近看到了很多关于热补的开源项目——Depoxed(阿里).AnFix(阿里).DynamicAPK(携程)等,它 ...
随机推荐
- ubuntu安装php常见错误集锦
一.configure 报错 1.错误类型: Configure: error: Please reinstall the libcurl distribution-easy.h should be ...
- 获取MAC地址的几种方式
.NET 后台中 using System; using System.Collections.Generic; using System.Linq; using System.Web; using ...
- 利用LruCache为GridView加载大量本地图片完整示例
MainActivity如下: package cc.testlrucache; import android.os.Bundle; import android.widget.GridView; i ...
- HDNOIP普及+提高整合
好久没有更新博客了...这几天一直在切 HDNOIP... 正式时跪惨了...所以考完后回来奋力切出了所有题. [COJ3351]HDNOIP201601回文质数 试题描述 回文数是具有对称性的自然数 ...
- Linux使用手册-vi使用手册
vi使用手册 VI是unix上最常用的文本编辑工具,作为unix软件测试人员,有必要熟练掌握它. 进入vi的命令 vi filename :打开或新建文件,并将光标置于第一行首 vi +n filen ...
- ubuntu add application to launcher
eg. add sublime text to launcher so as to be found by launcher, docky, etc. add a file sudo gedit /u ...
- 转:JQuery读写Cookie
Cookies是一种能够让网站服务器把少量数据储存到客户端的硬盘或内存,或是从客户端的硬盘读取数据的一种技术.当你浏览某网站时,你硬盘上会生产一个非常小的文本文件,它可以记录你的用户ID.密码.浏览过 ...
- 写一个迷你版Smarty模板引擎,对认识模板引擎原理非常好(附代码)
前些时间在看创智博客韩顺平的Smarty模板引擎教程,再结合自己跟李炎恢第二季开发中CMS系统写的tpl模板引擎.今天就写一个迷你版的Smarty引擎,虽然说我并没有深入分析过Smarty的源码,但是 ...
- 【GoLang】golang context channel 详解
代码示例: package main import ( "fmt" "time" "golang.org/x/net/context" ) ...
- 【GoLang】50 个 Go 开发者常犯的错误
1. { 换行: Opening Brace Can't Be Placed on a Separate Line 2. 定义未使用的变量: Unused Variables 2. import ...