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实现双守护进程

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

(0)

相关推荐

  • Ubuntu 使用Jni开发实例详解

    1. 编写Java文件,在其中声明native方法, 并通过static 语句块加载动态链接库,示例Prompt.java代码如下: class Prompt { private native String getLine(String prompt); public static void main(String args[]) { Prompt p = new Prompt(); String input = p.getLine("Type a line: "); System.o

  • 较详细的JNI简介

    在Java中,有时候我们不得不要去使用其他语言的代码,比如说: 1.你的应用需要访问系统的各个特性和设备,这些特性和设备通过java平台是无法访问的. 2.你已经有了大量的测试过和调试过的用另一种语言编写的代码,并且知道如何将其导出到所有的目标平台上. 3.通过基础测试,你已经发现所编写的Java代码比用其他语言编写的等价代码要慢得多. Java平台有一个用于和本地C代码进行互操作的API,称为Java本地接口(JNI).现在就开始学习JNI,先从这篇的一个最简单的例子,了解如何使用JNI调用本

  • Java的JNI快速入门教程(推荐)

    1. JNI简介 JNI是Java Native Interface的英文缩写,意为Java本地接口. 问题来源:由于Java编写底层的应用较难实现,在一些实时性要求非常高的部分Java较难胜任(实时性要求高的地方目前还未涉及,实时性这类话题有待考究). 解决办法:Java使用JNI可以调用现有的本地库(C/C++开发任何和系统相关的程序和类库),极大地灵活Java的开发. 2. JNI快速学习教程 2.1 问题: 使用JNI写一段代码,实现string_Java_Test_helloworld

  • 启动 Eclipse 弹出 Failed to load the JNI shared library jvm.dll 错误的解决方法

    错误如下图所示: 有时候,新电脑上回碰到打开Eclipse时,弹出提示"Failed to load the JNI shared library jvm.dll"错误,这里给大家分享解决方案. 通常情况下,如果你是64位的系统,却安装了32位的JDK,就会导致上面的情况. 解决办法 1.卸载掉原来安装的32位的JDK,然后安装64位的JDK到电脑中. 2.重新修改path 和 classpath 两个环境变量.因为在64系统当中,32位软件回安装到program files (*86

  • 从源码编译Android系统的Java类库和JNI动态库的方法

    利用源码编译Android系统Java类库 1.编写Java项目和Android.mk文件 ├── Android.mk └── src └── com └── lhw └── framework └── led └── Led.java Led.java文件 package com.lhw.framework.led; /** * LED操作库 * @author Micky Liu */ public class Led { public boolean turnOn() { return

  • Android通过JNI实现守护进程

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

  • 创建Android守护进程实例(底层服务)

    前言 Android底层服务,即运行在 linux 下的进程,是 Android 系统运行的基础,完成 Android 或者说计算机最基本的功能.比如连接服务(包括 WIFI,BT 等等):比如 Android 的 adb 功能:比如存储监控等等.没有这些底层服务,上层也就没有了对应的功能. Android 底层服务往往是常驻内存,时刻运行完成任务.底层服务进程,往往具有更多的权限,可能和驱动通信,可能和 linux 内核通信,可能需要操作系统核心运行文件以及节点等等.所以,底层服务,可以帮你完

  • linux 守护进程详解及建立守护进程

    linux 守护进程详解及建立守护进程 守护进程是一种后台运行并且独立于所有终端控制之外的进程. 守护进程的启动 要启动一个守护进程,可以采取一下几种方式: 在系统期间通过系统的初始化脚本启动守护进程.这些脚本通常在目录etc/rc.d下,通过它们所启动的守护进程具有超级用户的权限.系统的一些基本服务通常都是通过这种方式启动的. 很多网络服务程序都是由inetd守护程序启动的.它监听各种网络请求,如telnet.ftp等,在请求到达时启动相应的服务器程序(telnet server,ftp se

  • python daemon守护进程实现

    假如写一段服务端程序,如果ctrl+c退出或者关闭终端,那么服务端程序就会退出,于是就想着让这个程序成为守护进程,像httpd一样,一直在后端运行,不会受终端影响. 守护进程英文为daemon,像httpd,mysqld,最后一个字母d其实就是表示daemon的意思. 守护进程的编写步骤: 1.fork子进程,然后父进程退出,此时子进程会被init进程接管. 2.修改子进程的工作目录,创建新进程组合新会话,修改umask. 3.子进程再次fork一个进程,这个进程可以称为孙子进程,然后子进程退出

  • node.js应用后台守护进程管理器Forever安装和使用实例

    我们不可能直接通过node命令来管理远程站点,这样无法保证网站的可持续运行.我们用Forever来解决这个问题,它可以将NodeJS应用以后台守护进程的方式运行,我们还可以将NodeJS应用设成随系统启动而自动运行. 首先,安装Forever: 复制代码 代码如下: npm install forever -gd 这样Forever就安装好了,我们可以直接运行Forever命令: 复制代码 代码如下: forever --helpforever start app.jsforever stop

  • PHP程序员玩转Linux系列 使用supervisor实现守护进程

    PHP程序员玩转Linux系列文章: 1.PHP程序员玩转Linux系列-怎么安装使用CentOS 2.PHP程序员玩转Linux系列-lnmp环境的搭建 3.PHP程序员玩转Linux系列-搭建FTP代码开发环境 4.PHP程序员玩转Linux系列-备份还原MySQL 5.PHP程序员玩转Linux系列-自动备份与SVN 6.PHP程序员玩转Linux系列-Linux和Windows安装nginx 7.PHP程序员玩转Linux系列-nginx初学者引导 8.PHP程序员玩转Linux系列-N

  • linux下如何创建守护进程的步骤

    这两天学习了linux 守护进程这个地方知识点挺多的,而且很重要,所以,今天添加一点小笔记. 1,进程的概念:程序的一次动态执行过程.  进程存在于内存当中,存在着  创建,调度,执行和消亡,进程号是进程的唯一标志,每一个进程都有自己独立的内存空间,在32位操作系统中,进程拥有0-4G的内存空间,其中0-3G属于用户,3G-4G属于内核,所以就出现了进程的执行模式:用户模式和内核模式. 2,进程的类型:交互进程,批处理进程,守护进程 3,守护进程:Linux中的后台服务进程,daemon进程 4

  • Windows使用bat批处理实现守护进程脚本分享

    本文转自网络,由于找不到原作者,因而无法知道出处.如果有幸让原作者看到,请联系我加上.先转载至此. 最近几天加班加疯掉了,天天晚上没法睡.开发部的一个核心程序总是会自己宕机,然后需要手工去起,而这个服务的安全级别又很高,只有我可以操作,搞得我晚上老没法睡,昨晚实在受不了了,想起以前在hp-ux下写的shell守护进程,这回搞个windows下的bat版守护程序吧,当时晚上思路已经很迟钝了,就叫了个兄弟让他写了,上去后运行效果不错,至少昨晚我安心睡了7小时.   早上来把程序改完善一些,增加了记录

  • golang守护进程用法示例

    本文实例讲述了golang守护进程用法.分享给大家供大家参考,具体如下: 用node写了一个socket后台服务,可是有时候会挂,node一个异常就game over了,所以写了一个守候. 复制代码 代码如下: package main import (         "log"         "os"         "os/exec"         "time" ) func main() {         lf,

  • PHP将进程作为守护进程的方法

    本文实例讲述了PHP将进程作为守护进程的方法.分享给大家供大家参考.具体分析如下: php中posix_setsid()的用法 文档解释是"Make the current process a session leader" 参考文档:http://linux.die.net/man/2/setsid 意思就是在一个进程组之间(父进程和子进程)调用这个函数的进程会被选举为进程组的leader 所以让一个进程成为守护进程的方法就是: 1 fork出一个子进程 2 在子进程posix_se

随机推荐