Android通过JNI实现守护进程

开发一个需要常住后台的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)是如何关闭在应用退出后清理内存的:

Process.killProcessQuiet(pid);

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

Process.killProcessQuiet(app.pid); 
Process.killProcessGroup(app.info.uid, app.pid);

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

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

/** * 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) { // 第二个子进程 // 这里实际上为了防止重复开启线程,应该要有相应处理 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、为什么要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); // 应该要去判断service状态,这里一直restart 是不足之处 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); } }

这两个启动服务的函数,里面就涉及到一些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(); } } }

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

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())) { } } }

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; } }

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

<receiver android:name="com.yyh.activity.PhoneStatReceiver" android:enabled="true" android:permission="android.permission.RECEIVE_BOOT_COMPLETED" > <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.USER_PRESENT" /> </intent-filter> </receiver> <service android:name="com.yyh.service.HostMonitor" android:enabled="true" android:exported="true"> </service>

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

这边是运行在谷歌的原生系统上,Android版本为5.0...总结一下就是:服务常驻要应对的不是各种难的技术,而是各大ROM。QQ为什么不会被杀死,是因为国内各大ROM不想让他死...

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

时间: 2024-11-05 19:03:11

Android通过JNI实现守护进程的相关文章

Android通过JNI实现守护进程_Android

开发一个需要常住后台的App其实是一件非常头疼的事情,不仅要应对国内各大厂商的ROM,还需要应对各类的安全管家...虽然不断的研究各式各样的方法,但是效果并不好,比如任务管理器把App干掉,服务就起不来了... 网上搜寻一番后,主要的方法有以下几种方法,但都是治标不治本: 1.提高Service的优先级:这个,也只能说在系统内存不足需要回收资源的时候,优先级较高,不容易被回收,然并卵... 2.提高Service所在进程的优先级:效果不是很明显 3.在onDestroy方法里重启service:

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

   上一篇文章Android进程间通信(IPC)机制Binder简要介绍和学习计划简要介绍了Android系统进程间通信机制Binder的总体架构,它由Client.Server.Service Manager和驱动程序Binder四个组件构成.本文着重介绍组件Service Manager,它是整个Binder机制的守护进程,用来管理开发者创建的各种Server,并且向Client提供查询Server远程接口的功能.         既然Service Manager组件是用来管理Serve

android守护进程

Service组件在android开发中经常遇到,其经常作为后台服务,需要始终保持运行,负责处理一些必要(见不得人)的任务.而一些安全软件,如360等,会有结束进程的功能,如果不做Service的保持,就会被其杀掉. 在早些时候,我们可以通过在 1. service中重写onStartCommand方法,这个方法有三个返回值, START_STICKY是service被kill掉后自动 public int onStartCommand(Intent intent, int flags, int

SSSD系统安全服务守护进程

SSSD是红帽企业版Linux6中新加入的一个守护进程,该进程可以用来访问多种验证服务器,如LDAP,Kerberos等,并提供授权.SSSD是介于本地用户和数据存储之间的进程,本地客户端首先连接SSSD,再由SSSD联系外部资源提供者(一台远程服务器). 这样做有一些几点优势: 1.避免了本地每个客户端程序对认证服务器大量连接,所有本地程序仅联系SSSD,由SSSD连接认证服务器或SSSD缓存,有效的降低了负载. 2.允许离线授权.SSSD可以缓存远程服务器的用户认证身份,这允许在远程认证服务

python-如何在linux下开启守护进程

问题描述 如何在linux下开启守护进程 问题是这样的:我用python写了两个模块:Store.py,Search.py,在这两个文件中,分别会开启Store线程和Search线程.这两个线程是需要一直开启的,如果发现这两个线程挂了,需要重新开启. 我之前的做法是:在linux的begin.sh脚本中写下如下内容: #!/bin/bash python Store.py python Search.py 然后执行./begin.sh. 然后出现下面的问题: 由于Store.py中开启了线程,程

python:守护进程deamon

一.守护进程的基本编码规范     详细参见:<AdvancedProgrammingin The Unix Environment>Section 13.3 Page 583      本小节将介绍一些守护进程的基本编码规范,这些规范将阻止守护进程与当前环境产生一些不必要的交互.本节将通过一个函数daemonize实现这些规范.     1. 首先要做的被称为 umask,这一步骤会将文件创建掩码重置为0.这一步的原因是守护进程继承(inherited)得到的文件掩码有可能会拒绝某些特定的文

php守护进程 加linux命令nohup实现任务每秒执行一次

Unix中 nohup 命令功能就是不挂断地运行命令,同时 nohup 把程序的所有输出到放到当前目录 nohup.out 文件中,如果文件不可写,则放到 <用户主目录>/nohup.out 文件中.那么有了这个命令以后我们php就写成shell 脚本使用循环来让我们脚本一直运行下去,不管我们终端窗口是否关闭都能够让我们php 脚本一直运行下去. 马上动手写个 PHP 小程序,功能为每30秒记录时间,写入到文件 复制代码 代码如下: # vi for_ever.php #! /usr/loca

如何使用nodejs实现守护进程

写一个一直运行永远不挂掉的程序是一件很难的事儿,至少一次性写成很难.但是为了不因为程序挂掉而耽误时间我们往往需要一个守护进程来看着程序是否挂掉,如果挂掉那么将程序重启,重启的同时还要收集挂掉的原因,这样我们的程序才会越来越健壮. 今天我遇到了这样的问题,如下是我使用nodejs实现的一个守护进程, var spawn = require('child_process').spawn; function start(){ nw = spawn('./nw', ['book']); nw.on('c

Linux多任务编程(七) Linux守护进程及其基础实验

守护进程概述 守护进程,又叫daemon进程(不知怎的,我突然想起来吸血鬼日记中的达蒙了,很好 看的美剧),是Linux中的后台服务进程.他是一个生存期较长的进程,通常独立于控制终端并且周期性地执行 某种任务或者等待处理某些发生的事件.守护进程常常在系统引导载入时启动,在系统关闭时终止.Linux有 很多系统哦服务,大多数服务都是通过守护进程实现的.同时,守护进程还能完成许多系统任务,例如,作业 规划进程cronf.打印进程lqd等(这里的结尾字母 d 就是 daemon的意思). 在Linux