豌豆荚,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); }