Android基础进阶—JNI(上)

本文详细介绍Java通过JNI与C语言交互的过程,包括字符串、数组的传递及返回,调用Java方法,实现底层监控应用程序卸载等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JAVA通过JNI与C语言通信


【前言】在JNI的开发过程中,在所需要的头文件已经引入,语法都没有错误的情况下,Eclipse经常会报找不到所使用的JNI库中的方法等错误,一般情况下直接点击操作栏中的“锤子”按钮,对c的源文件进行编译,如果编译能够生成对应的.so文件,说明代码没有错误,而Eclipse报的错误可以不用理会,也可以直接删除这些错误,而报错的原因是Eclipse是按照C++的语法对我们的代码进行检查的,而在JNI开发中使用C++或C语言都可以进行底层的操作,只不过JNI的库中对这两种语言与Java的相互调用封装的接口有所不同,在后面的学习中会对这两种语言的JNI开发进行总结。

一、JAVA调用C中的方法传递字符串参数并返回字符串

由于c语言中没有String数据类型,当C语言的函数被java调用的传递字符串或返回字符串的时候必须要用C语言中的字符数组进行转换。

java字符串转C语言字符数组:

1. 获取字符串jstr的长度 
   jsize len = (*env)->GetStringLength(env,jstr);
2. 申请内存空间,并以char型的指针指向内存地址
   char *cstr = (char *)malloc(len+1);//C语言中以'\0'作为字符数组的结束,所以长度要+1
3. 将传递过来的字符串转换并赋值给*cstr
    //jstrjava字符串源
    //0   要获取的字符串内容的开始位置
    //len 要获取的长度
    //cstr要赋值的内存地址
   (*env)->GetStringUTFRegion(env,jstr,0,len,cstr);//将Java传递过来的字符串赋值给cstr指向的内存空间
4. 切记在函数结束前要释放内存空间
    free(cstr);
    cstr = NULL;

C语言数据转Java字符串

jstring jstr;//待返回的java字符串
char buf[] = "hello";//c语言数组
jstr = (*env)->NewStringUTF(env,buf);

C语言中调用android的日志打印函数

#include <android/log.h>//日志打印函数的头文件
#define TAG "System.out" //日志的 TAG标识 可以任意
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) //主要填充第三个参数(可变参数)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG, __VA_ARGS__)//主要填充第三个参数(可变参数)

//在需要打印的地方
LOGD("str = %s","hello");

//注意:在android中添加 连接的库文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := strtool
LOCAL_SRC_FILES := strtool.c
LOCAL_LDLIBS += -llog   #指定链接使用liblog.so库文件
include $(BUILD_SHARED_LIBRARY)

二、传递数组

C语言接收java传递的int型数组,并对数组排序后返回,由于数组传递的都是数组的首地址,所以在C语言中可以直接用一个int型的指针变量指向数组的地址,即可对数组进行操作。

//获取数组的长度
jsize len = (*env)->GetArrayLength(env,jarray);
//定义int型指针指向int型数组的首元素地址
jint *carray = (*env)->GetIntArrayElements(env,jarray,NULL);//参数三告java内存回收器,不要回收arr数组的内存,或者不要整理
内存如果回收器,回收和整理内存 ,那么我们在c中,访问的地址就可能错了

C调用JAVA中的方法

本地方法C语言要调用java中的函数,需要如下三步骤:
1. 获得类的字节码文件对象
2. 获得方法的唯一签名,获得方法签名可以用javap -s 命令获得
3. 执行方法

获得java类中方法的签名

在bin classes 目录下 执行javap -s 包名.类名获得类中方法签名


MainActivity.java中代码如下:

public class MainActivity extends Activity {

private AlertDialog dialog;
private EditText et_name;
static{
    System.loadLibrary("alipay");//加载动态库
}
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    et_name = (EditText)findViewById(R.id.et_name);
}

public void click(View v)
{
    final String str = et_name.getText().toString();
    if(TextUtils.isEmpty(str))
    {
        Toast.makeText(MainActivity.this, "不能为空",Toast.LENGTH_LONG).show();
        return;
    }
    new Thread(){
        public void run()
        {
            safepay(str);//调用C语言方法
        }
    }.start();
}
public native void safepay(String name);
public void showDialog(final String msg)//此方法会被C语言中方法调用
{
    if(dialog != null)
    {
        dialog.dismiss();
    }
    runOnUiThread(new Runnable(){//
        public void run()
        {
            AlertDialog.Builder build = new  AlertDialog.Builder(MainActivity.this);
            build.setTitle("提示");
            build.setMessage(msg);
            dialog = build.show();
        }
    });
}

public void dismiss()
{
    dialog.dismiss();
}
}

本地C语言

#include <jni.h>
#include <stdlib.h>
//注意这里第三个参数,要用C语言中的字符指针来指向传递过来的字符串
void showDialog(JNIEnv* env, jobject obj, char *jstr)
{
    //1.获得字节码文件对象
    jclass clazz = (*env)->FindClass(env,"com/pgm/c/MainActivity");//类的全路径,一定注意中间以'/'分隔
    //2.获取类中的方法签名
    jmethodID methodId = (*env)->GetMethodID(env,clazz,"showDialog","(Ljava/lang/String;)V");
    //3.执行方法 用方法返回值相对应的方法回调函数执行  比如showDialog 返回值为void 此处用 CallVoidMethod();
    jstring params =(*env)->NewStringUTF(env,jstr);//java中 showDialog方法的参数
    (*env)->CallVoidMethod(env,obj,methodId,params);
}
void dismiss(JNIEnv* env, jobject obj)
{
    //1.获得字节码文件对象
    jclass clazz = (*env)->FindClass(env,"com/pgm/c/MainActivity");//类的全路径
    //2.获取类中的方法签名
    jmethodID methodId = (*env)->GetMethodID(env,clazz,"dismiss","()V");
    //3.执行方法 用方法返回值相对应的方法回调函数执行  比如showDialog 返回值为void 此处用 CallVoidMethod();
    (*env)->CallVoidMethod(env,obj,methodId);
}
JNIEXPORT void JNICALL Java_com_pgm_c_MainActivity_safepay
  (JNIEnv *env, jobject obj, jstring jstr)
{
    jsize len = (*env)->GetArrayLength(env,jstr);//获得字符串的长度
    char *cstr = (char *)malloc(len+1);//开辟内存空间存储 字符串
    (*env)->GetStringUTFRegion(env,jstr,0,len,cstr);//存储字符串
    showDialog(env,obj,cstr);
    sleep(2);//休眠2秒
    dismiss(env,obj);
}

【疑问】在上述代码中我必须要将safepay()本地方法放在子线程调用,然后将本地方法中要调用的java方法showDialog()声明在主线程运行,才会出正确的结果;而将safepay()直接放在主线程调用,程序没有任何反应也不会包任何错误。此处不堪甚解,看来JNI开发的多线程相互调用,还有待探索。

开发模式


  • 只有一个android工程师,jni流程,本地c程序
  • 只有一个android工程师,有.so库,头文件,文档
  • 有android,有c工程师,把c工程师提供c函数包装调用

  1. 把c工程师提供.c和.h文件拷贝到工程的jni文件夹下
  2. 在jni源文件中包含.h头文件,调用基中c工程师实现的函数
  3. 在Android.mk文件中LOCALSRCFILES 需要指定编译c工程师提供.c源文件

JNI实现底层监控应用程序被卸载


基本原理: 在本地方法中对当前进程进行克隆,建立一个子进程,就算父进程被杀死或关闭,子进程在正常情况下一直会运行,除非用户关机。应用程序安装到手机上后都会在data/data目录下创建该应用程序的私有空间(文件夹),在应用程序被用户卸载后,系统会删除对应的文件夹,所以我们可以通过子进程来检查该文件夹的存在与否判断我们的引用是否被用户卸载,从而做出相应的后续操作。

本地C源代码

#include <jni.h>
#include <sys/types.h>
#include <unistd.h>//注意要添加此头文件 否则 fork() access()等方法无法无法调用
JNIEXPORT void JNICALL Java_com_example_trace_MainActivity_createChildProgress
  (JNIEnv *env, jobject obj)
{
    int pid = fork();//创建子进程
    if(pid  < 0)
    {
        exit(-1);//直接退出
    }else if(pid == 0)//子进程满足条件
    {
        while(1)
        {
            if(access("/data/data/com.example.trace",F_OK) < 0)//检查文件是否存在
            {   //弹出网页 实际是想虚拟机发出am命令   --user 0 管理员权限
                system("am start --user 0 -a android.intent.action.VIEW -d http://www.baidu.com/");
                break;
            }
        }
    }
    else if(pid > 0)//父进程满足条件
    {
            return;
    }
    return;
}

JNI开发中调用C++与C的区别


  • 1.源文件类型不同,.cpp,.c
  • 2.调用JNIEnv对应函数方法,c++ :

    //c++通过JNIEnv调用函数 env是一个一级结构体指针,比c调用少第一个参数env
    env->NewStringUTF("Hello From C++!");
    
    //c调用方法
    (*env)->NewStringUTF(env, buf);
    
  • 3.c++编写jni程序需要包含javah生成头文件

     #include "com_itheima_hellofromcpp_MainActivity.h" //

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值