JNI_3

①交叉编译概念

在windows上编译可以在除了windows平台之外 可以执行的机器码

android是Linux windows编译在linux上执行的机器码

动态链接库 .so   .dll

NDK google提供的交叉编译的工具

②如何使用ndk指令

ndk-build  添加到环境变量  可以在工程的根目录下执行ndk-build开启一个交叉编译的过程

ndk解压的路径不要包含空格

ndk-build->在当前目录下找jni的目录->找Android.mk(必须的) 解析这个配置文件 知道要编译的c源代码在什么位置,要生成的.so叫什么名字->找有没有Application.mk(可选的) 如果有 解析一下 知道要编译生成支持哪些平台的.so文件->编译c的代码

③jni开发流程

1
在java代码中 声明一个本地方法 使用native关键字 本地方法不用实现 没有方法体 只有声明

2
在项目的根目录创建一个jni目录 创建Android.mk 创建.c的源文件 如果需要创建Application.mk

  1. #获取当前的目录全路径
  2. LOCAL_PATH := $(call my-dir)
  3. # 清除之前编译时的配置信息 不会把call my-dir 这一步的内容清除
  4. include $(CLEAR_VARS)
  5. #编译之后生成的模块的名字 系统会自动加上一个lib前缀
  6. LOCAL_MODULE := hello-jni
  7. #告诉编译系统我要编译的源码的文件名字
  8. LOCAL_SRC_FILES := hello.c
  9. #生成一个动态链接库 .so文件
  10. include $(BUILD_SHARED_LIBRARY)

3.写c的代码
需要跟native方法对应的部分函数名字不能乱写

本地函数的命名规则
 返回值 Java_包名_类名_native方法的名字

c的本地函数
有两个必须要传递的参数

JNIEnv*
env  jni环境 二级指针访问结构体中的成员

(**env).NewStringUTF()

(*env)->NewStringUTF();

jobject
哪个类调用的这个native方法 就是这个类的对象

4.ndk-build
编译.c的代码 编译的过程会创建一个中间文件存放的目录 obj
 在这个目录生成.so最后copy到libs->armeabi目录下

5.运行之前一定要加载.so文件
不加载会有问题

System.loadlibray(".so的名字");
.so文件的名字 把lib 去掉 去掉.so的后缀 就是要加载的模块名字

④jni开发常见的错误

两种常见错误

1本地方法没有找到
native method not found

名字写错不符合jni规范
javah生成头文件

没写System.loadlibrary
没加载.so就执行了native方法

2.找.so库的时候
返回null find library returned null

System.loadLibrary名字写错

.so文件不支持当前的cpu平台
 需要通过Application.mk 指定

APP_ABI
:= x86 armeabi

⑤jni简便开发流程
⑥java数据传递给C

使用场景

java不能满足需求
图片的处理

在传递过程中要解决的问题

数据类型不匹配的问题

传递基本数据类型(int)
不需要特殊处理 大家都一样 除了char有区别

字符串
c char*  java String

c的字符串转换成java
NewStringUTF()

java的字符串转换成C GetStringUTFChars(env,java的字符串,NULL);

通过这个第三个参数可以知道
jstring转换成char*的时候是否创建了副本 一定会创建的不会直接修改

jstring

java的基本数据类型的数组
传递给c

数组的长度获取

GetArrayLength(env,jarray);

GetXXXXXArrayElements(env,jarray,NULL);
获取基本数据类型数组的首地址

⑦c语言如何集成logcat

①在Android.mk当中添加一个LOCAL_LDLIBS
+= -llog //加载liblog.so

②在代码中
导入头文件

C代码中增加以下内容

#include
<android/log.h>

#define LOG_TAG
"System.out"

#define
LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
__VA_ARGS__)

#define
LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG,
__VA_ARGS__)

C代码中使用logcat,
例:

LOGI("info\n");

LOGD("debug\n");

⑧实现C回调java中的方法

1
.so的逆向(反汇编)

.so只能反汇编
反汇编之后能拿到对应的汇编代码

通过汇编代码可以猜出C的源代码(伪代码)

相对来说
c在防止别人做破解工作方面 比java要靠谱 比较强调安全的应用 会把加密的逻辑放到c中操作 会使用到JNI JNI

2.美图秀秀

使用美图秀秀的.so和jni文件实现类似效果(练习的时候需要注意
只能用arm架构cpu的模拟器 )

①反编译
把.so文件copy到项目中

②把JNI文件copy到项目中注意保持jni文件的文件路径

③编写java代码
调用JNI中的图片处理方法

  1. public class MainActivity extends Activity {
  2. private ImageView iv_image;
  3. private Bitmap bitmap;
  4. private JNI jni;
  5. @Override
  6. protected void onCreate(Bundle savedInstanceState) {
  7. super.onCreate(savedInstanceState);
  8. setContentView(R.layout.activity_main);
  9. iv_image = (ImageView) findViewById(R.id.iv_image);
  10. bitmap = BitmapFactory.decodeFile("storage/sdcard/front.jpg");
  11. iv_image.setImageBitmap(bitmap);
  12. jni = new JNI();
  13. }
  14. public void processPic(View v){
  15. //图片的宽高 实际上就是 图片横向有多少个像素 竖直方向有多少个像素
  16. int width = bitmap.getWidth();
  17. int height = bitmap.getHeight();
  18. //ARGB 8888
  19. //alpha 透明度 8个bit 8位二进制数
  20. //red 红色 8位二进制数
  21. //G green 绿色 8位二进制数
  22. //B blue 蓝色 8位二进制数
  23. //一个像素 可以用4byte的 内存来保存 int 4byte
  24. //创建一个数组 用来保存图片的颜色信息
  25. int pixels[] = new int[width*height];
  26. //第一个参数 准备好的数组 用来保存图片的每一个像素的颜色信息
  27. //第二个参数 写入到pixels数组中第一元素的偏移量 也就是开始的索引
  28. //第三个参数 传入一个>=bitmap宽度的值 如果>bitmap的宽度 会有类似墙纸平铺的效果 现在想获取整张图片所以传入bitmap的宽度
  29. //第四个,第五个参数 从bitmap中读出的第一个像素的坐标
  30. //第六个第七个参数 从bitmap中每一行 读出多少个像素 每一列读出多少个像素
  31. bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
  32. //getPixels方法执行完 pixels数组中就保存了跟图片相关的所有像素信息
  33. //jni.StyleLomoB(pixels, width, height);
  34. jni.StyleJapanese(pixels, width, height);
  35. //StyleLomoB 这个方法执行完之后 pixels又发生了变化 所有的像素已经是加了特效的像素信息了
  36. //我拿着处理好的像素的数组创建一张新的图片 这个图片就包含特效了
  37. Bitmap bitmap2 = Bitmap.createBitmap(pixels, width, height, bitmap.getConfig());
  38. //bitmap2已经有特效了通过imageview展示出来
  39. iv_image.setImageBitmap(bitmap2);
  40. //c的函数 可以处理图片 需要你传递 图片int数组的首地址 传递图片的宽度 高度
  41. //int* p = GetIntArrayElments(env,jarry,NULL);
  42. //becomeBeautiful(int* ,intwith, int height)
  43. //becomeB(p,width,height);
  44. }
  45. }

3.锅炉压力

如何跟硬件沟通

需要调用硬件驱动(c编写的)
 返回一个结果 可以在界面上展示

通过jni的调用
c的函数也是执行在主线程中 同样不能执行耗时的操作

4.C++JNI开发

  1. #include <jni.h> //直接到系统指定的include目录去找
  2. #include "com_itheima_cppjni_MainActivity.h" //用双引号 来include头文件 会先到当前目录下找.h 当前目录没有 去系统指定的include目录找
  3. JNIEXPORT jstring JNICALL Java_com_itheima_cppjni_MainActivity_sayHelloInCPP
  4. (JNIEnv * env, jobject obj){
  5. //在c++当中 JNIEnv不再是结构体的一级指针 而是结构体的别名 实际上就是结构体
  6. //所以 env 是结构体一级指针 通过env访问函数的时候 直接env->
  7. // C++的结构体 可以有函数实现 c的结构体只能有函数指针
  8. //在c++中 JNIEnv 实际上是结构体 _JNIEnv的别名 _JNIEnv 包含了一堆函数
  9. //实际上就是调用了 结构体JNINativeInterface的同名函数指针
  10. //所以在写c++的jni相关代码时 调用到_JNIEnv结构体里的函数跟c的时候 名字都一样
  11. //有区别的是 第一个参数 env不用传了 _JNIEnv结构体 在调用同名函数指针的时候把第一个参数 已经传进去了
  12. //c++的函数要先声明再使用 可以使用javah生成的头文件作为函数声明
  13. return env->NewStringUTF("hello from cpp!!!");
  14. }

5.fork
分叉进程

5.1
fork函数

可以在当前的进程中复制出一个一模一样的进程
这个子进程跟父进程有相同的资源

fork函数返回值三种情况

返回值>0
说明 分叉成功 并且这个代码执行在主线程中 返回的是分叉出的子进程的进程编号

返回值==0
  分叉成功
这个代码是执行在子进程中 子进程再调用fork只会返回0 不会继续复制新的进程

返回值<0
说明复制失败

5.2
am命令

am命令
:在adb shell里可以通过am命令进行一些操作 如启动activity Service 启动浏览器等等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.cppjni/com.itheima.cppjni.MainActivity

5.3
execlp

  1. JNIEXPORT void JNICALL Java_com_itheima_cfork_MainActivity_cfork
  2. (JNIEnv * env, jobject obj){
  3. int pid = fork();
  4. //如果成功复制了(分叉了)一个进程 返回值会>=0
  5. //如果返回值<0说明分叉失败
  6. if(pid>0){
  7. LOGE("pid=%d",pid);
  8. }else if(pid == 0){
  9. //复制出的进程 来作为守护进程 我可以不断检测父进程的编号 如果变成1了说明亲爹不在了(应用进程别杀死了)
  10. //① 进程死了 应用还在
  11. //② 进程死了 应用也卸了
  12. LOGE("pid==0");
  13. int ppid;
  14. FILE* file = NULL;
  15. //执行在子进程中 这个进程是由java进程复制出来的(分叉出来);
  16. while(1){
  17. LOGE("subprocess is running");
  18. sleep(3);
  19. //获取当前进程的父进程编号
  20. ppid = getppid();
  21. //如果父进程编号变为init进程了
  22. if(ppid==1){
  23. //应用进程别杀死了
  24. file = fopen("/data/data/com.itheima.cfork","r");
  25. if(file==NULL){
  26. //应用被卸载了 弹出浏览器 显示调查问卷
  27. execlp("am", "am", "start", "--user","0","-a", "android.intent.action.VIEW", "-d", "http://www.baidu.com", (char *) NULL);
  28. }else{
  29. //应用还在 重新开启进程
  30. execlp("am", "am", "start", "--user","0", "-n" , "com.itheima.cfork/com.itheima.cfork.MainActivity",(char *) NULL);
  31. }
  32. }
  33. }
  34. }else{
  35. LOGE("failed");
  36. }
  37. }
时间: 2024-10-16 20:04:28

JNI_3的相关文章