转载请注明出处: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,这个主要是针对系统资源不足而导致的服务被关闭,还是有一定的道理的。

在这里推荐一篇文章:【腾讯Bugly】Android 进程保活招式大全

应对的方法是有,实现起来都比较繁琐,而且稳定性也不好。如果你自己可以定制ROM,那就有很多种办法了,比如把你的应用加入白名单,或是多安装一个没有图标的app作为守护进程... 不过这个思想大部分程序都不适用。

那么,有没有办法在一个APP里面,开启一个子线程,在主线程被干掉了之后,子线程通过监听、轮询等方式去判断服务是否存在,不存在的话则开启服务。答案自然是肯定的,通过JNI的方式,fork()出一个子线程作为守护进程,轮询监听服务状态。守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。而守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没有改变。

那么我们先来看看Android4.4的源码,ActivityManagerService(源码/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java)是如何关闭在应用退出后清理内存的:

[java] view plaincopy
  1. Process.killProcessQuiet(pid);

应用退出后,ActivityManagerService就把主进程给杀死了,但是,在Android5.0中,ActivityManagerService却是这样处理的:

[java] view plaincopy
  1. Process.killProcessQuiet(app.pid);
  2. Process.killProcessGroup(app.info.uid, app.pid);

虽只差了一句代码,差别却很大。Android5.0在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了...要不怎么说Android5.0在安全方面做了很多更新呢...

那么,有没有办法让子进程脱离出来,不要受到主进程的影响,当然也是可以的。那么,在C/C++层是如何实现的呢?先上关键代码:

[cpp] view plaincopy
  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. int pid = fork();
  10. LOGI("fork pid: %d", pid);
  11. if (pid < 0) {
  12. LOGI("first fork() error pid %d,so exit", pid);
  13. exit(0);
  14. } else if (pid != 0) {
  15. LOGI("first fork(): I'am father pid=%d", getpid());
  16. //exit(0);
  17. } else { //  第一个子进程
  18. LOGI("first fork(): I'am child pid=%d", getpid());
  19. setsid();
  20. LOGI("first fork(): setsid=%d", setsid());
  21. umask(0); //为文件赋予更多的权限,因为继承来的文件可能某些权限被屏蔽
  22. int pid = fork();
  23. if (pid == 0) { // 第二个子进程
  24. // 这里实际上为了防止重复开启线程,应该要有相应处理
  25. LOGI("I'am child-child pid=%d", getpid());
  26. chdir("/"); //<span style="font-family: Arial, Helvetica, sans-serif;">修改进程工作目录为根目录,chdir(“/”)</span>
  27. //关闭不需要的从父进程继承过来的文件描述符。
  28. if (r.rlim_max == RLIM_INFINITY) {
  29. r.rlim_max = 1024;
  30. }
  31. int i;
  32. for (i = 0; i < r.rlim_max; i++) {
  33. close(i);
  34. }
  35. umask(0);
  36. ret = pthread_create(&id, NULL, (void *) thread, srvname); // 开启线程,轮询去监听启动服务
  37. if (ret != 0) {
  38. printf("Create pthread error!\n");
  39. exit(1);
  40. }
  41. int stdfd = open ("/dev/null", O_RDWR);
  42. dup2(stdfd, STDOUT_FILENO);
  43. dup2(stdfd, STDERR_FILENO);
  44. } else {
  45. exit(0);
  46. }
  47. }
  48. return 0;
  49. }
  50. /**
  51. * 启动Service
  52. */
  53. void Java_com_yyh_fork_NativeRuntime_startService(JNIEnv* env, jobject thiz,
  54. jstring cchrptr_ProcessName, jstring sdpath) {
  55. char * rtn = jstringTostring(env, cchrptr_ProcessName); // 得到进程名称
  56. char * sd = jstringTostring(env, sdpath);
  57. LOGI("Java_com_yyh_fork_NativeRuntime_startService run....ProcessName:%s", rtn);
  58. a = rtn;
  59. start(1, rtn, sd);
  60. }

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

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 plaincopy
  1. void thread(char* srvname) {
  2. while(1){
  3. check_and_restart_service(srvname); // 应该要去判断service状态,这里一直restart 是不足之处
  4. sleep(4);
  5. }
  6. }
  7. /**
  8. * 检测服务,如果不存在服务则启动.
  9. * 通过am命令启动一个laucher服务,由laucher服务负责进行主服务的检测,laucher服务在检测后自动退出
  10. */
  11. void check_and_restart_service(char* service) {
  12. LOGI("当前所在的进程pid=",getpid());
  13. char cmdline[200];
  14. sprintf(cmdline, "am startservice --user 0 -n %s", service);
  15. char tmp[200];
  16. sprintf(tmp, "cmd=%s", cmdline);
  17. ExecuteCommandWithPopen(cmdline, tmp, 200);
  18. LOGI( tmp, LOG);
  19. }
  20. /**
  21. * 执行命令
  22. */
  23. void ExecuteCommandWithPopen(char* command, char* out_result,
  24. int resultBufferSize) {
  25. FILE * fp;
  26. out_result[resultBufferSize - 1] = '\0';
  27. fp = popen(command, "r");
  28. if (fp) {
  29. fgets(out_result, resultBufferSize - 1, fp);
  30. out_result[resultBufferSize - 1] = '\0';
  31. pclose(fp);
  32. } else {
  33. LOGI("popen null,so exit");
  34. exit(0);
  35. }
  36. }

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

C/C++端关键的部分主要是以上这些,接下来就是Java端调用。

首先来看一下so库的加载类,以及C++函数的调用:

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

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

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

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

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

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

[html] view plaincopy
  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. <service  android:name="com.yyh.service.HostMonitor"
  11. android:enabled="true"
  12. android:exported="true">
  13. </service>

run起来,在程序应用里面,结束掉这个进程,不一会了,又自动起来了~~~~跟流氓软件一个样,没错,就是这么贱...

这边是运行在谷歌的原生系统上,Android版本为5.0... 在其他系统下稳定性还远远不足,但是要真正做到杀不死服务几乎是不可能的。 总结一下就是:服务常驻要应对的不是各种难的技术,而是各大ROM。QQ为什么不会被杀死,是因为国内各大ROM不想让他死...

本文主要提供的是一个思路,实现还有诸多不足之处,菜鸟之作,不喜勿喷。

最后附上本例的源代码:Android 通过JNI实现双守护进程

Android 通过JNI实现守护进程相关推荐

  1. android无法启动守护进程,Android Studio无法启动守护进程异常怎么解决?

    大家好,我的这篇文章来自于我个人做的小项目是关于教你如何能过三关,斩五将的通过驾考,那么废话不多说下面是本人总结的错误集,本来仅供自己不要忘记,但如果觉得本人处理不好或没效果还是能够帮我提提意见,因为 ...

  2. android 8 ril,Android系统启动——8 附录2:相关守护进程简介

    本次系列的内容如下: 在init.rc中定义了很多系统的守护进程,这里主要是做一些简单的介绍 一.uevent 负责相应uevent事件,创建设备节点文件: 代码在init.rc 550行 550se ...

  3. Android 系统(271)---进程、守护进程的实现及进程拉活

    进程.守护进程的实现及进程拉活 1,概念 1)守护进程(Daemon) 是一种运行在后台的特殊进程,它独立于控制终端并且周期性的执行某些任务.android中守护进程的实现主要由Service来完成. ...

  4. 浅谈Service Manager成为Android进程间通信(IPC)机制Binder守护进程之路(1)

    上一篇文章Android进程间通信(IPC)机制Binder简要介绍和学习计划简要介绍了Android系统进程间通信机制Binder的总体架构,它由Client.Server.Service Mana ...

  5. Android 系统内的守护进程 - main类服务(1) : netd

    声明 工作需要,分析netd 其实很好奇Android系统中的一些关键守护进程服务的作用: 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客: ...

  6. Android 系统内的守护进程 - core类中的服务 (5) : logd

    声明 工作需要,分析logd 其实很好奇Android系统中的一些关键守护进程服务的作用: 本文使用的代码是LineageOS的cm-14.1,对应Android 7.1.2,可以参考我的另一篇博客: ...

  7. Android 之 守护进程

    之前,低版本做守护进程时,就是两个service 相互守护,但是大于5.0后,似乎就不管用了,今天我们来看看,怎么解决这个问题,多的不说,直接上代码: 首先是有业务处理的service: public ...

  8. 从源码解析-Android系统启动流程概述 init进程zygote进程SystemServer进程启动流程

    Android系统启动流程 启动流程 Loader Kernel Native Framework Application init进程 启动 rc文件规则 Actions Commands Serv ...

  9. Android系统的心脏-Zygote进程启动流程分析

    简介: Android中,Zygote是整个Android系统的核心进程,是Android系统的心脏.所有的Android应用程序,包括Android框架层所在的进程system_server,都是由 ...

最新文章

  1. 【力扣网练习题】有效的括号
  2. draw.io二次开发(2)文件解读
  3. R语言绘制ROC曲线如何画在一个图中
  4. PHP盈亏问题,小升初数学必考经典应用题—盈亏问题!(附经典例题分析)
  5. Linux脚本利器sed
  6. 使用JavaFX AnimationTimer
  7. educoder平台_22个在线平台,2.4万门网课
  8. gpedit msc组策略面板 win10在哪里_Win10家庭版找不到本地组策略gpedit.msc解决办法...
  9. Linux环境Elasticsearch6.xxx 之kibana可视化工具操作索引
  10. 如何用c语言调用c++做成的动态链接库
  11. 140.单词拆分II
  12. Extjs EditorGridPanel功能
  13. 怀念经理用鼠标线联网的1990年代当时
  14. 音频噪声抑制(1):经典滤波器篇
  15. 【基本功】Java魔法类:Unsafe应用解析
  16. ubuntu 常识(转)
  17. 联想服务器bios设置u盘启动不了系统,U盘重装系统的时候按F12不能启动,会出现联想拯救者是什么原因?bios设置U盘启动了...
  18. 一个疯子的DK马历程(易中天说:悲剧啊)
  19. “被提拔3个月,我离职了”:给想做管理的人提个醒!
  20. Thinking In Design Pattern——工厂模式演绎

热门文章

  1. 球与三角形的动态碰撞测试
  2. 基于java的车辆维护系统设计_基于SSM车辆维修管理系统-JavaWeb汽车保养管理系统...
  3. 微软收购动视暴雪之后棋要怎么下
  4. 【编译原理实验六】语义分析
  5. 【微机原理与接口技术】--第二章--微处理器及其结构
  6. 解析:正确的掌握逻辑运算符的优先顺序
  7. shell中$的作用
  8. #4051. ABBA
  9. 风控案例 | 宁波通商银行背后的科技力量
  10. oracle中冒号的意思,value(:字段)中的冒号是啥意思。。或者这么写的作用是》??...