概述
C调用Java方法一般是通过反射来实现的,和Java中的反射相似主要分为三个步骤:
1,通过反射拿到字节码对象
2,获取方法的方法ID
3,通过反射调用Java方法
开发环境
Eclipse + ADT + AndroidSDK + NDK
注意NDK版本不要太高,我用的是NDK r10e
创建项目
创建Android项目:项目名为CCallJava,主要是流程通过点击button调用C函数,C中再调用Java方法显示一个对话框;
配置NDK
Eclipse的window选项中选择Preferences,点击左边Android展开子选项,点击NDK,在右边NDK location中输入自己的NDK路径,或者点击Browser选择找到NDK根路径点击确认;
自动生成jni文件夹
配置之后可以右键项目选在Android Tools —>Add Native Support ...输入编译要生成动态库的名称也是生成的c++文件的名称例如:hello,点击finish;自动生成jni目录以及默认C++文件和Android.mk文件;这种方式新建之后不能删除jni,不然就会报错;
Android.mk文件内容,前两行和最后一行一般不用修改;
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= hello #动态库名称,Java代码中也是根据这个名称加载动态库
LOCAL_SRC_FILES:= hello.c #C文件,里面就是我们写的C代码。自动生成的是hello.cpp这个改成了C文件
include $(BUILD_SHARED_LIBRARY)#生成.so动态库
#include $(BUILD_STATIC_LIBRARY) 编译出.a的静态库
#注意注释用#开头
关联NDK源码
右键项目—>properties—>C C++General—>Path and Symbols—>Includes—>add —>File System...选择ndk源码路径,例如r10版本:android-ndk-r10e\platforms\android-19\arch-arm\usr\include (注意不同ndk版本include源码不一样,比如ndk r17是ndk_r17\sysroot\usr\include)
新建本地方法
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void click(View v){
helloC();//调用本地方法
}
//本地方法
public native void helloC();
//C中调用的方法
public void show(String msg){
Builder builder = new Builder(this); //注意导包是android.app.AlertDialog.Builder;
builder.setTitle("提示!");
builder.setMessage(msg);
builder.show();
}
}
自动生成头文件
通过javah命令生成本地方法对应的头文件;
cmd命令中 cd 切换到项目的src目录下,运行javah + 本地方所在类的全路径名;
C:\Users\Ang>cd D:\EProject\CCallJava\src
C:\Users\Ang>d:
D:\EProject\CCallJava\src>javah com.example.ccalljava.MainActivity
D:\EProject\CCallJava\src>
在src目录下就自动生成一个com_example_ccalljava_MainActivity.h头文件
#include <jni.h>
/* Header for class com_example_ccalljava_MainActivity */
#ifndef _Included_com_example_ccalljava_MainActivity
#define _Included_com_example_ccalljava_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_ccalljava_MainActivity
* Method: helloC
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_example_ccalljava_MainActivity_helloC
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
实现头文件的中函数
复制头文件中的函数到jni下的C文件中,并实现这个函数;
hello.c
#include <jni.h>
//env是个二级指针,JNIEnvb本来就是一个指针
JNIEXPORT void JNICALL Java_com_example_ccalljava_MainActivity_helloC//函数名:Java包名+类名+方法名
(JNIEnv* env, jobject obj)//这两个参数必须要写,JNIEvn* 是java运行环境的地址,可以理解为Java虚拟机地址;jobject Java中调用本地方的对象
{
//jclass (*FindClass)(JNIEnv*, const char*); jni.h中定义的函数指针
jclass class = (*env)->FindClass(env,"com/example/ccalljava/MainActivity"); //是不是和MainActivity.class.forName(classNmae);类似//(*env)->FindClass表示的逻辑是调用*env结构体中的FindClass函数
//jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);//第三个参数是要反射的方法名;最后一个参数char*是要反射调用的方法的签名
jmethodID methodID = (*env)->GetMethodID(env,class,"show","(Ljava/lang/String;)V");
// void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); 因为show返回值是void类型的所以这里调用CallVoidMethod()函数
(*env)->CallVoidMethod(env,obj,methodID,(*env)->NewStringUTF(env,"C调用Java方法显示的弹窗"));
//jstring (*NewStringUTF)(JNIEnv*, const char*);把C中的字符串转化为Java中的字符串;C中的字符串是个char*指针或者说char[];
}
注意:FindClass函数第二个参数com/example/ccalljava/MainActivity是斜杠作分割,不是点"."
获取方法签名
cmd 命令行中 cd 切换到项目的bin\classes 下,运行javap -s + 要签名方法所在类的全路径名;
C:\Users\Ang>cd D:\EProject\CCallJava\bin\classes\C:\Users\Ang>d:
D:\EProject\CCallJava\bin\classes>javap -s com.example.ccalljava.MainActivity //执行此命令
Compiled from "MainActivity.java"
public class com.example.ccalljava.MainActivity extends android.app.Activity {
public com.example.ccalljava.MainActivity();
descriptor: ()Vprotected void onCreate(android.os.Bundle);
descriptor: (Landroid/os/Bundle;)Vpublic void click(android.view.View);
descriptor: (Landroid/view/View;)Vpublic native void helloC();
descriptor: ()Vpublic void show(java.lang.String);
descriptor: (Ljava/lang/String;)V //show方法的签名
注意:C函数调用Java方法传入参数中有中文会报错,因为Eclipse创建的hello.c文件默认的时GBK编码,而Android使用的UTF-8,码表不一致导致的;修改hello.c默认的码表为UTF-8就OK了;
编译生成.so动态库
cmd 命令行 cd切换到jni上级目录或者jni目录下,输入ndk-build 注意:要配置ndk-build.cmd的环境变量,才能使用ndk-bulid命令;
C:\Users\Ang>cd D:\EProject\CCallJava
C:\Users\Ang>d:
D:\EProject\CCallJava>ndk-build
Android NDK: Found platform level in ./project.properties. Setting APP_PLATFORM to android-19.
D:/SDK/ndk-r16b-windows-x86_64/build//../build/core/setup-app.mk:81: Android NDK: Application targets deprecated ABI(s): armeabi
D:/SDK/ndk-r16b-windows-x86_64/build//../build/core/setup-app.mk:82: Android NDK: Support for these ABIs will be removed in a future NDK release.
[armeabi-v7a] Install : libhello.so => libs/armeabi-v7a/libhello.so
[armeabi] Install : libhello.so => libs/armeabi/libhello.so
[x86] Install : libhello.so => libs/x86/libhello.so
注意:如果不在Application.mk文件中指定输出CPU ABI架构的.so,会根据默认的输出对应的ABI架构.so库,一般是所有的ABI架构的.so库;
指定输出对应的ABI架构的.so库
Application.mk
APP_ABI := armeabi-v7a armeabi x86
使用.so库
在Java代码加载.so动态库
MainActivity中加载hello.so库
static{
System.loadLibrary("hello");
}
自动编译.so库
不用在命令行中输入ndk-build命令,自动完成编译输出.so库
自建Builder方法
右键项目选择properties—>Builders—>New...—>Program—>OK
在Edit Configuration 中 Name 输入Builders的名字,Location输入ndk-build.cmd的路径,Working Directory输入当前项目${workspace_loc}在Variables...中可以找到;
Refresh中勾选 Refresh resources upon completion.
Build Options
勾选Specify working set of relevant resources 接着点击Specify Resources...
选择当前项目的jni
这样每次运行项目的时候都会重新自动生成.so库,所以打包apk会变慢;