豌豆荚,360等一些软件在卸载后或弹出一个调查问卷的页面,这个是怎么做的呢?
有两种思路:
第一种是app本身检测卸载这个窗口,在按下卸载的瞬间调用内部浏览器,这种思路没试过,不知是否可行,就算可行的话,我认为最后调用失败的几率还是很大的,一旦app本身进程被关掉了,那么什么都做不了了
第二种是fork出一个子进程,一直监听data/data/com.xxxx这个目录,或者这个目录里的文件,那么在app卸载的时候data/data/com.xxx这个目录将被删除掉,此时父进程虽然被关掉了,但是fork出的子进程会被init进程领养变成孤儿进程,就由这个孤儿进程调用内部自带的浏览器,发送一个请求到服务器,返回调查问卷页面。缺点显而易见:由于fork的性质,内存消耗将有所增加。
我们这里不讨论如何fork子进程,主要讨论一下如何保证父进程只fork一个子进程。一般来说我们可以在application的pause里调用fork子进程,也就是在app将要返回launcher之前。我们可以在这里加一层限制,如果本身的application的守护进程存在的话那么不进行fork(如果存在守护进程的话)。但是这里会有一个问题,也就是守护进程被系统强制退出怎么办?答案是app会再次fork出那么子进程,所以就引发多次fork子进程的问题。
这个也有两种解决思路:
1.设置一个听文件f,将文件f进行加锁,如果fork的时候发现无法对文件f加锁,那么则表示现在已经存在了fork出的子进程。那么我们将这个监听文件存在哪呢?我们可以存在data/data/com.xx自己本身app的目录下,或者存在sd卡下。正常下这种方法是可以的,但是在某种异常的情况下可能导致被监听的文件锁死。在sdk卡里还好,最多就是卸载时无法挂起浏览器,那么在data下的话就会有一个很严重的风险:app无法卸载。虽然这种情况几率很小,但是不能保证它一定不会发生。
2.还有一种方法就是在fork之前再加一层判断,通过ps获取所有进程列表,从中筛选出fork出的子进程是否存在。代码很简单,只要分两种情况判断一下就可以了
<1>application的守护进程存在,那么判断ppid是否等会父进程的pid,name是否与包名相等
<2> application的守护进程不存在,那么判断ppid是否等于init进程的pid,name是否与包名相等
==》
if (temp[8].trim().equals(context.getPackageName().trim()) && (temp[2].trim().equals(mypid + "") || temp[2].trim().equals("1"))) {....}
完整方法:
package com.sanyinchen.hellojni;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.os.*;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.Process;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AppUninstall {
private static SharedPreferences sharedPreferences;
private static SharedPreferences.Editor editor;
static {
System.loadLibrary("hellojni");
}
public static void openUrlWhenUninstall(Context context, String openUrl) {
sharedPreferences = context.getSharedPreferences("process", context.MODE_PRIVATE);
editor = sharedPreferences.edit();
String pid = sharedPreferences.getString("pid", "0");
if (checkChildProcess(context, pid)) {
Log.i("OnAppUninstall", "已经开启过守护进程");
return;
}
Log.i("OnAppUninstall", "执行开启过守护进程");
String dirStr = context.getApplicationInfo().dataDir + "/lib";
String activity = null;
if (checkInstall(context, "com.android.browser")) {
activity = "com.android.browser/com.android.browser.BrowserActivity";
}
String action = "android.intent.action.VIEW";
String data = openUrl;
String gpid;
if (Build.VERSION.SDK_INT < 17) {
gpid = onUninstall(dirStr, activity, action, data, null);
} else {
gpid = onUninstall(dirStr, activity, action, data, getUserSerial(context));
}
if (gpid != null && !gpid.equals("") && !gpid.equals("0")) {
Log.i("OnAppUninstall", "获得守护进程pid-->" + gpid);
editor.putString("pid", gpid);
editor.commit();
}
}
private static boolean checkInstall(Context context, String packageName) {
try {
PackageInfo packageInfo = context.getPackageManager()
.getPackageInfo(packageName, 0);
if (packageInfo == null) {
return false;
} else {
return true;
}
} catch (Exception e) {
return false;
}
}
private static String getUserSerial(Context ctx) {
Object userManager = ctx.getSystemService("user");
if (userManager == null) {
return null;
}
try {
Method myUserHandleMethod = android.os.Process.class.getMethod(
"myUserHandle", (Class<?>[]) null);
Object myUserHandle = myUserHandleMethod.invoke(
android.os.Process.class, (Object[]) null);
Method getSerialNumberForUser = userManager.getClass().getMethod(
"getSerialNumberForUser", myUserHandle.getClass());
long userSerial = (Long) getSerialNumberForUser.invoke(userManager,
myUserHandle);
return String.valueOf(userSerial);
} catch (NoSuchMethodException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
return null;
}
private static native String onUninstall(String dirStr, String activity,
String action, String data, String userSerial);
private static boolean checkChildProcess(Context context, String mpid) {
boolean resflag = false;
int mypid = android.os.Process.myPid();
Log.i("OnAppUninstall", "mypid-->" + mypid);
BufferedReader in = null;
long starttime = System.currentTimeMillis();
List<Map<String, String>> listdata = new ArrayList<Map<String, String>>();
try {
Process p = Runtime.getRuntime().exec("ps");
in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
String[] temp;
boolean flag = false;
int length = 0;
while ((line = in.readLine()) != null) {
//Log.i("OnAppUninstall", "ps-->" + line);
if (!flag) {
flag = true;
continue;
}
line = line.replaceAll(" +", " ");
temp = line.split(" ");
System.out.println(context.getPackageName());
System.out.println(temp[8]);
if (temp[8].trim().equals(context.getPackageName().trim()) && (temp[2].trim().equals(mypid + "") || temp[2].trim().equals("1"))) {
Log.i("OnAppUninstall", "get it");
Map<String, String> map = new HashMap<String, String>();
map.put("pid", temp[1]);
map.put("pname", temp[8]);
listdata.add(map);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
in = null;
}
}
long endtime = System.currentTimeMillis();
long spendtime = endtime - starttime;
for (int i = 0; i < listdata.size(); i++) {
if (listdata.get(i).get("pid").equals(mpid)) {
resflag = true;
} else {
Log.w("OnAppUninstall", "捕获到多余守护进程-->" + listdata.get(i).get("pid") + " " + listdata.get(i).get("pname"));
// try {
// android.os.Process.killProcess(Integer.valueOf(listdata.get(i).get("pid").trim()));
// } catch (NumberFormatException e) {
// e.printStackTrace();
// }
}
Log.i("OnAppUninstall", listdata.get(i).get("pid") + " " + listdata.get(i).get("pname"));
}
Log.i("OnAppUninstall", "spendtime-->" + spendtime);
return resflag;
}
}
参考fork代码:
/*
* 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 "com_sanyinchen_hellojni_AppUninstall.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)
/* 内全局变量begin */
static char c_TAG[] = "OnAppUninstall";
static jboolean b_IS_COPY = JNI_TRUE;
JNIEXPORT jstring JNICALL Java_com_sanyinchen_hellojni_AppUninstall_onUninstall
(JNIEnv* env,
jobject thiz, jstring dirStr, jstring activity, jstring action,
jstring data, jstring userSerial) {
jstring tag = (*env)->NewStringUTF(env, c_TAG);
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "start------------------"), &b_IS_COPY));
//初始化log
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "init OK"), &b_IS_COPY));
//fork子进程,以执行轮询任务
pid_t pid = fork();
char showpid[10];
sprintf(showpid, "%d\0", pid);
LOG_INFO((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, showpid), &b_IS_COPY));
if (pid < 0) {
//出错log
LOG_ERROR((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork failed !!!"), &b_IS_COPY));
} else if (pid == 0) {
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "childen process"), &b_IS_COPY));
int fileDescriptor = inotify_init();
if (fileDescriptor < 0) {
//初始化文件监听器失败
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_init failed !!!"), &b_IS_COPY));
exit(1);
}
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "will creat a lockfile"), &b_IS_COPY));
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),(*env)->GetStringUTFChars(env, dirStr, &b_IS_COPY));
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "creat a new process"), &b_IS_COPY));
//子进程注册"/data/data/{package}"目录监听器
int watchDescriptor;
watchDescriptor = inotify_add_watch(fileDescriptor,
(*env)->GetStringUTFChars(env, dirStr, &b_IS_COPY), IN_DELETE);
if (watchDescriptor < 0) {
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "inotify_add_watch failed !!!"), &b_IS_COPY));
exit(1);
}
//分配缓存,以便读取event,缓存大小=一个struct inotify_event的大小,这样一次处理一个event
void *p_buf = malloc(sizeof(struct inotify_event));
if (p_buf == NULL) {
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "malloc failed !!!"), &b_IS_COPY));
exit(1);
}
//开始监听
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "start observer"), &b_IS_COPY));
size_t readBytes = read(fileDescriptor, p_buf,
sizeof(struct inotify_event));
//等待5秒
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "sleep 2 seconds"), &b_IS_COPY));
sleep(2);
//如果是覆盖安装跳过
FILE *p_appDir = fopen("/data/data/com.nuomi", "r");
if (p_appDir != NULL) {
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "reinstall"), &b_IS_COPY));
exit(1);
}
//read会阻塞进程,走到这里说明收到目录被删除的事件,注销监听器
free(p_buf);
inotify_rm_watch(fileDescriptor, IN_DELETE);
//目录不存在log
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "uninstalled"), &b_IS_COPY));
//执行命令am start -a android.intent.action.VIEW -d http://ww.baidu.com
// execlp("am", "am", "start", "-a", "android.intent.action.VIEW", "-d", "http://ww.baidu.com", (char *)NULL);
//4.2以上的系统由于用户权限管理更严格,需要加上 --user 0
if (userSerial == NULL) {
// 执行命令am start -a android.intent.action.VIEW -d $(url)
execlp("am", "am", "start", "-a", "android.intent.action.VIEW",
"-d", (*env)->GetStringUTFChars(env, data, &b_IS_COPY),
(char *) NULL);
} else {
if (activity == NULL
|| (*env)->GetStringUTFLength(env, activity) < 1) {
execlp("am", "am", "start", "--user",
(*env)->GetStringUTFChars(env, userSerial, &b_IS_COPY),
"-a",
(*env)->GetStringUTFChars(env, action, &b_IS_COPY),
"-d", (*env)->GetStringUTFChars(env, data, &b_IS_COPY),
(char *) NULL);
} else {
if (action == NULL
|| (*env)->GetStringUTFLength(env, action) < 1) {
if (data == NULL
|| (*env)->GetStringUTFLength(env, data) < 1) {
execlp("am", "am", "start", "--user",
(*env)->GetStringUTFChars(env, userSerial,
&b_IS_COPY), "-n",
(*env)->GetStringUTFChars(env, activity,
&b_IS_COPY), (char *) NULL);
} else {
execlp("am", "am", "start", "--user",
(*env)->GetStringUTFChars(env, userSerial,
&b_IS_COPY), "-n",
(*env)->GetStringUTFChars(env, activity,
&b_IS_COPY), "-d",
(*env)->GetStringUTFChars(env, data,
&b_IS_COPY), (char *) NULL);
}
} else {
if (data == NULL
|| (*env)->GetStringUTFLength(env, data) < 1) {
execlp("am", "am", "start", "--user",
(*env)->GetStringUTFChars(env, userSerial,
&b_IS_COPY), "-n",
(*env)->GetStringUTFChars(env, activity,
&b_IS_COPY), "-a",
(*env)->GetStringUTFChars(env, action,
&b_IS_COPY), (char *) NULL);
} else {
execlp("am", "am", "start", "--user",
(*env)->GetStringUTFChars(env, userSerial,
&b_IS_COPY), "-n",
(*env)->GetStringUTFChars(env, activity,
&b_IS_COPY), "-a",
(*env)->GetStringUTFChars(env, action,
&b_IS_COPY), "-d",
(*env)->GetStringUTFChars(env, data,
&b_IS_COPY), (char *) NULL);
}
}
}
}
} else {
//父进程直接退出,使子进程被init进程领养,以避免子进程僵死
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "fork pid > 0"), &b_IS_COPY));
}
LOG_DEBUG((*env)->GetStringUTFChars(env, tag, &b_IS_COPY),
(*env)->GetStringUTFChars(env, (*env)->NewStringUTF(env, "end------------------"), &b_IS_COPY));
return (*env)->NewStringUTF(env, showpid);
}