最近研究了一下如何监听自身应用卸载,最后,做了一个sample。
sample 下载地址:http://download.youkuaiyun.com/detail/keanbin/6991979
主要代码:
1. 监听本应用卸载,其实是监听应用在内部存储器中的文件夹是否被删除。
ListenUninstallProcess.c
/*
* Copyright (C) 2009 The Android Open Source Project
*
* 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 <string.h>
#include <jni.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/inotify.h>
#include "AndroidLog.h"
#include "CurlHttpOperate.h"
/* 宏定义begin */
//清0宏
#define MEM_ZERO(pDest, destSize) memset(pDest, 0, destSize)
//LOG宏定义
#define LOG_INFO(tag, msg) __android_log_write(ANDROID_LOG_INFO, tag, msg)
#define LOG_DEBUG(tag, msg) __android_log_write(ANDROID_LOG_DEBUG, tag, msg)
#define LOG_WARN(tag, msg) __android_log_write(ANDROID_LOG_WARN, tag, msg)
#define LOG_ERROR(tag, msg) __android_log_write(ANDROID_LOG_ERROR, tag, msg)
//#define FEEDBACK_URL "http://www.baidu.com"
//#define LISTEN_FILE_PATH "/data/data/com.keanbin.app"
/* 内全局变量begin */
static jboolean b_IS_COPY = JNI_TRUE;
/*
* 启动进程,监听指定文件,如果文件被删除,就启动网页。
*
* listenFilePathJStr: 监听的文件路径
* urlJStr: 网址
*/
void Java_com_keanbin_testlistenselfuninstall_ListenUninstallProcess_startBrowser(
JNIEnv* env, jobject thiz, jstring listenFilePathJStr, jstring urlJStr,
jint androidSdkVersion) {
const char *listenFilePath;
const char *url;
//初始化log
LOGI("jni startBrowser()");
listenFilePath = (*env)->GetStringUTFChars(env, listenFilePathJStr, NULL);
if (listenFilePath == NULL) {
return;
}
url = (*env)->GetStringUTFChars(env, urlJStr, NULL);
if (url == NULL) {
return;
}
LOGI("jni startBrowser() listenFilePath = %s , url = %s",
listenFilePath, url);
//fork子进程,以执行轮询任务
pid_t pid = fork();
if (pid < 0) {
//出错log
LOGI("fork failed !!!");
} else if (pid == 0) {
//子进程注册"/data/data/pym.test.uninstalledobserver"目录监听器
int fileDescriptor = inotify_init();
if (fileDescriptor < 0) {
LOGI("inotify_init failed !!!");
exit(1);
}
int watchDescriptor;
watchDescriptor = inotify_add_watch(fileDescriptor, listenFilePath,
IN_DELETE);
if (watchDescriptor < 0) {
LOGI("inotify_add_watch failed !!!");
exit(1);
}
//分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL) {
LOGI("malloc failed !!!");
exit(1);
}
//开始监听
LOGI("start observer");
size_t readBytes = read(fileDescriptor, p_buf,
sizeof(struct inotify_event));
//read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器
free(p_buf);
inotify_rm_watch(fileDescriptor, IN_DELETE);
//目录不存在log
LOGI("uninstalled");
LOGI("Android Sdk Version: %d \n", androidSdkVersion);
if (androidSdkVersion < 17) {
//执行命令am start -a android.intent.action.VIEW -d http://www.baidu.com
execlp("am", "am", "start", "-a", "android.intent.action.VIEW",
"-d", url, (char *) NULL);
} else {
//4.2以上的系统由于用户权限管理更严格,需要加上 --user 0
execlp("am", "am", "start", "--user", "0", "-a",
"android.intent.action.VIEW", "-d", url, (char *) NULL);
}
} else {
//父进程直接退出,使子进程被init进程领养,以避免子进程僵死
}
}
/*
* 启动进程,监听指定文件,如果文件被删除,就进行http请求。
*
* listenFilePathJStr: 监听的文件路径
* urlJStr: 网址
* paramsStr: 参数。参数格式:变量名=值,多个变量用"&"连接起来,比如“key1=value1&key2=value2”。
* httpMethod:请求方式。get方式:1,post方式:2。
*/
void Java_com_keanbin_testlistenselfuninstall_ListenUninstallProcess_httpRequest(
JNIEnv* env, jobject entryObject, jstring listenFilePathJStr,
jstring urlJStr, jstring paramsStr, jint httpMethod) {
const char *listenFilePath;
//初始化log
LOGI("jni httpRequest()");
listenFilePath = (*env)->GetStringUTFChars(env, listenFilePathJStr, NULL);
if (listenFilePath == NULL) {
return;
}
LOGI("jni httpRequest() listenFilePath = %s", listenFilePath);
//fork子进程,以执行轮询任务
pid_t pid = fork();
if (pid < 0) {
//出错log
LOGI("fork failed !!!");
} else if (pid == 0) {
//子进程注册"/data/data/pym.test.uninstalledobserver"目录监听器
int fileDescriptor = inotify_init();
if (fileDescriptor < 0) {
LOGI("inotify_init failed !!!");
exit(1);
}
int watchDescriptor;
watchDescriptor = inotify_add_watch(fileDescriptor, listenFilePath,
IN_DELETE);
if (watchDescriptor < 0) {
LOGI("inotify_add_watch failed !!!");
exit(1);
}
//分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL) {
LOGI("malloc failed !!!");
exit(1);
}
//开始监听
LOGI("start observer");
size_t readBytes = read(fileDescriptor, p_buf,
sizeof(struct inotify_event));
//read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器
free(p_buf);
inotify_rm_watch(fileDescriptor, IN_DELETE);
//目录不存在log
LOGI("uninstalled");
curlHttp(env, entryObject, urlJStr, paramsStr, httpMethod);
} else {
//父进程直接退出,使子进程被init进程领养,以避免子进程僵死
}
}
ListenUninstallProcess.java:
package com.keanbin.testlistenselfuninstall;
import android.content.Context;
/**
* C层启动另一个进程监听本应用卸载。
*
* @author keanbin
*
*/
public class ListenUninstallProcess {
private Context mContext;
static {
System.loadLibrary("listen-uninstall-process-jni");
}
public ListenUninstallProcess(Context context) {
mContext = context;
}
/**
* 使用方法:在监听开机启动广播的接收器中执行,还有安装完后执行,可以保存个配置信息来标记。
*
* @param androidSdkVersion
*/
private native void startBrowser(String listenFilePath, String url,
int androidSdkVersion);
/**
* 使用方法:在监听开机启动广播的接收器中执行,还有安装完后执行,可以保存个配置信息来标记。
*
* @param url
* @param parames
* @param httpMethod
*/
private native void httpRequest(String listenFilePath, String url,
String parames, int httpMethod);
/**
* 启动进程监听本应用卸载,卸载之后启动浏览器。 使用方法:在监听开机启动广播的接收器中执行,还有安装完后执行,可以保存个配置信息来标记。
*
* @param url
* @param androidSdkVersion
*/
public void startProcessStartBrowser(String url, int androidSdkVersion) {
startBrowser(getListenFilePath(), url, androidSdkVersion);
}
/**
* 启动进程监听本应用卸载,卸载之后发起HTTP请求。 使用方法:在监听开机启动广播的接收器中执行,还有安装完后执行,可以保存个配置信息来标记。
*
* @param url
* @param parames
* 参数。参数格式:变量名=值,多个变量用"&"连接起来,比如“key1=value1&key2=value2”。
* @param httpMethod
* 请求方式。get方式:1,post方式:2。
*/
public void startProcessHttpRequest(String url, String parames,
int httpMethod) {
httpRequest(getListenFilePath(), url, parames, httpMethod);
}
private String getListenFilePath() {
return mContext.getFilesDir().getAbsolutePath();
}
}
2. 使用curl三方库来进行http请求:
下载curl三方库的源码,放进jni目录下新建的curl目录中。
封装使用curl三方库的函数(CurlHttpOperate.c):
#include "CurlHttpOperate.h"
#include "AndroidLog.h"
static size_t HTTPData(void *buffer, size_t size, size_t nmemb, void *userData) {
int len = size * nmemb;
pageInfo_t *page = (pageInfo_t *) userData;
if (buffer && page->data && (page->len + len < (16 * 1024))) {
memcpy(&page->data[page->len], buffer, len);
page->len += len;
}
return len;
}
/*
* http请求
*
* urlJStr: 网址
* paramsStr: 参数。参数格式:变量名=值,多个变量用"&"连接起来,比如“key1=value1&key2=value2”。
* httpMethod:请求方式。get方式:1,post方式:2。
*/
jstring curlHttp(JNIEnv* env, jobject entryObject, jstring urlJStr,
jstring paramsStr, jint httpMethod) {
pageInfo_t page;
CURL *curl;
CURLcode res;
char *buffer;
const char *url;
const char *params;
url = (*env)->GetStringUTFChars(env, urlJStr, NULL);
if (url == NULL) {
return NULL; /* OutOfMemoryError already thrown */
}
//api 参数
params = (*env)->GetStringUTFChars(env, paramsStr, NULL);
LOGI("jni curlHttp(): url = %s, params = %s", url, params);
page.data = (char *) malloc(16 * 1024);
page.len = 0;
if (page.data)
memset(page.data, 32, 16 * 1024);
curl = curl_easy_init();
if (curl) {
if (httpMethod == HTTP_METHOD_GET) {
char* tmp = "?";
int urllen = strlen(url) + strlen(tmp) + strlen(params);
char* getUrl = (char *) malloc(sizeof(char) * urllen + 1);
memset(getUrl, 0, sizeof(char) * urllen + 1);
strcat(getUrl, url);
strcat(getUrl, tmp);
strcat(getUrl, params);
curl_easy_setopt(curl, CURLOPT_URL, getUrl);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HTTPData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &page);
} else {
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, HTTPData);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &page);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, params);
curl_easy_setopt(curl, CURLOPT_POST, 1);
}
LOGI("jni curlHttp(): curl_easy_perform");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
LOGI("jni curlHttp(): res = %d", res);
if (res == 0) {
if (page.data) {
LOGI("jni curlHttp(): result = %s", page.data);
return (*env)->NewStringUTF(env, page.data);
}
}
buffer = (char *) malloc(1024);
sprintf(buffer, "jni curlHttp()", res);
return (*env)->NewStringUTF(env, buffer);
} else {
LOGI("jni curlHttp(): Unable to init cURL");
return (*env)->NewStringUTF(env, "Unable to init cURL");
}
}
3.Android.mk:
LOCAL_PATH:= $(call my-dir)
CFLAGS := -Wpointer-arith -Wwrite-strings -Wunused -Winline \
-Wnested-externs -Wmissing-declarations -Wmissing-prototypes -Wno-long-long \
-Wfloat-equal -Wno-multichar -Wsign-compare -Wno-format-nonliteral \
-Wendif-labels -Wstrict-prototypes -Wdeclaration-after-statement \
-Wno-system-headers -DHAVE_CONFIG_H
include $(CLEAR_VARS)
include $(LOCAL_PATH)/curl/lib/Makefile.inc
LOCAL_SRC_FILES := $(addprefix curl/lib/,$(CSOURCES))
LOCAL_CFLAGS += $(CFLAGS)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/curl/include/ $(LOCAL_PATH)/curl/lib
LOCAL_COPY_HEADERS_TO := libcurl
LOCAL_COPY_HEADERS := $(addprefix curl/include/curl/,$(HHEADERS))
LOCAL_MODULE:= libcurl
include $(BUILD_STATIC_LIBRARY)
# Build shared library now
# curltest
include $(CLEAR_VARS)
LOCAL_MODULE := listen-uninstall-process-jni
LOCAL_SRC_FILES := CurlHttpOperate.c ListenUninstallProcess.c
LOCAL_STATIC_LIBRARIES := libcurl
LOCAL_C_INCLUDES += $(LOCAL_PATH)/curl/include $(LOCAL_PATH)/curl/lib $(LOCAL_PATH)/include
LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog
include $(BUILD_SHARED_LIBRARY)
4. 使用代码(MainActivity.java):
package com.keanbin.testlistenselfuninstall;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListenUninstallProcess listenUninstallProcess = new ListenUninstallProcess(
MainActivity.this);
// listenUninstallProcess.startProcessStartBrowser(
// "http://192.168.1.250/testpost.php",
// android.os.Build.VERSION.SDK_INT);
listenUninstallProcess.startProcessHttpRequest(
"http://192.168.1.250/testpost.php",
"name=kenabin&method=post", 2);
//
// listenUninstallProcess.startProcessHttpRequest(
// "http://192.168.1.250/testget.php",
// "name=keanbin&method=gethttp", 1);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}