注:功能为在apk被卸载时,打开卸载反馈的web页面,其中linux的C代码源自http://www.cnblogs.com/zealotrouge/p/3182617.html,本文流程主要是生成.so流程、加载使用等
(核心功能代码版权是http://www.cnblogs.com/zealotrouge/的热气球所有,本人只是稍加改动)
注2:cygwin、NDK等其他配置请google
1. 在自己的apk中生成用于启动检测卸载的UninstallStatictics.java类
package jp.accessport.gamebox.utils; import java.lang.reflect.Method; import android.content.Context; import android.os.Build; import android.util.Log; //被卸载的统计==处理 public class UninstallStatictics { //log private static final String TAG = "UninstallStatictics"; //生成的监听进程的库 private static final String S_ObserverLibrary = "observe-uninstall"; //监听进程pid private int m_nObserverProcessPid = -1; //初始化监听进程:此处无需实现 private native int initObserverProcess(String strUserSerial); // static { Log.d(TAG, "load observer process lib --> " + S_ObserverLibrary); System.loadLibrary(S_ObserverLibrary); } //启动后调用,用于启动监听进程 public void start(Context context){ //API level小于17,不需要获取userSerialNumber if (Build.VERSION.SDK_INT < 17) m_nObserverProcessPid = initObserverProcess(null); //否则,需要获取userSerialNumber else m_nObserverProcessPid = initObserverProcess(getUserSerial(context)); } //由于targetSdkVersion低于17,只能通过反射获取 //需要添加权限:<uses-permission android:name="android.permission.READ_PHONE_STATE" /> private String getUserSerial(Context context){ Object objUserManager = context.getSystemService("user"); if (objUserManager == null){ Log.e(TAG, "userManager not exist!!!"); return null; } String strSN = null; try { Class<?> classSP = Class.forName("android.os.SystemProperties"); Method methodGet = classSP.getMethod("get", String.class); strSN = (String)methodGet.invoke(classSP, "ro.serialno"); } catch (Exception e){ e.printStackTrace(); } return strSN; } }
3. 使用javah命令生成jp_accessport_gamebox_utils_UninstallStatictics.h头文件(命名==是有规则的,不用自己标示名字),因为使用的不是简单类型,而是包含如Context==的android的类库,所以生成过程中产生各种问题,解决方法参见http://blog.csdn.net/hejinjing_tom_com/article/details/8125648
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class jp_accessport_gamebox_utils_UninstallStatictics */ #ifndef _Included_jp_accessport_gamebox_utils_UninstallStatictics #define _Included_jp_accessport_gamebox_utils_UninstallStatictics #ifdef __cplusplus extern "C" { #endif /* * Class: jp_accessport_gamebox_utils_UninstallStatictics * Method: initObserverProcess * Signature: (Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_jp_accessport_gamebox_utils_UninstallStatictics_initObserverProcess (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif
4. 生成.h文件后,就可以写代码实现Java_jp_accessport_gamebox_utils_UninstallStatictics_initObserverProcess功能了,我生成的实现类时observe-file.c,没有对应.h文件的名字,因为在生成.so后,就不需要.h文件了,只要有.so的实现即可(且.so的名字也可随意,但内部的函数名不能随意,这个跟native的调用命名相关),代码如下
//注:因为生成.so文件后,所以不再需要对应的jp_accessport_gamebox_utils_UninstallStatictics.h头文件
//所以去掉默认的#include "jp_accessport_gamebox_utils_UninstallStatictics.h"
#include <jni.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <android/log.h>
//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_WARNING(tag, msg) __android_log_write(ANDROID_LOG_WARNING, tag, msg)
//#define LOG_ERROR(tag, msg) __android_log_write(ANDROID_LOG_ERROR, tag, msg)
#undef jp_accessport_gamebox_utils_UninstallStatictics_MODE_PRIVATE
#define jp_accessport_gamebox_utils_UninstallStatictics_MODE_PRIVATE 0L
#undef jp_accessport_gamebox_utils_UninstallStatictics_MODE_WORLD_READABLE
#define jp_accessport_gamebox_utils_UninstallStatictics_MODE_WORLD_READABLE 1L
#undef jp_accessport_gamebox_utils_UninstallStatictics_MODE_WORLD_WRITEABLE
#define jp_accessport_gamebox_utils_UninstallStatictics_MODE_WORLD_WRITEABLE 2L
#undef jp_accessport_gamebox_utils_UninstallStatictics_MODE_APPEND
#define jp_accessport_gamebox_utils_UninstallStatictics_MODE_APPEND 32768L
#undef jp_accessport_gamebox_utils_UninstallStatictics_MODE_MULTI_PROCESS
#define jp_accessport_gamebox_utils_UninstallStatictics_MODE_MULTI_PROCESS 4L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_AUTO_CREATE
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_AUTO_CREATE 1L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_DEBUG_UNBIND
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_DEBUG_UNBIND 2L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_NOT_FOREGROUND
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_NOT_FOREGROUND 4L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_ABOVE_CLIENT
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_ABOVE_CLIENT 8L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_ALLOW_OOM_MANAGEMENT
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_ALLOW_OOM_MANAGEMENT 16L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_WAIVE_PRIORITY
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_WAIVE_PRIORITY 32L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_IMPORTANT
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_IMPORTANT 64L
#undef jp_accessport_gamebox_utils_UninstallStatictics_BIND_ADJUST_WITH_ACTIVITY
#define jp_accessport_gamebox_utils_UninstallStatictics_BIND_ADJUST_WITH_ACTIVITY 128L
#undef jp_accessport_gamebox_utils_UninstallStatictics_CONTEXT_INCLUDE_CODE
#define jp_accessport_gamebox_utils_UninstallStatictics_CONTEXT_INCLUDE_CODE 1L
#undef jp_accessport_gamebox_utils_UninstallStatictics_CONTEXT_IGNORE_SECURITY
#define jp_accessport_gamebox_utils_UninstallStatictics_CONTEXT_IGNORE_SECURITY 2L
#undef jp_accessport_gamebox_utils_UninstallStatictics_CONTEXT_RESTRICTED
#define jp_accessport_gamebox_utils_UninstallStatictics_CONTEXT_RESTRICTED 4L
#undef jp_accessport_gamebox_utils_UninstallStatictics_RESULT_CANCELED
#define jp_accessport_gamebox_utils_UninstallStatictics_RESULT_CANCELED 0L
#undef jp_accessport_gamebox_utils_UninstallStatictics_RESULT_OK
#define jp_accessport_gamebox_utils_UninstallStatictics_RESULT_OK -1L
#undef jp_accessport_gamebox_utils_UninstallStatictics_RESULT_FIRST_USER
#define jp_accessport_gamebox_utils_UninstallStatictics_RESULT_FIRST_USER 1L
#undef jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_DISABLE
#define jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_DISABLE 0L
#undef jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_DIALER
#define jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_DIALER 1L
#undef jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_SHORTCUT
#define jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_SHORTCUT 2L
#undef jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_SEARCH_LOCAL
#define jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_SEARCH_LOCAL 3L
#undef jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_SEARCH_GLOBAL
#define jp_accessport_gamebox_utils_UninstallStatictics_DEFAULT_KEYS_SEARCH_GLOBAL 4L
#ifdef __cplusplus
extern "C" {
#endif
//全局变量
static char TAG[] = "UninstallStatictics.initObserverProcess";
static jboolean isCopy = JNI_TRUE;
static const char APP_DIR[] = "/data/data/jp.accessport.gamebox";
static const char APP_FILES_DIR[] = "/data/data/jp.accessport.gamebox/files";
static const char APP_OBSERVED_FILE[] = "/data/data/jp.accessport.gamebox/files/observedFile";
static const char APP_LOCK_FILE[] = "/data/data/jp.accessport.gamebox/files/lockFile";
/************************************************************************/
/* class: jp_accessport_gamebox_utils_UninstallStatictics
/* method: initObserverProcess
/* return: 子进程pid
/************************************************************************/
JNIEXPORT jint JNICALL Java_jp_accessport_gamebox_utils_UninstallStatictics_initObserverProcess(JNIEnv *env, jobject obj, jstring userSerial)
{
jstring tag = (*env)->NewStringUTF(env, TAG);
// LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "init observer"), &isCopy));
//fork子进程,以执行轮询任务
pid_t pid = fork();
if (pid < 0)
{
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork failed !!!"), &isCopy));
exit(1);
}
else if (pid == 0)
{
//若监听文件所在文件夹不存在,则创建
FILE* pFilesDir = fopen(APP_FILES_DIR, "r");
if (pFilesDir == NULL)
{
int nRet = mkdir(APP_FILES_DIR, S_IRWXU | S_IRWXG | S_IXOTH);
if (nRet == -1)
{
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "mkdir failed !!!"), &isCopy));
exit(1);
}
}
fclose(pFilesDir);
//若被监听文件不存在,则创建文件
FILE* pObservedFile = fopen(APP_OBSERVED_FILE, "r");
if (pObservedFile == NULL)
pObservedFile = fopen(APP_OBSERVED_FILE, "w");
fclose(pObservedFile);
//创建琐文件,通过检查加锁状态来保证只有一个监听进程
int nLockFileDescriptor = open(APP_LOCK_FILE, O_RDONLY);
if (nLockFileDescriptor == -1)
nLockFileDescriptor = open(APP_LOCK_FILE, O_CREAT);
int nLockRet = flock(nLockFileDescriptor, LOCK_EX | LOCK_NB);
if (nLockRet == -1)
{
// LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "observed by another process"), &isCopy));
exit(0);
}
// LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "observed by child process"), &isCopy));
//分配空间,以便读取event
void* pBuf = malloc(sizeof(struct inotify_event));
if (pBuf == NULL)
{
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &isCopy));
exit(1);
}
//分配空间,以便打印mask
int nMaskStringLen = 7 + 10 + 1; //mask=0x占7字节,32位整形数最大为10位,转换为字符串占10字节,'\0'占1字节
char* pMaskString = malloc(nMaskStringLen);
if (pMaskString == NULL)
{
if (pBuf != NULL)
{
free(pBuf);
pBuf = NULL;
}
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &isCopy));
exit(1);
}
//开始监听
// LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "start observe"), &isCopy));
//初始化
int nFileDescriptor = inotify_init();
if (nFileDescriptor < 0)
{
if (pMaskString != NULL)
{
free(pMaskString);
pMaskString = NULL;
}
if (pBuf != NULL)
{
free(pBuf);
pBuf = NULL;
}
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_init failed !!!"), &isCopy));
exit(1);
}
//添加被监听文件到监听列表
int nWatchDescriptor = inotify_add_watch(nFileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS);
if (nWatchDescriptor < 0)
{
if (pMaskString != NULL)
{
free(pMaskString);
pMaskString = NULL;
}
if (pBuf != NULL)
{
free(pBuf);
pBuf = NULL;
}
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &isCopy));
exit(1);
}
//
while (1)
{
//read会阻塞进程
size_t nReadBytes = read(nFileDescriptor, pBuf, sizeof(struct inotify_event));
//打印mask
snprintf(pMaskString, nMaskStringLen, "mask=0x%x\0", ((struct inotify_event *)pBuf)->mask);
// LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, pMaskString), &isCopy));
//若文件被删除,则可能被卸载,还需进一步判断app文件是否存在
if (IN_DELETE_SELF == ((struct inotify_event *)pBuf)->mask)
{
FILE* pAppDir = fopen(APP_DIR, "r");
//确认已卸载
if (pAppDir == NULL)
{
inotify_rm_watch(nFileDescriptor, nWatchDescriptor);
break;
}
//未卸载
else
{
fclose(pAppDir);
//重新创建监听文件,并重新监听
FILE* pObservedFile = fopen(APP_OBSERVED_FILE, "w");
fclose(pObservedFile);
nWatchDescriptor = inotify_add_watch(nFileDescriptor, APP_OBSERVED_FILE, IN_ALL_EVENTS);
if (nWatchDescriptor < 0)
{
if (pMaskString != NULL)
{
free(pMaskString);
pMaskString = NULL;
}
if (pBuf != NULL)
{
free(pBuf);
pBuf = NULL;
}
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &isCopy));
exit(1);
}
}
}
}
//释放资源
if (pMaskString != NULL)
{
free(pMaskString);
pMaskString = NULL;
}
if (pBuf != NULL)
{
free(pBuf);
pBuf = NULL;
}
//停止监听
// LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "stop observe"), &isCopy));
if (userSerial == NULL)
{
// 执行命令am start -a android.intent.action.VIEW -d $(url)
execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://www.so.com", (char *)NULL);
}
else
{
// 执行命令am start --user userSerial -a android.intent.action.VIEW -d $(url)
execlp("am", "am", "start", "--user", (*env)->GetStringUTFChars(env, userSerial, &isCopy), "-a", "android.intent.action.VIEW", "-d", "http://www.so.com", (char *)NULL);
}
// 执行命令失败log
// LOG_ERROR((*env)->GetStringUTFChars(env, tag, &isCopy)
// , (*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "exec AM command failed !!!"), &isCopy));
}
else
{
// 父进程直接退出,使子进程被init进程领养,以避免子进程僵死,同时返回子进程pid
return pid;
}
}
#ifdef __cplusplus
}
#endif
# 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. # LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := observe-uninstall #生成的.so文件的名字,可随意起 LOCAL_SRC_FILES := observe-file.c #打包编译的文件,此处即是自己生成的用于监控文件是否删除的实现文件 #这个是不能少的,因为observe-file.c中使用了很多的linux的库,此处若不包含,在使用cygwin时编译通不过,就这行,还得我一顿编译不通过啊... LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../../platforms/android-14/arch-arm/usr/include LOCAL_EXPORT_LDLIBS:= -llog #此处忘了哪儿写的了,好像是链接需要的.so库文件 include $(BUILD_SHARED_LIBRARY) include $(CLEAR_VARS)因为我原来是windows下的C++,linux的C编程是真的不熟悉,看到网上关于android的JNI可以调用.dll文件,但反复想过后感觉windows下的api实现,在以linux为基础的android系统中估计不能发挥作用,最后还是硬着头皮整了linux下的.so
另:个人感觉android可以调用.dll主要是指.dll是自己的功能实现,不依赖于系统,否则估计调用不会成功
另2:原来不知道咋搞,找了网上的教程很多,但有的编译不了,有的根本就不行,我连cocos2d-x都实验了,最后还是有问题(说明自己这块是真的不熟悉啊)
6. 使用cygwin,cd到test-jni2目录中:cd /cygdrive/D/ProgramFiles/android-ndk-r8e/samples/test-jni2/,然后使用命令$NDK_ROOT/ndk-build即可进行编译
注1:NDK_ROOT是cygwin的安装目录下D:\ProgramFiles\cygwin\home\gxj\.bash_profile文件的最后添加的执行ndk根目录的变量名,如下面代码的最下面两行的名字,自己也可以修改
# To the extent possible under law, the author(s) have dedicated all # copyright and related and neighboring rights to this software to the # public domain worldwide. This software is distributed without any warranty. # You should have received a copy of the CC0 Public Domain Dedication along # with this software. # If not, see <http://creativecommons.org/publicdomain/zero/1.0/>. # base-files version 4.1-1 # ~/.bash_profile: executed by bash(1) for login shells. # The latest version as installed by the Cygwin Setup program can # always be found at /etc/defaults/etc/skel/.bash_profile # Modifying /etc/skel/.bash_profile directly will prevent # setup from updating it. # The copy in your home directory (~/.bash_profile) is yours, please # feel free to customise it to create a shell # environment to your liking. If you feel a change # would be benifitial to all, please feel free to send # a patch to the cygwin mailing list. # User dependent .bash_profile file # source the users bashrc if it exists if [ -f "${HOME}/.bashrc" ] ; then source "${HOME}/.bashrc" fi # Set PATH so it includes user's private bin if it exists # if [ -d "${HOME}/bin" ] ; then # PATH="${HOME}/bin:${PATH}" # fi # Set MANPATH so it includes users' private man if it exists # if [ -d "${HOME}/man" ]; then # MANPATH="${HOME}/man:${MANPATH}" # fi # Set INFOPATH so it includes users' private info if it exists # if [ -d "${HOME}/info" ]; then # INFOPATH="${HOME}/info:${INFOPATH}" # fi NDK_ROOT=/cygdrive/D/ProgramFiles/android-ndk-r8e export NDK_ROOT
注2:使用编译命令时要使用“$NDK_ROOT/ndk-build”而不是“NDK_ROOT/ndk-build”,之前编译时,因为在光标前有默认的符号“$”,以为编译命令不用输入了,搞了几次都没成功,最后整个copy进去才发现命令“$NDK_ROOT/ndk-build”才有效,无语
7. 编译成功后生成D:\ProgramFiles\android-ndk-r8e\samples\test-jni2\libs\armeabi\libobserve-uninstall.so文件,将libobserve-uninstall.so文件拷贝到自己的apk工程目录E:\Company\Client\Android\GameBox\android_app_gamebox\libs\armeabi\下,注意别直接放在libs下面,要放在libs\armeabi\下面,否则打包.apk时不能打包进去
另:生成的.so的名字是lib+自己的命名,但在android中调用时,若使用System.loadLibrary时,则不要带前面的lib后后边的后缀.so,即System.loadLibrary("observe-uninstall");,当然若是使用System.load,好像得加上全路径了好像,名称也得是全名
8. 刷新编译.apk工程,测试,则可发现在启动自己的apk后,卸载apk则会弹出网页www.so.com,以上