http://www.cnblogs.com/devinzhang/archive/2012/02/29/2373729.html
关于NDK的介绍如上:
JNI
what 什么是JNI?
*java native interface native 本地语言 系统是由什么开发的 linux是由c开发的 那么这种语言对于这个系统来说就是本地语言
*native 本地语言 本地代码
*作用是java 和c/c++相互调用
jni 实质是一个接口 java的方法调用c的函数
jni 相当于是 把c/c++函数翻译成java
也可以把java翻译成c/c++代码
why 为什么用jni?
*java的优势 一处编译 到处运行 java虚拟机跨平台
*java想直接访问系统底层的驱动
*android里的java代码都是跑在虚拟机里面的
*不能直接控制驱动 jni扩展java代码的能力 驱动大部分是由c编写的
*特斯拉车载智能系统就是用的linux --按下按钮登就开了。
*c编译生成的就是机器码 java需要虚拟机翻译(解释型语言--效
率低) 大型的3d游戏或者是音视频解码就不能用java
*android系统是16ms刷新一次屏幕 通过JNI 放到C或者C++来实现
*抗反编译的能力加强 加密的登录逻辑一定要放在c里面如密文和算法 eg拿到上传包+源码加密逻辑 即可破解
开发者平时编写android时。framwork层连接函数库层也是用的JNI(Native Developer Kit)
只是google已经提开发者封装好了,提供给开发者一个API。所以我们也用JNI,只是没有感觉到而已。
how 怎么用JNI
会用 java
c/c++ 能看懂,会调用。
interface JNI的开发流程
交叉编译
—在一个平台上编译出另一个平台可以运行的本地代码
cpu平台
arm架构 x86(Atom) mips
不同架构支持的指令集是有差别的
精简指令和复杂指令集
##操作系统平台
windows linux Mac os unix
模拟另一个平台的特点进行编译
jni开发工具
NDk native develop kit 本地开发工具
NDK的目录
> 引用块内容
结构
docs 帮助文档
platforms 根据不同的android版本分了不同的文件夹
--include jni开发常用的头文件
--lib google官方提供 jni开发中可能用到的库
build\tools .sh文件linux下批处理文件 系统自动调用 开始交叉编译的过程
samples 样例
sources ndk相关源码
ndk-build.cmd 开始交叉编译的命令
CDT 高亮显示c关键字 c代码提示
jni helloworld
新建一个android工程JNI_01
先写JAVA代码 用native关键字 声明本地方法 本地方法不用实现 在c中实现
看源码
JNIEnv 是一个别名 上面是结构体的指针
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif
jint (*GetVersion)(JNIEnv *);
这个是函数体的指针
通过JNIEnv调用
再写玩c之后用ndk-build构建头文件
cd /d 项目所在的目录即可构建头文件
编辑后报错说缺少android.mk文件
补充上mk文件makefile文件
#找到jni目录的路径
LOCAL_PATH := $(call my-dir)
#清除上一次编译的local_Path不会被清除
include $(CLEAR_VARS)
#编译生成的文件的名字
LOCAL_MODULE := hello
#本地资源文件,指定.c的文件 hello.c hello2.c
LOCAL_SRC_FILES := hello.c
#指定编译动态链接库.so(linux) .dll(windows)
include $(BUILD_SHARED_LIBRARY)
在cd到项目目录下 用ndk-build再编译
jni开发常见错误
natvie method not found
1.本地函数名字 不符合命名规则
方法名或者包名有下滑线的需要进行+1操作
hello____1111world11___JNi();
用javah生成头文件
如果是jdk1.7以上的就到项目的src目录下看
如果是jdk1.6以下的就到bin/classes在哪运行头文件就到哪去找
2. System.loadlibrary("hello");忘记写了
loader ....findlibrary returned null
只截取lib之后的modle libhello
1.System.loadlibrary("libhello")
2.cpu平台不支持(比如 把你个只支持arm处理器的.so文件部署到x86架构下)
这时候就需要解决需要生成x86的so文件---途径如下:
建立一个Application.mk 导入APP_ABI :x86 armeabi
也可解决mindsdkversion的问题
APP_PLATFORM :=android-14 #解决警告
再用ndk-build重新编译一次
jni开发简便流程
先写java代码 用native关键字 声明本地方法 本地方法不需要实现
添加本地支持 选择 adroid toos -》add native support
有的需要 点击window -》preferencs -》android 》ndk
会自动生成jni目录
再用javah生成头文件
实例分析
以前常见的卸载 弹窗
https://raw.githubusercontent.com/venshine/AppUninstall/master/app/src/main/cpp/uninstall.cpp
/*
* Copyright (C) 2016 venshine.cn@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <jni.h>
#include <string>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/stat.h>
#include <android/log.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/system_properties.h>
#define TAG "venshine"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
static const char APP_DIR[] = "/data/data/com.wx.appuninstall";
static const char APP_FILES_DIR[] = "/data/data/com.wx.appuninstall/files";
static const char APP_OBSERVED_FILE[] = "/data/data/com.wx.appuninstall/files/observedFile";
static const char APP_LOCK_FILE[] = "/data/data/com.wx.appuninstall/files/lockFile";
static const char *HOST_ADDR = "www.baidu.com";
static const char *SERVER_ADDR = "http://www.baidu.com";
static const int OK = 0;
static const int ERROR = -1;
int watchDescriptor;
int fileDescriptor;
pid_t observer = -1;
#ifdef __cplusplus
extern "C" {
#endif
/**
* 获取SDK版本号
*/
int get_sdk_version();
/**
* Jstring转char*
*/
char *JstringToCStr(JNIEnv *env, jstring jstr) ;
/**
* 上传统计数据
*/
int uploadStatData(char* versionName, jint versionCode);
/**
* 监听
*/
int startObserver(void *p_buf);
/**
* 判断是否进程活着
*/
int isProcessAlive(const char *pid);
/**
* 记录pid
*/
void writePidFile(const char *pid);
/**
* 监控程序
*/
jint Java_com_wx_appuninstall_Uninstall_watch(
JNIEnv *env, jobject thiz, jobject upload_obj) {
if (upload_obj == NULL) {
exit(1);
}
// 获得UploadInfo类引用
jclass upload_cls = env->GetObjectClass(upload_obj);
if (upload_cls == NULL) {
exit(1);
}
// 判断监听进程是否活着
if (isProcessAlive(APP_OBSERVED_FILE) == OK) {
LOGE("watch process already exists");
return observer;
}
// 若被监听文件存在,删除
FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
if (p_observedFile != NULL) {
LOGD("delete observed file");
remove(APP_OBSERVED_FILE);
fclose(p_observedFile);
}
// 若被监听文件存在,删除
FILE *p_LockedFile = fopen(APP_LOCK_FILE, "r");
if (p_LockedFile != NULL) {
LOGD("delete lock file");
remove(APP_LOCK_FILE);
fclose(p_LockedFile);
}
// 创建进程
pid_t pid = fork();
// prctl(PR_SET_NAME, "m.uninstall");
// 根据返回值不同做不同操作
if (pid < 0) { // 创建进程失败
LOGE("fork process error!");
} else if (pid == 0) { // 创建第一个子进程成功,代码运行在子进程中
LOGD("fork first process succ pid = %d", getpid());
setsid(); // 将进程和它当前的对话过程和进程组分离开,并且把它设置成一个新的对话过程的领头进程。
umask(0); // 为文件赋予更多的权限,因为继承来的文件可能某些权限被屏蔽
int pid = fork();
// prctl(PR_SET_NAME, "j.k.l.uninstall");
if (pid == 0) { // 第二个子进程
// 保存监听进程id
LOGD("fork second process succ pid = %d", getpid());
// 分配缓存,以便读取event,缓存大小等于一个struct inotify_event的大小,这样一次处理一个event
void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL) {
LOGD("malloc failed !!!");
exit(1);
}
// 通过linux中的inotify机制来监听应用的卸载。inotify是linux内核用于通知用户空间文件系统变化的机制,文件的添加或卸载等事件都能够及时捕获到。
if (startObserver(p_buf) != 0) {
return 0;
}
writePidFile(APP_OBSERVED_FILE);
// 开始监听
while (1) {
LOGD("start watch");
// 调用read函数开始监听,read会阻塞进程
ssize_t readBytes = read(fileDescriptor, p_buf, sizeof(struct inotify_event));
// 走到这里说明收到目录被删除的事件
if (IN_DELETE_SELF == ((struct inotify_event *) p_buf)->mask) {
LOGD("IN_DELETE_SELF");
// 若文件被删除,可能是已卸载,还需进一步判断app文件夹是否存在
FILE *p_appDir = fopen(APP_DIR, "r");
if (p_appDir != NULL) {
// 应用主目录还在(可能还没有来得及清除),sleep一段时间后再判断
sleep(5);
p_appDir = fopen(APP_DIR, "r");
}
// 确认已卸载
if (p_appDir == NULL) {
LOGD("inotify rm watch");
inotify_rm_watch(fileDescriptor, watchDescriptor);
break;
} else { // 未卸载,可能用户执行了"清除数据"
LOGD("not uninstall");
fclose(p_appDir);
// 应用没有卸载,重新监听
if (startObserver(p_buf) != 0) {
return 0;
}
}
} else {
LOGD("NOT IN_DELETE_SELF");
}
}
LOGD("end watch");
remove(APP_OBSERVED_FILE);
remove(APP_LOCK_FILE);
free(p_buf);
jfieldID nameFieldID = env->GetFieldID(upload_cls, "versionName", "Ljava/lang/String;"); // 获得属性ID
jfieldID codeFieldID = env->GetFieldID(upload_cls, "versionCode", "I"); // 获得属性ID
jfieldID browserFieldID = env->GetFieldID(upload_cls, "isBrowser", "Z"); // 获得属性ID
jstring versionName = (jstring) env->GetObjectField(upload_obj, nameFieldID);// 获得属性值
jint versionCode = env->GetIntField(upload_obj, codeFieldID); // 获得属性值
jboolean isBrowser = env->GetBooleanField(upload_obj, browserFieldID); // 获得属性值
char *vName = JstringToCStr(env, versionName);
// 上传统计数据
if (uploadStatData(vName, versionCode) == OK) {
LOGD("upload data succ");
}
// 是否打开浏览器
if (isBrowser) { // TODO 打开浏览器命令在有些手机上可能失效
// 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)
execlp("am", "am", "start", "--user", "0", "-a", "android.intent.action.VIEW", "-d",
SERVER_ADDR,
(char *) NULL);
}
} else {
exit(0);
}
} else {
// 父进程直接退出,使子进程被init进程领养,以避免子进程僵死,同时返回子进程pid
LOGD("parent process exit");
}
return pid;
}
/**
* 监听
*/
int startObserver(void *p_buf) {
// 若监听文件所在文件夹不存在,创建文件夹
FILE *p_filesDir = fopen(APP_FILES_DIR, "r");
if (p_filesDir == NULL) {
int filesDirRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH);
if (filesDirRet == -1) {
LOGE("create app files dir failed");
exit(1);
}
}
// if (access(APP_FILES_DIR, F_OK) != 0) {
// LOGD("folder not exists");
// if (mkdir(APP_FILES_DIR, 0755) == -1) {
// LOGE("mkdir failed!");
// exit(1);
// }
// }
// 若被监听文件不存在,创建监听文件
FILE *p_observedFile = fopen(APP_OBSERVED_FILE, "r");
if (p_observedFile == NULL) {
p_observedFile = fopen(APP_OBSERVED_FILE, "w");
LOGD("create app observed file");
}
fclose(p_observedFile);
// 创建锁文件,通过检测加锁状态来保证只有一个卸载监听进程
int lockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY);
if (lockFileDescriptor == -1) {
lockFileDescriptor = open(APP_LOCK_FILE, O_CREAT);
LOGD("create app lock file");
}
int lockRet = flock(lockFileDescriptor, LOCK_EX | LOCK_NB);
if (lockRet == -1) {
LOGE("watch by other process");
return ERROR;
}
// 初始化inotify进程
fileDescriptor = inotify_init();
if (fileDescriptor < 0) {
LOGE("inotify init failed");
free(p_buf);
exit(1);
}
// 添加inotify监听器,监听APP_OBSERVED_FILE文件
watchDescriptor = inotify_add_watch(fileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS);
if (watchDescriptor < 0) {
LOGE("inotify watch failed");
free(p_buf);
exit(1);
}
return OK;
}
/**
* 上传统计数据
*/
int uploadStatData(char* versionName, jint versionCode) {
LOGD("upload stat data");
struct sockaddr_in serv_addr;
struct hostent *host;
int sock = socket(AF_INET, SOCK_STREAM, 0);
if ((host = gethostbyname(HOST_ADDR)) == NULL) {
LOGE("host name is null.");
return ERROR;
}
memset(&serv_addr, 0, sizeof(serv_addr)); // 每个字节都用0填充
serv_addr.sin_family = AF_INET; // 使用IPv4地址
// serv_addr.sin_addr.s_addr = inet_addr("192.168.1.1"); // 具体的IP地址
serv_addr.sin_addr = *((struct in_addr *) host->h_addr);
serv_addr.sin_port = htons(80); //端口
if (connect(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
LOGE("connect error");
return ERROR;
}
LOGD("connect succ");
int sdkVersion = get_sdk_version();
char request[200];
sprintf(request, "GET /web/index.html?versionName=%s&versionCode=%d&sdkVersion=%d", versionName,
versionCode, sdkVersion);
if (write(sock, request, strlen(request)) < 0) {
LOGE("request failed");
return ERROR;
}
LOGD("request success");
// 关闭套接字
close(sock);
return OK;
}
/**
* 判断是否进程活着
*/
int isProcessAlive(const char *pid) {
FILE *pidFile;
char observerPID[32];
if ((pidFile = fopen(pid, "rb")) == NULL) {
LOGE("can't open pid file");
return ERROR;
}
// fread(&observerPID,sizeof(observerPID),1,pidFile);
fscanf(pidFile, "%d", &observer);
fclose(pidFile);
if (observer > 1) {
sprintf(observerPID, "%d/n", observer);
LOGD("read saved pid");
if (kill(observer, 0) == 0) {
LOGD("process is alive");
return OK;
}
LOGD("process is not alive");
} else {
LOGD("not read saved pid");
return ERROR;
}
}
/**
* 记录pid
*/
void writePidFile(const char *pid) {
char str[32];
int pidFile = open(pid, O_WRONLY | O_TRUNC);
if (pidFile < 0) {
LOGE("pid is %d", pidFile);
exit(1);
}
if (flock(pidFile, LOCK_EX | LOCK_NB) < 0) {
LOGD("cann't lock pid file: %s", pid);
fprintf(stderr, "can't lock pid file: %s", pid);
exit(1);
}
sprintf(str, "%d/n", getpid());
ssize_t len = strlen(str);
ssize_t ret = write(pidFile, str, len);
if (ret != len) {
LOGE("can't write pid file: %s", pid);
fprintf(stderr, "can't write pid file: %s", pid);
exit(1);
}
close(pidFile);
LOGD("write pid file success");
}
/**
* 获取SDK版本号
*/
int get_sdk_version() {
char value[8] = "";
__system_property_get("ro.build.version.sdk", value);
return atoi(value);
}
/**
* Jstring转char*
*/
char *JstringToCStr(JNIEnv *env, jstring jstr) {
char *rtn = NULL;
jclass clsstring = (*env).FindClass("java/lang/String"); //String
jstring strencode = (*env).NewStringUTF("GB2312"); // 得到一个java字符串 "GB2312"
jmethodID mid = (*env).GetMethodID(clsstring, "getBytes",
"(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
jbyteArray barr = (jbyteArray) (*env).CallObjectMethod(jstr, mid,
strencode); // String .getByte("GB2312");
jsize alen = (*env).GetArrayLength(barr); // byte数组的长度
jbyte *ba = (*env).GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char *) malloc(alen + 1); //"\0"
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
(*env).ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
#ifdef __cplusplus
}
#endif
代码总结:
- bytes to bytesarray to char
/**
* Jstring转char*
*/
char *JstringToCStr(JNIEnv *env, jstring jstr) {
char *rtn = NULL;
jclass clsstring = (*env).FindClass("java/lang/String"); //String
jstring strencode = (*env).NewStringUTF("GB2312"); // 得到一个java字符串 "GB2312"
jmethodID mid = (*env).GetMethodID(clsstring, "getBytes",
"(Ljava/lang/String;)[B"); //[ String.getBytes("gb2312");
jbyteArray barr = (jbyteArray) (*env).CallObjectMethod(jstr, mid,
strencode); // String .getByte("GB2312");
jsize alen = (*env).GetArrayLength(barr); // byte数组的长度
jbyte *ba = (*env).GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char *) malloc(alen + 1); //"\0"
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
(*env).ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
2.子进程离开父进程
3.native传数据