Eclipse中使用JNI/NDK实现C代码调用Java方法

概述

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: ()V

  protected void onCreate(android.os.Bundle);
    descriptor: (Landroid/os/Bundle;)V

  public void click(android.view.View);
    descriptor: (Landroid/view/View;)V

  public native void helloC();
    descriptor: ()V

  public 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会变慢;

源代码:https://github.com/lz-ang/CCallJava 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ang_qq_252390816

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值