android 中保证进程不被杀死

来源:互联网 发布:淘宝宝贝评价采集工具 编辑:程序博客网 时间:2024/05/17 07:18

前言

开发一个需要常住后台的App其实是一件非常头疼的事情,不仅要应对国内各大厂商的ROM,还需要应对各类的安全管家…虽然不断的研究各式各样的方法,但是效果并不好,比如任务管理器把App干掉,服务就起不来了… 
一般来说:有以下几种可行的方法来使自己的进程不被杀死

1 进程不被杀死的方法

1、提高Service的优先级:这个,也只能说在系统内存不足需要回收资源的时候,优先级较高,不容易被回收,然并卵…

2、提高Service所在进程的优先级:效果不是很明显

3、在onDestroy方法里重启service:这个倒还算挺有效的一个方法,但是,直接干掉进程的时候,onDestroy方法都进不来,更别想重启了

4、broadcast广播:和第3种一样,没进入onDestroy,就不知道什么时候发广播了,另外,在Android4.4以上,程序完全退出后,就不好接收广播了,需要在发广播的地方特定处理

5、放到System/app底下作为系统应用:这个也就是平时玩玩,没多大的实际意义。这个功能需要root,实用性不大

6、Service的onStartCommand方法,返回START_STICKY,这个也主要是针对系统资源不足而导致的服务被关闭,还是有一定的道理的。

7、开启守护进程监听,如果监听到主进程被杀死就启动主进程。但是这个在5.0以后的系统由于守护进程与主进程是同一进程组,一样的会被杀死。但是我们可以做一下修改,参考一下网上大神的代码.

思想就是:让子进程脱离出来,不要受到主进程的影响

/** * srvname 进程名 * sd 之前创建子进程的pid写入的文件路径 */ int start(int argc, char* srvname, char* sd) {     pthread_t id;     int ret;     struct rlimit r;    int pid = fork();      LOGI("fork pid: %d", pid);      if (pid < 0) {          LOGI("first fork() error pid %d,so exit", pid);          exit(0);      } else if (pid != 0) {          LOGI("first fork(): I'am father pid=%d", getpid());          //exit(0);      } else { //  第一个子进程          LOGI("first fork(): I'am child pid=%d", getpid());          setsid();          LOGI("first fork(): setsid=%d", setsid());          umask(0); //为文件赋予更多的权限,因为继承来的文件可能某些权限被屏蔽          int pid = fork();          if (pid == 0) { // 第二个子进程              FILE  *fp;              sprintf(sd,"%s/pid",sd);              if((fp=fopen(sd,"a"))==NULL) {//打开文件 没有就创建                  LOGI("%s文件还未创建!",sd);                  ftruncate(fp, 0);                  lseek(fp, 0, SEEK_SET);              }              fclose(fp);              fp=fopen(sd,"rw");              if(fp>0){                  char buff1[6];                  int p = 0;                  memset(buff1,0,sizeof(buff1));                  fseek(fp,0,SEEK_SET);                  fgets(buff1,6,fp);  //读取一行(pid)                  LOGI("读取的进程号:%s",buff1);                  if(strlen(buff1)>1){ // 有值                      kill(atoi(buff1), SIGTERM); // 把上一次的进程干掉,防止重复执行                      LOGI("杀死进程,pid=%d",atoi(buff1));                  }              }              fclose(fp);              fp=fopen(sd,"w");              char buff[100];              int k = 3;              if(fp>0){                  sprintf(buff,"%lu",getpid());                  fprintf(fp,"%s\n",buff); // 把进程号写入文件              }              fclose(fp);              fflush(fp);              LOGI("I'am child-child pid=%d", getpid());              chdir("/"); //<span style="font-family: Arial, Helvetica, sans-serif;">修改进程工作目录为根目录,chdir(“/”)</span>              //关闭不需要的从父进程继承过来的文件描述符。              if (r.rlim_max == RLIM_INFINITY) {                  r.rlim_max = 1024;              }              int i;              for (i = 0; i < r.rlim_max; i++) {                  close(i);              }              umask(0);              ret = pthread_create(&id, NULL, (void *) thread, srvname); // 开启线程,轮询去监听启动服务              if (ret != 0) {                  printf("Create pthread error!\n");                  exit(1);              }              int stdfd = open ("/dev/null", O_RDWR);              dup2(stdfd, STDOUT_FILENO);              dup2(stdfd, STDERR_FILENO);          } else {              exit(0);          }      }      return 0;  }/** * 启动Service */ void Java_com_yyh_fork_NativeRuntime_startService(JNIEnv* env, jobject thiz, jstring cchrptr_ProcessName, jstring sdpath) {     char * rtn = jstringTostring(env, cchrptr_ProcessName); // 得到进程名称     char * sd = jstringTostring(env, sdpath);     LOGI(“Java_com_yyh_fork_NativeRuntime_startService run….ProcessName:%s”, rtn);     a = rtn;     start(1, rtn, sd); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95

这里有几个重点需要理解一下:

1、为什么要fork两次?第一次fork的作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader),而第一次调用的时候父进程是进程组组长。第二次调用后,把前面一次fork出来的子进程退出,这样第二次fork出来的子进程,就和他们脱离了关系。

2、setsid()作用是什么?setsid() 使得第二个子进程是会话组长(sid==pid),也是进程组组长(pgid == pid),并且脱离了原来控制终端。故不管控制终端怎么操作,新的进程正常情况下不会收到他发出来的这些信号。

3、umask(0)的作用:由于子进程从父进程继承下来的一些东西,可能并未把权限继承下来,所以要赋予他更高的权限,便于子进程操作。

4、chdir (“/”);作用:进程活动时,其工作目录所在的文件系统不能卸下,一般需要将工作目录改变到根目录。

5、进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。所以在最后,记得关闭掉从父进程继承过来的文件描述符。

然后,在上面的代码中开启线程后做的事,就是循环去startService(),代码如下:

void thread(char* srvname) {     while(1){         check_and_restart_service(srvname);         sleep(4);     } }/** * 检测服务,如果不存在服务则启动. * 通过am命令启动一个laucher服务,由laucher服务负责进行主服务的检测,laucher服务在检测后自动退出 */ void check_and_restart_service(char* service) {     LOGI(“当前所在的进程pid=”,getpid());     char cmdline[200];     sprintf(cmdline, “am startservice –user 0 -n %s”, service);     char tmp[200];     sprintf(tmp, “cmd=%s”, cmdline);     ExecuteCommandWithPopen(cmdline, tmp, 200);     LOGI( tmp, LOG); }/** * 执行命令 */ void ExecuteCommandWithPopen(char* command, char* out_result, int resultBufferSize) {     FILE * fp;     out_result[resultBufferSize - 1] = ‘\0’;     fp = popen(command, “r”);     if (fp) {         fgets(out_result, resultBufferSize - 1, fp);         out_result[resultBufferSize - 1] = ‘\0’;         pclose(fp);     } else {         LOGI(“popen null,so exit”);         exit(0);     } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

这两个启动服务的函数,里面就涉及到一些Android和Linux的命令了,这里我就不细说了。特别是am,挺强大的功能的,不仅可以开启服务,也可以开启广播等等…然后调用ndk-build命令进行编译,生成so库,ndk不会的,自行百度咯~

C/C++端关键的部分主要是以上这些,自然而然,Java端还得配合执行。

首先来看一下C/C++代码编译完的so库的加载类,以及native的调用:

package com.yyh.fork;import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File;public class NativeRuntime {    private static NativeRuntime theInstance = null;      private NativeRuntime() {  }  public static NativeRuntime getInstance() {      if (theInstance == null)          theInstance = new NativeRuntime();      return theInstance;  }  /**  * RunExecutable 启动一个可自行的lib*.so文件  * @date 2016-1-18 下午8:22:28  * @param pacaageName  * @param filename  * @param alias 别名  * @param args 参数  * @return  */  public String RunExecutable(String pacaageName, String filename, String alias, String args) {      String path = "/data/data/" + pacaageName;      String cmd1 = path + "/lib/" + filename;      String cmd2 = path + "/" + alias;      String cmd2_a1 = path + "/" + alias + " " + args;      String cmd3 = "chmod 777 " + cmd2;      String cmd4 = "dd if=" + cmd1 + " of=" + cmd2;      StringBuffer sb_result = new StringBuffer();      if (!new File("/data/data/" + alias).exists()) {          RunLocalUserCommand(pacaageName, cmd4, sb_result); // 拷贝lib/libtest.so到上一层目录,同时命名为test.          sb_result.append(";");      }      RunLocalUserCommand(pacaageName, cmd3, sb_result); // 改变test的属性,让其变为可执行      sb_result.append(";");      RunLocalUserCommand(pacaageName, cmd2_a1, sb_result); // 执行test程序.      sb_result.append(";");      return sb_result.toString();  }  /**  * 执行本地用户命令  * @date 2016-1-18 下午8:23:01  * @param pacaageName  * @param command  * @param sb_out_Result  * @return  */  public boolean RunLocalUserCommand(String pacaageName, String command, StringBuffer sb_out_Result) {      Process process = null;      try {          process = Runtime.getRuntime().exec("sh"); // 获得shell进程          DataInputStream inputStream = new DataInputStream(process.getInputStream());          DataOutputStream outputStream = new DataOutputStream(process.getOutputStream());          outputStream.writeBytes("cd /data/data/" + pacaageName + "\n"); // 保证在command在自己的数据目录里执行,才有权限写文件到当前目录          outputStream.writeBytes(command + " &\n"); // 让程序在后台运行,前台马上返回          outputStream.writeBytes("exit\n");          outputStream.flush();          process.waitFor();          byte[] buffer = new byte[inputStream.available()];          inputStream.read(buffer);          String s = new String(buffer);          if (sb_out_Result != null)              sb_out_Result.append("CMD Result:\n" + s);      } catch (Exception e) {          if (sb_out_Result != null)              sb_out_Result.append("Exception:" + e.getMessage());          return false;      }      return true;  }  public native void startActivity(String compname);  public native String stringFromJNI();  public native void startService(String srvname, String sdpath);  public native int findProcess(String packname);  public native int stopService();  static {      try {          System.loadLibrary("helper"); // 加载so库      } catch (Exception e) {          e.printStackTrace();      }  }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

然后,我们在收到开机广播后,启动该服务。

package com.yyh.activity;import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log;import com.yyh.fork.NativeRuntime; import com.yyh.utils.FileUtils; public class PhoneStatReceiver extends BroadcastReceiver {private String TAG = "tag";  @Override  public void onReceive(Context context, Intent intent) {      if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {          Log.i(TAG, "手机开机了~~");          NativeRuntime.getInstance().startService(context.getPackageName() + "/com.yyh.service.HostMonitor", FileUtils.createRootPath());      } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {      }  }  } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

Service服务里面,就可以做该做的事情。

package com.yyh.service;import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.util.Log;public class HostMonitor extends Service {@Override  public void onCreate() {      super.onCreate();      Log.i("daemon_java", "HostMonitor: onCreate! I can not be Killed!");  }  @Override  public int onStartCommand(Intent intent, int flags, int startId) {      Log.i("daemon_java", "HostMonitor: onStartCommand! I can not be Killed!");      return super.onStartCommand(intent, flags, startId);  }  @Override  public IBinder onBind(Intent arg0) {      return null;  }  } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

当然,也不要忘记在Manifest.xml文件配置receiver和service:

2 危害

虽然我们这样做了,但是还是有许多危害的。借用知乎上的话就是: 
安卓越用越卡 
非常耗电 
作恶全家桶 
为抹黑安卓出一份力

0 0