Android如何解决多次fork进程的问题

豌豆荚,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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值