JNI入门


介绍

JNI含义:java native interface

JNI好处:

1、JNI可以扩展java虚拟机的能力,让java代码可以调用驱动

2、C/C++的效率要高,通过jni把耗时操作方法C/C++可以提高java运行效率

3、java代码编译成的.class文件安全性较差, 可以通过jni把重要的业务逻辑放到c/c++去实现,c/c++反编译比较困难,安全性较高.

C基本语法

1、C的基本数据类型

java基本数据类型:boolean(1个字节),byte(1),char(2),short(2),int(4),long(8),float(4),double(8)

C的基本数据类型:char(1个字节), int(4), float(4), double(8), long(4), short(2), signed, unsigned, void

signed:有符号数,可以表示负数;unsigned:无符号数,不可以表示负数。signed和unsigned只能用来修饰整形变量char、short、int、long。

C没有boolean和byte,C用0和非0表示false和true。

2、占位符和字符串

占位符:

%d  -  int
%ld – long int
%lld - long long
%hd – short
%c  - char
%f -  float
%lf – double
%u – 无符号数
%x – 十六进制输出 int 或者long int 或者short int
%o -  八进制输出
%s – 字符串

占位符不要乱用,要选择正确的对应类型,否则可能会损失精度。

C没有String类型,C的字符串实际就是字符数组。C字符串两种定义方式:

//注意'\0'字符串结束符, []只能再变量名之后
char str[] = {'q','w','e','r','t','\0'};

//这种定义方式不用写结束符,可以表示汉字
char str[] = "你好"; 

C字符串不检查下标越界,使用时要注意。

C的控制台输入

scanf("占位符", &地址);//& 取地址符

3、内存地址的概念

声明一个变量,就会立即为这个变量申请内存,一定会有一个对应的内存地址;没有地址的内存是无法使用的;内存的每一个字节都有一个对应的地址;内存地址用一个16进制数来表示;32位操作系统最大可以支持4G内存。

4、指针

int i = 123;
//一般计算机中用16进制数来表示一个内存地址 
printf("%#x\n",&i); 
//int* int类型的指针变量  pointer指针  指针变量只能用来保存内存地址
//用取地址符&i 把变量i的地址取出来 用指针变量pointer 保存了起来
//此时我们可以说 指针pointer指向了 i的地址 
int* pointer = &i;   
printf("pointer的值 = %#x\n",pointer);
printf("*pointer的值%d\n",*pointer);
*pointer = 456;
printf("i的值是%d\n",i);
system("pause"); 

未赋值的指针称为野指针。

5、指针交换两个数的值

所有传递其实本质都是值传递,引用传递本质是把地址传递过去,引用传递其实也是传递一个值,但是这个值是一个内存地址。

void swap(int* p, int* p2){
    int temp = *p;
    *p = *p2;
    *p2 = temp;	
}

6、数组和指针的关系

数组占用的内存空间是连续的

数组变量保存的地址,是第0个元素地址,也就是首地址。&array&array[0]值一样。

*(p + 1):指针位移一个单位,一个单位是多少个字节,取决于指针的类型。

7、指针的长度

不管变量的类型是什么,它的内存地址的长度一定是相同的;类型不同只决定变量占用的内存空间不同;32位环境下,内存地址长度都是4个字节,64位环境下,内存地址长度都是8个字节。

8、多级指针

二级指针变量只能保存一级指针变量的地址,有几个*就是几级指针

int i = 123;
//int类型一级指针 
int* p = &i;
//int 类型 二级指针 二级指针只能保存一级指针的地址 
int** p2 = &p;
//int 类型 三级指针  三级指针只能保存二级指针的地址 
int*** p3 = &p2;
//通过p3 取出 i的值
printf("***p3 = %d\n", ***p3);

9、堆、栈、静态内存、动态内存

栈内存:系统自动分配,系统自动销毁,连续的内存区域,向低地址扩展,大小固定,栈上分配的内存称为静态内存。

静态内存分配:子函数执行完,子函数中的所有局部变量都会被销毁,内存释放,但内存地址不可能被销毁,只是地址上的值没了。

堆内存:程序员手动分配(java:new,c:malloc),空间不连续,大小取决于系统的虚拟内存,C程序员手动回收free,java自动回收,堆上分配的内存称为动态内存。

#include<stdio.h>    
#include<stdlib.h>    
main(){ 
   printf("请输入班级的人数:");
   int count;
   scanf("%d",&count);
   //申请一块堆内存
   int* pointer = malloc(sizeof(int)*count);
   int i;
   for(i = 0;i<count;i++){
         printf("请输入第%d个学生的学号:",i+1);
         scanf("%d", pointer+i);
         }  
   for(i = 0;i<count;i++){
         printf("第%d个学生的学号是:%d\n",i+1,*(pointer+i));  
         } 
   printf("请输入插班生的人数:");
   //声明一个变量increment用来保存 插班生的人数 
   int increment;
   //接受用户的输入 
   scanf("%d",&increment);
   //重新申请一块足够大的内存 
   //如果 malloc申请到的内存后面还有足够的空间 realloc会在malloc申请的内存空间后继续申请足够大的内存空间
   //如果 malloc申请到的内存后面没有足够的空间 realloc会找到一块足够大的堆内存 并且把 malloc申请到的内存中的值复制过来 
  pointer = realloc(pointer,sizeof(int)*(count+increment));
  for(i = count;i<count+increment;i++){
         printf("请输入第%d个学生的学号:",i+1);
         scanf("%d", pointer+i);
        }
  for(i = count;i<count+increment;i++){
        printf("第%d个学生的学号是:%d\n",i+1,*(pointer+i));  
        }
   system("pause"); 
} 

10、结构体

结构体的大小大于等于结构体中每一变量的占字节数的和;结构体的大小是最大的那个变量所占字节数的整数倍。

c结构体类似java的class。struct来声明c的结构体。C结构体中不能定义函数,可以定义函数指针 。

#include<stdio.h>    
#include<stdlib.h>    
void study(){
           printf("good good study!\n");
           }
typedef struct Student{
      int age;  //8
      int score;  // 4
      char sex;   //1
      void(*studypointer)();
      } stud;
main(){    
 stud stu = {18,100,'f'};
 stu.studypointer = &study;
 stu.studypointer();
 struct Student* stuPointer = &stu;
 printf("*stuPointer.age = %d\n",(*stuPointer).age);
 (*stuPointer).sex ='m';
 printf("stu.sex = %c\n",stu.sex);
 printf("stuPointer->age = %d",stuPointer->age);
 //printf("stu.age = %hd\n",stu.age);
 //printf("stu.score = %d\n",stu.score);
 //printf("stu.sex = %c\n",stu.sex);
 // printf("结构体student占%d个字节\n",sizeof(stu));
       system("pause"); 
} 

11、联合体

union长度等于联合体中定义的变量当中最长的那个,联合体只能保存一个变量的值,联合体共用同一块内存

c++

c++开发jni代码时 env不再是结构体Jninativeinterface的二级指针。_JNIEnv是C++的结构体,C++的结构体可以定义函数。envJNIEnv的一级指针,也就是结构体_JNIEnv的一级指针。env->来调用结构体里的函数。

am命令

am命令:在adb shell里可以通过am命令进行一些操作如启动activity Service 启动浏览器等等。

#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <android/log.h>
    #define LOG_TAG "System.out"
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
int ppid;
JNIEXPORT void JNICALL Java_com__cfork_demo_MainActivity_cfork
  (JNIEnv * env, jobject obj){
    int pid = fork();
    //fork成功的分叉出一个子进程 会返回当前进程的id 但是只能在主进程中fork成功
    //在子进程中运行fork 会返回0 但是不能再分叉出新的进程
    //fork的返回值可能三种  >0  == 0 <0
    FILE* file;
    if(pid>0){
        LOGD("pid = %d",pid);

    }else if(pid == 0){
        //拿到父进程的进程编号

        LOGD("pid == 0");
        while(1){
            ppid = getppid();
            //如果父进程的进程编号为1 说明父进程被杀死了
            if(ppid == 1){
                LOGD("ppid =%d",ppid);
                file = fopen("/data/data/com.cfork.demo","r");
                if(file == NULL){
                    //打开网页 调用am命令
                    execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
                }else{
                    execlp("am", "am", "start", "--user","0", "-n" , "com.cfork.demo/com.cfork.demo.MainActivity",(char *) NULL);

                }
                break;
            }
                    LOGD("sub process is running");
                    sleep(2);
        }
    }else{
        LOGD("pid<0 ");
    }

}

JNI

c本地函数命名规则:Java_包名_类名_本地方法名

参数jobject thiz:调用本地函数的java对象,在这个例子中,就是MainActivity的实例。

参数JNIEnv* env:是结构体JNINativeInterface的二级指针。JNIEnv是结构体JNINativeInterface的一级指针,操作一级指针用(*env)->。JNINativeInterface结构体中定义了大量的函数指针,这些函数指针在jni开发中很常用。

C代码

#include <stdlib.h>
#include <stdio.h>
#include <jni.h>

jstring Java_com_vcredit_cdemo_MainActivity_stringFromJNI(JNIEnv* env,jobject thiz){
    char* cstr = "hello from c!";
    return (*env)->NewStringUTF(env,cstr);
}

C++代码

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_vcredit_cdemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

java传参数给c

Java给C传递intStringint[],并且返回:

#include <jni.h>
#include <stdlib.h>
#include <string.h>

//传递数字,并返回数字	
JNIEXPORT jint JNICALL
Java_com_vcredit_cdemo_MainActivity_intFromJNI(JNIEnv *env, jobject thiz, jint a, jint b) {
    return a + b;
}

char *_JString2CStr(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = (*env)->FindClass(env, "java/lang/String");
    jstring strencode = (*env)->NewStringUTF(env, "GB2312");
    jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray) (*env)->CallObjectMethod(env, jstr, mid,
                                                            strencode); // String .getByte("GB2312");
    jsize alen = (*env)->GetArrayLength(env, barr);
    jbyte *ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char *) malloc(alen + 1); //"\0"
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    (*env)->ReleaseByteArrayElements(env, barr, ba, 0);
    return rtn;
}

//传递字符串,并返回字符串
JNIEXPORT jstring JNICALL
Java_com_vcredit_cdemo_MainActivity_stringChange(JNIEnv *env, jobject thiz, jstring string) {
    char *cstr = _JString2CStr(env, string);
    int length = strlen(cstr);
    int i;
    for (i = 0; i < length; i++) {
        *(cstr + i) += 1;
    }
    return (*env)->NewStringUTF(env, cstr);
}

//传递int数组,并返回数组
JNIEXPORT jintArray JNICALL
Java_com_vcredit_cdemo_MainActivity_arrIncrease(JNIEnv *env, jobject thiz, jintArray jArray) {
    jsize length = (*env)->GetArrayLength(env, jArray);
    jint *arrayPointer = (*env)->GetIntArrayElements(env, jArray, NULL);
    int i;
    for (i = 0; i < length; i++) {
        *(arrayPointer + i) += 10;
    }
    (*env)->SetIntArrayRegion(env, jArray, 0, length, arrayPointer);
    return jArray;
}

在C中打印log

#include <android/log.h>
#define LOG_TAG "System.out"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

//使用
LOGD("length = %d",length);

c调用Java

C调用Java空参方法

JNIEXPORT void JNICALL
Java_com_vcredit_cdemo_MainActivity_callbackvoidmethod(JNIEnv* env, jobject thiz) {
    jclass claz = (*env)->FindClass(env, "com/vcredit/cdemo/MainActivity");
    jmethodID methodID = (*env)->GetMethodID(env, claz, "helloFromJava", "()V");
    (*env)->CallVoidMethod(env, thiz, methodID);
}

public void helloFromJava() {
    Log.d("-----rrrrr", "hello from java");
}

注意FindClass路径用/

C调用Java有Int参数方法并返回

JNIEXPORT void JNICALL
Java_com_vcredit_cdemo_MainActivity_callbackintmethod(JNIEnv *env, jobject thiz) {
    jclass claz = (*env)->FindClass(env, "com/vcredit/cdemo/MainActivity");
    jmethodID methodID = (*env)->GetMethodID(env, claz, "add", "(II)I");
    int result = (*env)->CallIntMethod(env, thiz, methodID, 6, 6);
    LOGD("result = %d", result);
}

public int add(int x, int y) {
    return x + y;
}

C调用Java有String参数方法

JNIEXPORT void JNICALL
Java_com_vcredit_cdemo_MainActivity_callbackStringmethod(JNIEnv *env, jobject thiz) {
    jclass claz = (*env)->FindClass(env, "com/vcredit/cdemo/MainActivity");
    jmethodID methodID = (*env)->GetMethodID(env, claz, "printString", "(Ljava/lang/String;)V");
    jstring result = (*env)->NewStringUTF(env, "hello from c");
    (*env)->CallVoidMethod(env, thiz, methodID, result);
}

public void printString(String s) {
    Log.d("-----rrrrr", s);
}

文章作者:
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 !
  目录