Android Studio中JNI NDK开发(二)

#android下JNI开发 ##what 什么是JNI

  • JNI java native interface native本地 java本地接口
  • 通过JNI可以实现java和本地代码之间相互调用
  • jni可以看做是翻译 实际上就是一套协议

why 为什么要用JNI

  • Java 一处编译到处运行
    • ①java运行在虚拟机上 JNI可以扩展java虚拟机的能力 让java代码可以调用驱动
    • ②java是解释型语言 运行效率相对较低 C/C的效率要高很多 通过jni把耗时操作方法C/C可以提高java运行效率
    • ③ java代码编译成的.class 文件安全性较差, 可以通过jni 把重要的业务逻辑放到c/c去实现,c/c反编译比较困难 安全性较高
  • C历史悠久 1972年C 通过JNI可以调用优秀的C开源类库

##How 怎么用JNI

  • java
  • c/c++ 能看懂 会调用
  • JNI开发流程 NDK native develop kit

##C基本语法

CHelloWorld

#include<stdio.h>    // 相当于 java的import .h c的头文件 stdio.h standard io 标准输入输出 
#include<stdlib.h>   // stdlib standard library 标准函数库    java.lang 
/**
*/
main(){    // public static void main(String[] args)
       printf("helloworld!\n");  //System.out.println();   "\n"换行符 
       system("javac Hello.java");
       system("java Hello");
       system("notepad");
       system("pause"); //system执行windows的系统命令 
       }

C的基本数据类型

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

char, int, float, double, long, short, signed, unsigned, void

  • signed 有符号数 最高位是符号位 可以表示负数 但是表示的最大值相对要小
  • unsigned 无符号数 最高位是数值位 不可以表示负数 表示的最大值相对要大
  • signed unsigned 只能用来修饰整形变量 char short int long
  • C没有 boolean byte C用0和非0表示false true

C的输出函数

%d  -  int
%ld – long int
%lld - long long
%hd – 短整型
%c  - char
%f -  float
%lf – double
%u – 无符号数
%x – 十六进制输出 int 或者long int 或者short int
%o -  八进制输出
%s – 字符串
  • 占位符不要乱用 要选择正确的对应类型 否则可能会损失精度
  • C字符串
    • C没有String类型 C的字符串实际就是字符数组

    • C数组定义 [ ]只能再变量名之后

    • C字符串两种定义方式

      char str[] = {'h','e','l','l','o','\0'};//注意'\0'字符串结束符
      char str[] = "你好"; //这种定义方式不用写结束符 可以表示汉字

C的输入函数

  • scanf("占位符", &地址);
  • & 取地址符
  • C字符串不检查下标越界 使用时要注意

内存地址的概念

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

指针入门 ******

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");
  • 指针常见错误
    • 声明了指针变量后 未初始化直接通过*p 进行赋值操作 运行时会报错
        • 未赋值的指针称为野指针
    • 指针类型错误 如int* p 指向了double类型的地址, 通过指针进行读取操作时,读取值会出错

指针的练习

  • 值传递和引用传递(交换两个数的值)
    • 引用传递本质是把地址传递过去

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

      void swap(int* p, int* p2){
      	int temp = *p;
      	*p = *p2;
      	*p2 = temp;	
      }
      main(){
      	int i = 123;
      	int j = 456;
      	//将i, j的地址传递过去
      	swap(&i,&j);
      	printf("i = %d, j = %d", i, j);
      }
  • 返回多个值
    • 把地址作为参数传入函数中,当函数执行完毕时,参数的值就已经被修改了

多级指针

  • int* p; int 类型的一级指针 int** p2; int 类型的二级指针

  • 二级指针变量只能保存一级指针变量的地址

  • 有几个* 就是几级指针 int*** 三级指针

  • 通过int类型三级指针 操作int类型变量的值 ***p

    int i = 123;
    	//int类型一级指针 
    	int* p = &i;
    	//int 类型 二级指针 二级指针只能保存一级指针的地址 
    	int** p2 = &p;
    	//int 类型 三级指针  三级指针只能保存二级指针的地址 
    	int*** p3 = &p2;
    	//通过p3 取出 i的值
    	printf("***p3 = %d\n", ***p3);
  • 多级指针案例 取出子函数中临时变量的地址

数组和指针的关系

  • 数组占用的内存空间是连续的
  • 数组变量保存的是第0个元素地址,也就是首地址
  • *(p + 1):指针位移一个单位,一个单位是多少个字节,取决于指针的类型 ###指针的长度
  • 不管变量的类型是什么,它的内存地址的长度一定是相同的
  • 类型不同只决定变量占用的内存空间不同
  • 32位环境下,内存地址长度都是4个字节,所以指针变量长度只需4个字节即可
  • 区分指针类型是为了指针位移运算方便 ##堆栈概念 静态内存分配 动态内存分配
  • 栈内存
    • 系统自动分配
    • 系统自动销毁
    • 连续的内存区域
    • 向低地址扩展
    • 大小固定
    • 栈上分配的内存称为静态内存
  • 静态内存分配
  • 子函数执行完,子函数中的所有局部变量都会被销毁,内存释放,但内存地址不可能被销毁,只是地址上的值没了
  • 堆内存
    • 程序员手动分配
      • java:new
      • c:malloc
    • 空间不连续
    • 大小取决于系统的虚拟内存
    • C:程序员手动回收free
    • java:自动回收
    • 堆上分配的内存称为动态内存

结构体

  • 结构体中的属性长度会被自动补齐,这是为了方便指针位移运算
  • 结构体中不能定义函数,可以定义函数指针
  • 程序运行时,函数也是保存在内存中的,也有一个地址
  • 结构体中只能定义变量
  • 函数指针其实也是变量,它是指针变量
  • 函数指针的定义 返回值类型(*变量名)(接收的参数);
  • 函数指针的赋值: 函数指针只能指向跟它返回值和接收的参数相同的函数

联合体

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

Hello Jni

  • 第一步:定义本地方法 public native String helloFromC();
  • 第二步:根目录建立jni文件夹(注意jni最好在app目录下m与src同级,不然有可能造成so文件加载出问题,eclipse环境除外),新建hello.c
#include<stdio.h>
#include<stdlib.h>
#include<jni.h>
//Java_包名_类名_本地方法名
//jobject 调用本地函数的java对象,本例中就是MainActivity
//JNIEnv* 是结构体JNINativeInterface的二级指针
//JNINativeInterface结构体中定义了大量的函数指针,这些函数指针在jni开发中常用
//(*env)->可以调用函数指针
//前面两个参数固定,如果本地方法中有参数,接在后面(JNIEnv* env,jobject     thiz,jint i)
jstring
Java_com_cjf_hellojni_MainActivity_helloFromC(JNIEnv* env,jobject thiz){
   char* cstr="hello from c";
    //在jni.h中可以找到类型对应的方法
    return (*env)->NewStringUTF(env,cstr);
}
  • 第三步:建立Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    :=hello
LOCAL_SRC_FILES := hello.c
include $(BUILD_SHARED_LIBRARY)
  • 第四步:在cmd或者Terminal中ndk-build,注意一定要进入jni文件夹所在的目录
sourceSets {
    main {
        // 1. 配置在根目录libs下可以加载第三方so库, (最好不要创建jniLibs, 在众多的开源库中可能会引起冲突,还没发现)
        // 2. 运行时会自动将libs目录下的so库拷贝到指定目录
        // 3. 如果自己创建的so不需要重新编译,可以将(app/build/intermediates/transforms)生成的so拷贝到这个目录
        jniLibs.srcDirs = ['libs'] //注意libs要和src同级
        // 如果是单个文件夹 可以直接这样如下配置
        // jniLibs.srcDir 'libs'
    }    }
  • 第五步: System.loadLibrary("hello");

简化流程

  • 在高版本的android studio中不需要Android.mk文件,可直接在gradle.build中配置
  • \app\src\main下建立jni目录,新建.c文件,用javah 编译java文件可生成.h文件
#include "com_cjf_ndktest_NDKTest.h" //引入上面生成的.h,.h中引入jni.h
JNIEXPORT jstring JNICALL Java_com_cjf_ndktest_NDKTest_hello(JNIEnv *env,     jobject obj)
{
return (*env)->NewStringUTF(env, "Hello from JNI !");
}
  • 在gradle.build中
sourceSets { main { jni.srcDirs = ['src/main/jni'] } }//指定jni目录
ndk {
        moduleName "ndk_test"          //生成的so名字
        abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库,目前可有可无
    }
  • gradle.properties中

    android.useDeprecatedNdk=true


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值