/*
* SSIDMonitor.h
*
* Created on: 2015-3-19
* Author: hgm
*/
#ifndef SSIDMONITOR_H_
#define SSIDMONITOR_H_
#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_feizhang_ssidmonitor_MainActivity
* Method: uninstall
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_com_feizhang_ssidmonitor_MainActivity_uninstall(JNIEnv *,jobject, jstring,jint,jint,jstring);
#ifdef __cplusplus
}
#endif
#endif
这个是一个典型的java调用c语言的接口,贴在这里不是贴错了,而是本来就是为了实现一个安卓app而做的。顺便回忆一下c++的对象模型,因为看着这个为java调用的c接口是十分非常的顺眼啊。
JNIEXPORT dllexport 不解释,告诉编译器该函数导出
JNICALL stdcall 不解释,告诉编译器该函数的参数压栈方式
void 函数返回值
Java_com_feizhang_ssidmonitor 包名
MainActivity需要扩展的java类
uninstall 需要扩展的java类成员函数
JNIENV*进程执行环境变量,可以理解为虚拟机句柄
jobject,扩展的java类实例
后面四个为成员函数参数
#include <stdio.h>
#include <jni.h>
#include <malloc.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <android/log.h>
#include "SSIDMonitor.h"
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define LOG_TAG "System.out.c"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
static const char* active_name = "com.feizhang.ssidmonitor/com.feizhang.ssidmonitor.MainActivity";
static const char* connect_path = "/data/data/com.feizhang.ssidmonitor/my.sock";
static int using_sdk_version = 0;
/*
* ret = 0 文件已经上锁
* ret = 1 文件上锁
* ret = -1 出错
*
* flock锁的释放非常具有特色,
* 即可调用LOCK_UN参数来释放文件锁,
* 也可以通过关闭fd的方式来释放文件锁(flock的第一个参数是fd),
* 意味着flock会随着进程的关闭而被自动释放掉。
*
* 再此我们使用了这个函数的这个特性用来检测被监听进程是否还在存活
* 之所以如此是因为安卓系统更新后(包括各种管家升级后)在清除进程时
* 不再优雅清除而是直接kill掉,导致以前在析构函数中重启服务无法使
* 用
*/
static int connected_fd = -1;
static int connected_alive(int get_lock,int closefd){
int flag = get_lock ? (LOCK_EX|LOCK_NB) : (LOCK_UN);
connected_fd = open(connect_path,O_RDWR|O_CREAT,0644);
errno = 0;
if(-1 == connected_fd){
LOGE("open file failed : %d(%s)",errno,strerror(errno));
return -1;
}
int ret = flock(connected_fd,flag);
if(-1 == ret && EWOULDBLOCK != errno){
LOGE("process is alive : ... %d(%s)",errno,strerror(errno));
close(connected_fd);
return -1;
}
LOGD("process locked file : ret = %d , errno = %d(%s)",ret,errno,strerror(errno));
if(closefd) close(connected_fd);
return 0 == ret;
}
/*
* 重新启动宿主进程
*/
static void excute_active(){
int ret = 0;
if(using_sdk_version < 17){
ret = execlp("am", "am", "start", "-n", active_name,(char*)NULL);
}else{
ret =execlp("am", "am", "start", "--user", "0","-n",active_name,(char*)NULL);
}
if(-1 == ret){
LOGE("Execute a new active failed : %d(%s)",errno,strerror(errno));
}
}
/**
* 返回值 char* 这个代表char数组的首地址
* Jstring2CStr 把java中的jstring的类型转化成一个c语言中的char 字符串
*/
char* Jstring2CStr(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;
}
/*
* 扩展宿主进程的行为
* 因为用java写一个守护进程比较困难
* 所以在此使用ndk进行扩展,开启一个守护进程
* 来监视宿主进程是否被杀死或者被卸载掉
* */
extern "C" JNIEXPORT void JNICALL Java_com_feizhang_ssidmonitor_MainActivity_uninstall(
JNIEnv * env, jobject obj, jstring packageDir, jint sdkVersion,jint sleepTime,jstring reportUrl) {
// 1,将传递过来的java的包名转为c的字符串
char * pd = Jstring2CStr(env, packageDir);
char * rp = Jstring2CStr(env,reportUrl);
using_sdk_version = sdkVersion;
/*
* 本函数调用多次导致守护进程也启动多次
* 原理:
* 当宿主进程第二次启动时,如果第一次
* */
if(connected_alive(1,0) <= 0){
LOGD("local port is using daemon process is running ...");
return;
}
LOGD("packagedDir = %s,sleepTime = %d,reportURL = %s",pd,sleepTime,rp);
// 2,创建当前进程的克隆进程
pid_t pid = fork();
// 3,根据返回值的不同做不同的操作,<0,>0,=0
if (pid < 0) {
// 说明克隆进程失败
LOGD("current crate process failure");
return;
}
if (pid > 0) {
// 说明克隆进程成功,而且该代码运行在父进程中
LOGD("crate process success,current parent pid = %d", pid);
free(pd);
free(rp);
return;
}
//释放有可能继承自文件句柄以及锁资源
close(connected_fd);
// 说明克隆进程成功,而且代码运行在子进程中
LOGD("crate process success,current child pid = %d", pid);
// 4,在子进程中监视/data/data/包名这个目录
for (; JNI_TRUE; sleep(sleepTime)) {
FILE* file = fopen(pd, "rt");
if (file == NULL) {
break;
}
fclose(file);
LOGD("app run normal,will be checked service ... %d",sleepTime);
if(1 == connected_alive(1,1)){
excute_active();
//重启应用,守护进程退出
return ;
}
}
// 应用被卸载了,通知系统打开用户反馈的网页
LOGD("app uninstall,current sdkversion = %d,url = %s", sdkVersion,rp);
if (using_sdk_version < 17) {
// Android4.2以前的版本无需指定用户
execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d",rp, (char*) NULL);
return;
}
// Android4.2系统之后支持多用户操作,所以得指定用户
execlp("am", "am", "start", "--user", "0", "-a","android.intent.action.VIEW", "-d", rp, (char*) NULL);
}
注释已经很清楚了,就不解释了