jni备忘:jni调用java方法显示一个Dialog
写在前面
之前研究过jni,发现一段时间不用就基本忘干净了,现在在这里通过一个简单的例子记录一下。
完整代码下载
1、环境搭建
开发ndk需要下载ndk的开发包,我下载的是android-ndk32-r10-windows-x86.zip,这个版本中自带了c的编译链,可以不使用cygwin。
将ndk解压到一个目录以后,文件夹中有一个批处理文件ndk-build.cmd,在jni文件夹所在的目录执行这个命令就能进行编译。
为了方便敲命令,把ndk文件夹的根目录加入到环境变量中。
2、编写java代码
例子期望的效果是,显示一个普通的Dialog,有标题,有提示信息,有按钮。
首先需要开发java的代码,把ndk中的例子HelloJni代码稍微改动一下:
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
public class HelloJni extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.VERTICAL);
Button btn = null;
btn = new Button(this);
btn.setText("show Dialog by jni");
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
test1(HelloJni.this);
}
});
ll.addView(btn);
btn = new Button(this);
btn.setText("show Dialog by java");
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Context context = HelloJni.this;
new AlertDialog.Builder(context).setTitle("标题")
.setMessage("提示信息").setPositiveButton("确定", null)
.show();
}
});
ll.addView(btn);
setContentView(ll);
}
public native void test1(Context context);
static {
System.loadLibrary("test_jni");
}
}
最后一句”System.loadLibrary(“test_jni”);”是加载so库的代码
3、生成jni头文件
在上面的java代码中声明了一个native函数,这个native函数就是需要在jni中实现的接口函数。
jni编写遵循固定的格式,详细查看:https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html
为了方便,可以使用javah命令生成对应的头文件,这样就不怕写错了。
命令为
javah com.example.hellojni.HelloJni
执行命令以后报错:
D:\android\worksapce\TestJni\src>javah com.example.hellojni.HelloJni
错误: 无法确定Context的签名
这个错误是由于Context不能被jdk环境识别造成的,可以先将Context更改为Object,生成头文件以后再改回来即可。
生成的头文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_hellojni_HelloJni */
#ifndef _Included_com_example_hellojni_HelloJni
#define _Included_com_example_hellojni_HelloJni
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_hellojni_HelloJni
* Method: test1
* Signature: (Ljava/lang/Object;)V
*/
JNIEXPORT void JNICALL Java_com_example_hellojni_HelloJni_test1
(JNIEnv *, jobject, jobject);
#ifdef __cplusplus
}
#endif
#endif
一个小知识点:如果声明的native方法存在下划线的话,用转义字符”_1”代替。比如set_name,应该写成set_1name。
4、编写jni实现
在Android项目的根目录创建文件夹”jni”,新建test_jni.cpp文件
编写的jni代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <jni.h>
#include <com_example_hellojni_HelloJni.h>
//#include <com_hengbao_trustedenv_ccbhce_TuiAPI.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_hellojni_HelloJni
* Method: test1
* Signature: (Ljava/lang/Object;)V
*/
JNIEXPORT void JNICALL Java_com_example_hellojni_HelloJni_test1
(JNIEnv * env, jobject obj, jobject context) {
/*
new AlertDialog.Builder(context)
.setTitle("标题")
.setMessage("提示信息")
.setPositiveButton("确定", null)
.show();
*/
jmethodID jmethod_tem;
// AlertDialog.Builder builder = new AlertDialog.Builder(context);
jclass jclass_AlertDialogBuilder = env->FindClass("android/app/AlertDialog$Builder");
jmethod_tem = env->GetMethodID(jclass_AlertDialogBuilder, "<init>", "(Landroid/content/Context;)V");
jobject jobject_AlertDialogBuilder = env->NewObject(jclass_AlertDialogBuilder, jmethod_tem, context);
// builder.setTitle("text"); public Builder setTitle(java.lang.CharSequence message)
jmethod_tem = env->GetMethodID(jclass_AlertDialogBuilder, "setTitle",
"(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;");
env->CallObjectMethod(jobject_AlertDialogBuilder, jmethod_tem, env->NewStringUTF("标题"));
// builder.setMessage("text"); public Builder setMessage(CharSequence message)
jmethod_tem = env->GetMethodID(jclass_AlertDialogBuilder, "setMessage",
"(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;");
env->CallObjectMethod(jobject_AlertDialogBuilder, jmethod_tem, env->NewStringUTF("提示信息"));
// builder..setPositiveButton("确定", null);
// public Builder setPositiveButton(CharSequence text, final android.content.DialogInterface.OnClickListener listener)
jmethod_tem = env->GetMethodID(jclass_AlertDialogBuilder, "setPositiveButton",
"(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;");
env->CallObjectMethod(jobject_AlertDialogBuilder, jmethod_tem, env->NewStringUTF("确定"), NULL);
// builder.show(); public AlertDialog show()
jmethod_tem = env->GetMethodID(jclass_AlertDialogBuilder, "show", "()Landroid/app/AlertDialog;");
env->CallObjectMethod(jobject_AlertDialogBuilder, jmethod_tem);
}
#ifdef __cplusplus
}
#endif
编写中总结的知识点:
1、调用一个类的构造方法,方法的名称填写””
2、如果是内部类的话,应该用$分割,比如”android/app/AlertDialog$Builder”
3、使用c和c++开发的话,jni的函数调用形式略有不同,c中调用的话是”(*env)->FindClass(env, “xxxx”);”,而c++中是”env->FindClass(“xxxx”);”
4、写方法签名时,如果是类对象的话,用”Ljava/lang/String;”的形式,分号不能省。
如果是基本类型的话,根据规则用”Z”表示boolean等
有关方法的签名详细写法参见:Type Signatures
5、编写代码中发现,jni的代码方法签名很容易出错,使用javap可以查看java类中所有方法的签名,不过必须是对编译后的class文件进行操作,详见利用javap生成方法签名
5、编译运行
编译文件时需要额外两个配置文件:Android.mk和Application.mk。
Application.mk描述要生成so库的对应cpu指令集,默认为全部,即armeabi、x86等,这里指定只生成armeabi。
APP_ABI := armeabi
Android.mk负责描述编译时的信息。
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
#编译的文件,不包括.h头文件
LOCAL_SRC_FILES := test_jni.cpp
#将inc文件夹整个包含进来,这样导头文件就可以直接引用,不用带文件夹名字inc/xxx.h
LOCAL_C_INCLUDES:= inc
LOCAL_MODULE := test_jni
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
接下来启动一个cmd命令窗口,cd到jni文件夹目录下,执行命令:
ndk-build
执行结果如下:
D:\android\worksapce\TestJni\jni>ndk-build
[armeabi] Compile++ thumb: test_jni <= test_jni.cpp
[armeabi] SharedLibrary : libtest_jni.so
[armeabi] Install : libtest_jni.so => libs/armeabi/libtest_jni.so
运行结果:
6、最后
在上面的例子中,Dialog的确定按钮并没有设置点击事件,如果要在jni中设置点击事件的话,估计需要在jni中定义一个c++的类,继承DialogInterface.OnClickListener才能做到,现在先空着。
完整代码下载