热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Android通过JNI实现守护进程

这篇文章主要为大家详细介绍了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("/"); //修改进程工作目录为根目录,chdir(“/”)
 //关闭不需要的从父进程继承过来的文件描述符。
 if (r.rlim_max == RLIM_INFINITY) {
 r.rlim_max = 1024;
 }
 int i;
 for (i = 0; i 

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

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:


   
    
    
   
  
  
  
   

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

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

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

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

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


推荐阅读
  • 简单理解rsa的加密和签名PHP实现
    我们先动手在linux上生成一下rsaPs:openssl是一堆加密算法和安全协议的开源集合,像RSA,DES,MD5,RC4等等,都能在openssl里面找到源代码 ... [详细]
  • Git是一个开源的分布式版本控制系统,用以有效、高速的处理从很小到非常大的项目版本管理,现在在企业中的使用率也是很广的。git是一个分布式的版本控制系统,不像以前的svn,svn是 ... [详细]
  • 先看看效果是不是自己想要的吧item及item内部控件点击事件不懂的可以先点击查看 ... [详细]
  • docker整体了解
    Docker是一个基于LXC技术构建的容器引擎,基于Go语言开发,遵循Apache2.0协议开源Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移 ... [详细]
  • hadoop完全分布式搭建
    原文链接:hadoop完全分布式搭建主机分配以及地址要求:角色主机名IP地址Namenodemaster192.168.222.201Datanodeslave ... [详细]
  • linux文件系统和挂载
    创建ISO文件cpdevcdrom目的地.isomkfs命令生成对应·的文件系统但是使用mkfs没有办法修该生成的系统文件的某些特性,例如标记LABEL,如果强行修改会导致文件里面 ... [详细]
  • 成都万有算力(广州算力网络科技有限公司)
    在同期举办的第十三届天翼智能生态高峰论坛上,中国电信正式发布《中国电信AI+计划》。但从目前来看,后者的影响早已反过来远大于受置疑的前者。包括自由的金针菇、单纯的长颈鹿在内多位专家 ... [详细]
  • handler机制_Handler机制与原理
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Handler机制与原理相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Unity3D平台宏定义之美
    Unity包含一个“平台相关的编译”功能。这包括一些预处理指令,让你分割你的脚本编译和专为支持的平台之一执行代码段。您可以Unity编辑器中运行代码,这 ... [详细]
  • 本人先前是linux的支持都,使用过后就成为了其的鄙视者,观点如下:linux想跟windows比,tmd先搞好软件安装的问题。在linux在动不动就要命令行,有多少人会这恶心的命令行。会命令 ... [详细]
  • 用户管理_用户管理的小项目
      之前学习链表数据结构的时候,写过(相信很多人都做过)dos窗口版的学生管理系统,通过输入数字来实现CURD学生的信息,顶多就是把数据写入文件来存储数据 ... [详细]
  • Today,IstartedtocreateacoupleofJSPpagesfortheserver-sidepartofmyMScthesisprojectinordertob ... [详细]
  • “新建安卓工程时,src与res目录下没有自动生成的.java和.xml文件“的解决
    在自学编程的过程中,由于没有老司机带路,环境搭建是一个非常容易出小错误而且很难找错的过程。此次JAVA环境搭建好,并进行JAVA基础的学习之后,搭建安卓环境。我默认下了目前较高版本 ... [详细]
  • 人生的旅途,前途很远,也很暗。然而不要怕,不怕的人的面前才有路。——鲁迅自从上一篇博客发布后,已经有很长时间没有更新博客了,一直忙着支付通的事情,在此给大家道个歉。先贴个图:你不要惊讶 ... [详细]
  • Splunk Enterprise 存在任意代码执行漏洞
    splunk,enterprise,存在,任 ... [详细]
author-avatar
可惜偏偏孤独一个小姐_448
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有