Android 通过JNI实现守护进程,使得Service服务不被杀死

赋料扬雄敌,诗看子建亲。这篇文章主要讲述Android 通过JNI实现守护进程,使得Service服务不被杀死相关的知识,希望能为你提供帮助。
来自:  http://finalshares.com/read-7306
转载请注明出处:  http://blog.csdn.net/yyh352091626/article/details/50542554
开发一个需要常住后台的App其实是一件非常头疼的事情,不仅要应对国内各大厂商的ROM,还需要应对各类的安全管家...虽然不断的研究各式各样的方法,但是效果并不好,比如任务管理器把App干掉,服务就起不来了...
网上搜寻一番后,主要的方法有以下几种方法,但都是治标不治本:
1、提高Service的优先级:这个,也只能说在系统内存不足需要回收资源的时候,优先级较高,不容易被回收,然并卵...
2、提高Service所在进程的优先级:效果不是很明显
3、在onDestroy方法里重启service:这个倒还算挺有效的一个方法,但是,直接干掉进程的时候,onDestroy方法都进不来,更别想重启了
4、broadcast广播:和第3种一样,没进入onDestroy,就不知道什么时候发广播了,另外,在android4.4以上,程序完全退出后,就不好接收广播了,需要在发广播的地方特定处理
5、放到System/app底下作为系统应用:这个也就是平时玩玩,没多大的实际意义。
6、Service的onStartCommand方法,返回START_STICKY,这个也主要是针对系统资源不足而导致的服务被关闭,还是有一定的道理的。
应对的方法是有,实现起来都比较繁琐。如果你自己可以定制ROM,那就有很多种办法了,比如把你的应用加入白名单,或是多安装一个没有图标的app作为守护进程...但是,哪能什么都是定制的,对于安卓开发者来说,这个难题必须攻破~
那么,有没有办法在一个APP里面,开启一个子线程,在主线程被干掉了之后,子线程通过监听、轮询等方式去判断服务是否存在,不存在的话则开启服务。答案自然是肯定的,通过JNI的方式(NDK编程),fork()出一个子线程作为守护进程,轮询监听服务状态。守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。而守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没有改变。
那么我们先来看看Android4.4的源码,ActivityManagerService(源码/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java)是如何关闭在应用退出后清理内存的:
[java]  view plain  copy

Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

  1. Process.killProcessQuiet(pid);    
应用退出后,ActivityManagerService就把主进程给杀死了,但是,在Android5.0中,ActivityManagerService却是这样处理的:
[java]  view plain  copy
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

  1. Process.killProcessQuiet(app.pid);    
  2. Process.killProcessGroup(app.info.uid,  app.pid);    
就差了一句话,却差别很大。Android5.0在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了...要不怎么说Android5.0在安全方面做了很多更新呢...
那么,有没有办法让子进程脱离出来,不要受到主进程的影响,当然也是可以的。那么,在C/C++层是如何实现的呢?先上关键代码:
[cpp]  view plain  copy
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

  1. /** 
  2.   *  srvname    进程名 
  3.   *  sd  之前创建子进程的pid写入的文件路径 
  4.   */   
  5. int  start(int  argc,  char*  srvname,  char*  sd)  {   
  6.         pthread_t  id;    
  7.         int  ret;    
  8.         struct  rlimit  r;    
  9.    
  10.         int  pid  =  fork();    
  11.         LOGI("fork  pid:  %d",  pid);    
  12.         if  (pid  <   0)  {   
  13.                 LOGI("first  fork()  error  pid  %d,so  exit",  pid);    
  14.                 exit(0);    
  15.         }  else  if  (pid  !=  0)  {   
  16.                 LOGI("first  fork():  I‘am  father  pid=%d",  getpid());    
  17.                 //exit(0);    
  18.         }  else  {  //    第一个子进程   
  19.                 LOGI("first  fork():  I‘am  child  pid=%d",  getpid());    
  20.                 setsid();    
  21.                 LOGI("first  fork():  setsid=%d",  setsid());    
  22.                 umask(0);   //为文件赋予更多的权限,因为继承来的文件可能某些权限被屏蔽   
  23.    
  24.                 int  pid  =  fork();    
  25.                 if  (pid  ==  0)  {  //  第二个子进程   
  26.                         FILE    *fp;    
  27.                         sprintf(sd,"%s/pid",sd);    
  28.                         if((fp=fopen(sd,"a"))==NULL)  {//打开文件  没有就创建   
  29.                                 LOGI("%s文件还未创建!",sd);    
  30.                                 ftruncate(fp,  0);    
  31.                                 lseek(fp,  0,  SEEK_SET);    
  32.                         }   
  33.                         fclose(fp);    
  34.                         fp=fopen(sd,"rw");    
  35.                         if(fp> 0){   
  36.                                 char  buff1[6];    
  37.                                 int  p  =  0;    
  38.                                 memset(buff1,0,sizeof(buff1));    
  39.                                 fseek(fp,0,SEEK_SET);    
  40.                                 fgets(buff1,6,fp);     //读取一行(pid)   
  41.                                 LOGI("读取的进程号:%s",buff1);    
  42.                                 if(strlen(buff1)> 1){  //  有值   
  43.                                         kill(atoi(buff1),  SIGTERM);   //  把上一次的进程干掉,防止重复执行   
  44.                                         LOGI("杀死进程,pid=%d",atoi(buff1));    
  45.                                 }   
  46.                         }   
  47.                         fclose(fp);    
  48.                         fp=fopen(sd,"w");    
  49.                         char  buff[100];    
  50.                         int  k  =  3;    
  51.                         if(fp> 0){   
  52.                                 sprintf(buff,"%lu",getpid());    
  53.                                 fprintf(fp,"%s\n",buff);   //  把进程号写入文件   
  54.                         }   
  55.                         fclose(fp);    
  56.                         fflush(fp);    
  57.    
  58.                         LOGI("I‘am  child-child  pid=%d",  getpid());    
  59.                         chdir("/");   //< span  style="font-family:  Arial,  Helvetica,  sans-serif; "> 修改进程工作目录为根目录,chdir(“/”)< /span>    
  60.                         //关闭不需要的从父进程继承过来的文件描述符。   
  61.                         if  (r.rlim_max  ==  RLIM_INFINITY)  {   
  62.                                 r.rlim_max  =  1024;    
  63.                         }   
  64.                         int  i;    
  65.                         for  (i  =  0;   i  <   r.rlim_max;   i++)  {   
  66.                                 close(i);    
  67.                         }   
  68.    
  69.                         umask(0);    
  70.                         ret  =  pthread_create(& id,  NULL,  (void  *)  thread,  srvname);   //  开启线程,轮询去监听启动服务   
  71.                         if  (ret  !=  0)  {   
  72.                                 printf("Create  pthread  error!\n");    
  73.                                 exit(1);    
  74.                         }   
  75.                         int  stdfd  =  open  ("/dev/null",  O_RDWR);    
  76.                         dup2(stdfd,  STDOUT_FILENO);    
  77.                         dup2(stdfd,  STDERR_FILENO);    
  78.                 }  else  {   
  79.                         exit(0);    
  80.                 }   
  81.         }   
  82.         return  0;    
  83. }   
  84.    
  85. /** 
  86.   *  启动Service 
  87.   */   
  88. void  Java_com_yyh_fork_NativeRuntime_startService(JNIEnv*  env,  jobject  thiz,   
  89.                 jstring  cchrptr_ProcessName,  jstring  sdpath)  {   
  90.         char  *  rtn  =  jstringTostring(env,  cchrptr_ProcessName);   //  得到进程名称   
  91.         char  *  sd  =  jstringTostring(env,  sdpath);    
  92.         LOGI("Java_com_yyh_fork_NativeRuntime_startService  run....ProcessName:%s",  rtn);    
  93.         a  =  rtn;    
  94.         start(1,  rtn,  sd);    
  95. }   
这里有几个重点需要理解一下:
1、为什么要fork两次?第一次fork的作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader),而第一次调用的时候父进程是进程组组长。第二次调用后,把前面一次fork出来的子进程退出,这样第二次fork出来的子进程,就和他们脱离了关系。
2、setsid()作用是什么?setsid() 使得第二个子进程是会话组长(sid==pid),也是进程组组长(pgid == pid),并且脱离了原来控制终端。故不管控制终端怎么操作,新的进程正常情况下不会收到他发出来的这些信号。
3、umask(0)的作用:由于子进程从父进程继承下来的一些东西,可能并未把权限继承下来,所以要赋予他更高的权限,便于子进程操作。
4、chdir ("/"); 作用:进程活动时,其工作目录所在的文件系统不能卸下,一般需要将工作目录改变到根目录。
5、进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。所以在最后,记得关闭掉从父进程继承过来的文件描述符。
然后,在上面的代码中开启线程后做的事,就是循环去startService(),代码如下:
[cpp]  view plain  copy
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

  1. void  thread(char*  srvname)  {   
  2.         while(1){   
  3.                 check_and_restart_service(srvname);    
  4.                 sleep(4);    
  5.         }   
  6. }   
  7.    
  8. /** 
  9.   *  检测服务,如果不存在服务则启动. 
  10.   *  通过am命令启动一个laucher服务,由laucher服务负责进行主服务的检测,laucher服务在检测后自动退出 
  11.   */   
  12. void  check_and_restart_service(char*  service)  {   
  13.         LOGI("当前所在的进程pid=",getpid());    
  14.         char  cmdline[200];    
  15.         sprintf(cmdline,  "am  startservice  --user  0  -n  %s",  service);    
  16.         char  tmp[200];    
  17.         sprintf(tmp,  "cmd=%s",  cmdline);    
  18.         ExecuteCommandWithPopen(cmdline,  tmp,  200);    
  19.         LOGI(  tmp,  LOG);    
  20. }             
  21.    
  22. /** 
  23.   *  执行命令 
  24.   */   
  25. void  ExecuteCommandWithPopen(char*  command,  char*  out_result,   
  26.                 int  resultBufferSize)  {   
  27.         FILE  *  fp;    
  28.         out_result[resultBufferSize  -  1]  =  ‘\0‘;    
  29.         fp  =  popen(command,  "r");    
  30.         if  (fp)  {   
  31.                 fgets(out_result,  resultBufferSize  -  1,  fp);    
  32.                 out_result[resultBufferSize  -  1]  =  ‘\0‘;    
  33.                 pclose(fp);    
  34.         }  else  {   
  35.                 LOGI("popen  null,so  exit");    
  36.                 exit(0);    
  37.         }   
  38. }   
这两个启动服务的函数,里面就涉及到一些Android和linux的命令了,这里我就不细说了。特别是am,挺强大的功能的,不仅可以开启服务,也可以开启广播等等...然后调用ndk-build命令进行编译,生成so库,ndk不会的,自行百度咯~
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

C/C++端关键的部分主要是以上这些,自然而然,Java端还得配合执行。
首先来看一下C/C++代码编译完的so库的加载类,以及native的调用:
[java]  view plain  copy
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

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

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

  1. package  com.yyh.activity;    
  2.    
  3. import  android.content.BroadcastReceiver;    
  4. import  android.content.Context;    
  5. import  android.content.Intent;    
  6. import  android.util.Log;    
  7.    
  8. import  com.yyh.fork.NativeRuntime;    
  9. import  com.yyh.utils.FileUtils;    
  10. public  class  PhoneStatReceiver  extends  BroadcastReceiver  {   
  11.    
  12.         private  String  TAG  =  "tag";    
  13.    
  14.         @Override   
  15.         public  void  onReceive(Context  context,  Intent  intent)  {   
  16.                 if  (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction()))  {   
  17.                         Log.i(TAG,  "手机开机了~~");    
  18.                         NativeRuntime.getInstance().startService(context.getPackageName()  +  "/com.yyh.service.HostMonitor",  FileUtils.createRootPath());    
  19.                 }  else  if  (Intent.ACTION_USER_PRESENT.equals(intent.getAction()))  {   
  20.                 }   
  21.         }   
  22.    
  23.            
  24. }   
Service服务里面,就可以做该做的事情。
[java]  view plain  copy
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

  1. package  com.yyh.service;    
  2.    
  3. import  android.app.Service;    
  4. import  android.content.Intent;    
  5. import  android.os.IBinder;    
  6. import  android.util.Log;    
  7.    
  8. public  class  HostMonitor  extends  Service  {   
  9.    
  10.         @Override   
  11.         public  void  onCreate()  {   
  12.                 super.onCreate();    
  13.                 Log.i("daemon_java",  "HostMonitor:  onCreate!  I  can  not  be  Killed!");    
  14.         }   
  15.    
  16.         @Override   
  17.         public  int  onStartCommand(Intent  intent,  int  flags,  int  startId)  {   
  18.                 Log.i("daemon_java",  "HostMonitor:  onStartCommand!  I  can  not  be  Killed!");    
  19.                 return  super.onStartCommand(intent,  flags,  startId);    
  20.         }   
  21.    
  22.         @Override   
  23.         public  IBinder  onBind(Intent  arg0)  {   
  24.                 return  null;    
  25.         }   
  26. }   
当然,也不要忘记在Manifest.xml文件配置receiver和service:
[html]  view plain  copy
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片
 
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

  1. < receiver   
  2.                         android:name="com.yyh.activity.PhoneStatReceiver"   
  3.                         android:enabled="true"   
  4.                         android:permission="android.permission.RECEIVE_BOOT_COMPLETED"  >    
  5.                         < intent-filter>    
  6.                                 < action  android:name="android.intent.action.BOOT_COMPLETED"  />    
  7.                                 < action  android:name="android.intent.action.USER_PRESENT"  />    
  8.                         < /intent-filter>    
  9.                 < /receiver>    
  10.                    
  11.                 < service    android:name="com.yyh.service.HostMonitor"   
  12.                                     android:enabled="true"   
  13.                                     android:exported="true">    
  14.                   < /service>    
run起来,在程序应用里面,结束掉这个进程,不一会了,又自动起来了~~~~完美~~~~跟流氓软件一个样,没错,就是这么贱,就是这么霸道!!
Android 通过JNI实现守护进程,使得Service服务不被杀死

文章图片

这边是运行在谷歌的原生系统上,Android版本为5.0...不知道会不会被国内的各大ROM干掉,好紧张好紧张~~~所以,总结一下就是:服务常驻要应对的不是各种难的技术,而是各大ROM。QQ为什么不会被杀死,是因为国内各大ROM不想让他死...
【Android 通过JNI实现守护进程,使得Service服务不被杀死】最后附上本例的源代码:  Android 通过JNI实现双守护进程,保证服务不被杀死 源码

    推荐阅读