Android JNI编程

来源:互联网 发布:淘宝招聘官网首页 编辑:程序博客网 时间:2024/05/01 16:40
#android 下的JNI开发
##what 什么是JNI
## JNI java native interface nactive java 本地接口
##通过JNI可以实现java 和本地代码之间的互相调用
##jni可以看成是翻译 实际上就是一套协议
## why 为什么要用到JNI
#java 一处编译到处运行
1 * java 运行在虚拟机上 jni 可以扩展java 虚拟机的能力让代码调用驱动
2 java 是结实语言 运行效率相对较低 c/c++效率要高很多通过jni把耗时操作方法C/C++可以提高java的运行效率
3 java 代码编译成.class文件 文件的安全性较差,可以通过jni 把把重要的业务逻辑放到c/c++去实现,c/c++反编译比较困难 安全性较高
*C历史悠久 1972年就发明了C 通过JNI可以调用优秀的C类库

HOW 怎么用JNI
1 java
2 c/c++看得懂 会调用
3 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
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 long4
long 8 float 4
float 4 float 4
double 8 double 8

char ,int ,float double ,long short signed unsigned void
* signed 有符号数 最高位是符号位 可以表示负数 但是表示的最大值相对要小
* unsigned 无符号数 最高位是数值位 不可以表示负数 表示的最大值相对要大
* 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:自动回收
* 堆上分配的内存称为动态内存
### 结构体
* 结构体中的属性长度会被自动补齐,这是为了方便指针位移运算
* 结构体中不能定义函数,可以定义函数指针
* 程序运行时,函数也是保存在内存中的,也有一个地址
* 结构体中只能定义变量
* 函数指针其实也是变量,它是指针变量
* 函数指针的定义 返回值类型(*变量名)(接收的参数);
* 函数指针的赋值: 函数指针只能指向跟它返回值和接收的参数相同的函数
### 联合体
* 长度等于联合体中定义的变量当中最长的那个
* 联合体只能保存一个变量的值
* 联合体共用同一块内存
##交叉编译
*在一个平台上去编译另外一个平台可以执行本地代码
*cpu 平台 arm x86 mips
*操作系统平台 windows linux mac os
原理 模拟不同平台的特性去编译代码
##jni 开发工具
*ndk native develop kit
ndk目录
*docs帮助文档
*platform 好多好多平台版本文件夹 选择时选择项目支持的最小版本号对应的文件夹

* 每一个版本号的文件夹中放了 不同cpu架构的资源文件
* include文件夹 jni开发中常用的 .h头文件
* samples google官方提供的样例工程 可以参考进行开发
* lib 文件夹 google打包好的 提供给开发者使用的 .so文件
* android-ndk-r9d\build\tools linux系统下的批处理文件 在交叉编译时会自动调用
* ndk-build 交叉编译的命令
* cdt eclipse的插件 高亮C代码 C的代码提示
##jni简便开发流程
1 写java 代码 native 声明本地方法
2 添加本地支持 右键单击项目-》Android tools>add native surpot
* 如果发现 finish不能点击需要给工作空间配置ndk目录的位置
* window->preferences->左侧选择android->ndk 把ndk解压的目录指定进来
③ 如果写的是.c的文件 先修改一下生成的.cpp文件的扩展名 不要忘了 相应修改Android.mk文件中LOCAL_SRC_FILES的值
* ④ javah生成头文件 在生成的头文件中拷贝c的函数名到.c的文件
* ⑤ 解决CDT插件报错的问题
* 右键单击项目选择 properties 选测 c/c++ general->paths and symbols->include选项卡下->点击add..->file system 选择ndk目录下 platforms文件夹 对应平台下(项目支持的最小版本)
usr 目录下 arch-arm -> include 确定后 会解决代码提示和报错的问题
* ⑥编写C函数 如果需要单独编译一下c代码就在c/c++视图中找到小锤子
* 如果想直接运行到模拟器上 就不用锤子了
* ⑦ java代码中不要忘了 system.loadlibrary();

## C代码回调java方法

* ① 找到字节码对象
* //jclass (*FindClass)(JNIEnv*, const char*);
* //第二个参数 要回调的java方法所在的类的路径 "com/itheima/callbackjava/JNI"
* ② 通过字节码对象找到方法对象
* //jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
* 第二个参数 字节码对象 第三个参数 要反射调用的java方法名 第四个参数 要反射调用的java方法签名
* javap -s 要获取方法签名的类的全类名 项目/bin/classes 运行javap
* ③ 通过字节码创建 java对象(可选) 如果本地方法和要回调的java方法在同一个类里可以直接用 jni传过来的java对象 调用创建的Method
* jobject obj =(*env)->AllocObject(env,claz);
* 当回调的方法跟本地方法不在一个类里 需要通过刚创建的字节码对象手动创建一个java对象
* 再通过这个对象来回调java的方法
* 需要注意的是 如果创建的是一个activity对象 回调的方法还包含上下文 这个方法行不通!!!回报空指针异常
* ④ 反射调用java方法
* //void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
* 第二个参数 调用java方法的对象 第三个参数 要调用的jmethodID对象 可选的参数 调用方法时接收的参数

#C++开发JNI#
*C的预编译处理命令
*#开头的就是C/C++的预编译处理命令
*在编译之前先走预编译阶段 预编译就是把include进来的头文件 copy到源码
*define 这些宏定义 用真实的值替换一下
*#if #else# endif改删除的删除掉
*
*C++开发jni代码是 env不再是结构体Jninativeinterface 的二级指针
* _JNIEnv JNIEnv _JNIEnv 是C++的结构体 C++结构体跟C区别 C++的结构体可以定义函数

* _JNIEnv的函数 实际上调用的就是结构体JNINativeInterface的同名函数指针
* 在调用时第一个参数 env已经传进去了
* C++的函数要先声明再使用 可以把javah生成的头文件include进来作为函数的声明
* include的方法 <> "" ""
* 如果用"" 来导入头文件 系统会先到 源代码所在的文件夹去找头文件 如果找不到再到系统指定的incude文件夹下找
* //用<> 直接到系统指定的include目录下去找

#am 命令
*am 命令 在 adb shell里面可以通过am命令进行一些操作 如 启动 activity
启动浏览器等
*am 命令的源码在Am.java中 在adb shell 里执行am命令 实际上就是启动一个线程Am.java 的main方法 am命令后面带的参数都会当作运行时的参数传到main函数中
am 命令可以用start子命令 并且携带指定的参数
参见的参数 -a:action -d data -t表示传入的类型 -n指定的组件名字
*举例 在 adb shell中通过am命令打开网页
am start--user 0 -a android.intent.action.VIEW -d
*http://www.baidu.com
* 通过am命令打开activity
* am start --user 0 -n com.itheima.fork/com.itheima.fork.MainActivity
* (系统sdk版本>16 需要加上--user 0 , <16不需要加)

execlp c语言中华执行系统命令的函数
execlp() 会从PATH环境变量所指的目录中查找符合参数file的文件找到后就执行该文件 第二个参数开始就是执行该文件args[0] ,args[1]最后一个参数用(char*)NULL结束
*Android开发中 execlp函数对应Android的path路劲为system/bin/目录* 调用格式

execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
execlp("am", "am", "start", "--user","0", "-n" , "com.itheima.cforktest/com.itheima.cforktest.MainActivity",(char *) NULL);























































0 0
原创粉丝点击