①交叉编译概念java
在windows上编译能够在除了windows平台以外 能够执行的机器码
android是Linux windows编译在linux上执行的机器码
动态连接库 .so .dlllinux
NDK google提供的交叉编译的工具android
②如何使用ndk指令c++
ndk-build 添加到环境变量 能够在工程的根目录下执行ndk-build开启一个交叉编译的过程shell
ndk解压的路径不要包含空格windows
ndk-build->在当前目录下找jni的目录->找Android.mk(必须的) 解析这个配置文件 知道要编译的c源代码在什么位置,要生成的.so叫什么名字->找有没有Application.mk(可选的) 若是有 解析一下 知道要编译生成支持哪些平台的.so文件->编译c的代码数组
③jni开发流程浏览器
1 在java代码中 声明一个本地方法 使用native关键字 本地方法不用实现 没有方法体 只有声明
2 在项目的根目录建立一个jni目录 建立Android.mk 建立.c的源文件 若是须要建立Application.mk安全
#获取当前的目录全路径
LOCAL_PATH := $(call my-dir)
# 清除以前编译时的配置信息 不会把call my-dir 这一步的内容清除
include $(CLEAR_VARS)
#编译以后生成的模块的名字 系统会自动加上一个lib前缀
LOCAL_MODULE := hello-jn
#告诉编译系统我要编译的源码的文件名字
LOCAL_SRC_FILES := hello.c
#生成一个动态连接库 .so文件
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中的图片处理方法
public class MainActivity extends Activity {
private ImageView iv_image;
private Bitmap bitmap;
private JNI jni;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
iv_image = (ImageView) findViewById(R.id.iv_image);
bitmap = BitmapFactory.decodeFile("storage/sdcard/front.jpg");
iv_image.setImageBitmap(bitmap);
jni = new JNI();
}
public void processPic(View v){
//图片的宽高 实际上就是 图片横向有多少个像素 竖直方向有多少个像素
int width = bitmap.getWidth();
int height = bitmap.getHeight();
//ARGB 8888
//alpha 透明度 8个bit 8位二进制数
//red 红色 8位二进制数
//G green 绿色 8位二进制数
//B blue 蓝色 8位二进制数
//一个像素 能够用4byte的 内存来保存 int 4byte
//建立一个数组 用来保存图片的颜色信息
int pixels[] = new int[width*height];
//第一个参数 准备好的数组 用来保存图片的每个像素的颜色信息
//第二个参数 写入到pixels数组中第一元素的偏移量 也就是开始的索引
//第三个参数 传入一个>=bitmap宽度的值 若是>bitmap的宽度 会有相似墙纸平铺的效果 如今想获取整张图片因此传入bitmap的宽度
//第四个,第五个参数 从bitmap中读出的第一个像素的坐标
//第六个第七个参数 从bitmap中每一行 读出多少个像素 每一列读出多少个像素
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
//getPixels方法执行完 pixels数组中就保存了跟图片相关的全部像素信息
//jni.StyleLomoB(pixels, width, height);
jni.StyleJapanese(pixels, width, height);
//StyleLomoB 这个方法执行完以后 pixels又发生了变化 全部的像素已是加了特效的像素信息了
//我拿着处理好的像素的数组建立一张新的图片 这个图片就包含特效了
Bitmap bitmap2 = Bitmap.createBitmap(pixels, width, height, bitmap.getConfig());
//bitmap2已经有特效了经过imageview展现出来
iv_image.setImageBitmap(bitmap2);
//c的函数 能够处理图片 须要你传递 图片int数组的首地址 传递图片的宽度 高度
//int* p = GetIntArrayElments(env,jarry,NULL);
//becomeBeautiful(int* ,intwith, int height)
//becomeB(p,width,height);
}
}
3.锅炉压力
如何跟硬件沟通
须要调用硬件驱动(c编写的) 返回一个结果 能够在界面上展现
经过jni的调用 c的函数也是执行在主线程中 一样不能执行耗时的操做
4.C++JNI开发
#include <jni.h> //直接到系统指定的include目录去找 #include "com_ngyb_cppjni_MainActivity.h" //用双引号 来include头文件 会先到当前目录下找.h 当前目录没有 去系统指定的include目录找 JNIEXPORT jstring JNICALL Java_com_ngyb_cppjni_MainActivity_sayHelloInCPP (JNIEnv * env, jobject obj){ //在c++当中 JNIEnv再也不是结构体的一级指针 而是结构体的别名 实际上就是结构体 //因此 env 是结构体一级指针 经过env访问函数的时候 直接env-> // C++的结构体 能够有函数实现 c的结构体只能有函数指针 //在c++中 JNIEnv 其实是结构体 _JNIEnv的别名 _JNIEnv 包含了一堆函数 //实际上就是调用了 结构体JNINativeInterface的同名函数指针 //因此在写c++的jni相关代码时 调用到_JNIEnv结构体里的函数跟c的时候 名字都同样 //有区别的是 第一个参数 env不用传了 _JNIEnv结构体 在调用同名函数指针的时候把第一个参数 已经传进去了 //c++的函数要先声明再使用 可使用javah生成的头文件做为函数声明 return env->NewStringUTF("hello from cpp!!!"); }
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.ngyb.cppjni/com.ngyb.cppjni.MainActivity
5.3 execlp
JNIEXPORT void JNICALL Java_com_ngyb_cfork_MainActivity_cfork (JNIEnv * env, jobject obj){ int pid = fork(); //若是成功复制了(分叉了)一个进程 返回值会>=0 //若是返回值<0说明分叉失败 if(pid>0){ LOGE("pid=%d",pid); }else if(pid == 0){ //复制出的进程 来做为守护进程 我能够不断检测父进程的编号 若是变成1了说明亲爹不在了(应用进程别杀死了) //① 进程死了 应用还在 //② 进程死了 应用也卸了 LOGE("pid==0"); int ppid; FILE* file = NULL; //执行在子进程中 这个进程是由java进程复制出来的(分叉出来); while(1){ LOGE("subprocess is running"); sleep(3); //获取当前进程的父进程编号 ppid = getppid(); //若是父进程编号变为init进程了 if(ppid==1){ //应用进程别杀死了 file = fopen("/data/data/com.ngyb.cfork","r"); if(file==NULL){ //应用被卸载了 弹出浏览器 显示调查问卷 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.ngyb.cfork/com.ngyb.cfork.MainActivity",(char *) NULL); } } } }else{ LOGE("failed"); } }