我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android通过JNI实现守护进程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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++层是如何实现的呢?先上关键代码:



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

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

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;
 }
 
 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();
 }
 
 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实现双守护进程

您可能感兴趣的文章:记录Android studio JNI开发的三种方式(推荐)详解Android JNI的基本使用(CMake)Android 通过jni返回Mat数据类型方法


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android通过JNI实现守护进程

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android通过JNI实现守护进程

开发一个需要常住后台的App其实是一件非常头疼的事情,不仅要应对国内各大厂商的ROM,还需要应对各类的安全管家...虽然不断的研究各式各样的方法,但是效果并不好,比如任务管理器把App干掉,服务就起不来了... 网上搜寻一番后,主要的方法有
2022-06-06

Python守护进程daemon实现

1.1 守护进程守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。在
2023-01-31

python daemon守护进程实现

假如写一段服务端程序,如果ctrl+c退出或者关闭终端,那么服务端程序就会退出,于是就想着让这个程序成为守护进程,像httpd一样,一直在后端运行,不会受终端影响。 守护进程英文为daemon,像httpd,mysqld,最后一个字母d其实
2022-06-04

Python实现Daemon(守护)进程

最近在写Daemon进程,在编写过程中遇到一些小麻烦,最终还是解决了。我编写了两种,第一种是编写了一个程序,将其用setsid命令让其放入后台运行,第二种是直接fork()一个进程,在代码里将进程设置为后台启动。在os.sytem()函数其
2023-01-31

PHP怎么实现守护进程

今天小编给大家分享一下PHP怎么实现守护进程的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。成为守护进程的步骤其实只需要创建子
2023-06-30

C#守护进程如何实现

今天小编给大家分享一下C#守护进程如何实现的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。1、为什么需要守护进程一般是为了保护
2023-07-02

python中的daemon守护进程实现

守护进程是生存期长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。守护进程的特性1.在后台运行2.与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符、控制
2023-01-31

linux shell实现守护进程脚本

嵌入式初学者,第一次上传代码。昨天做了一个udhcpd与udhcpc的守护,目前只会用shell模仿编写,还有什么方法可以做守护呢?#! /bin/sh #进程名字可修改 PRO_NAME=udhcpc WLAN=ra0while true
2022-06-04

python守护进程监控子进程怎么实现

在Python中,可以使用multiprocessing模块来创建子进程并监控它们。具体实现方法如下:导入multiprocessing模块。import multiprocessing创建一个子进程的函数。def child_proces
2023-10-23

python使用fork实现守护进程的方法

os模块中的fork方法可以创建一个子进程。相当于克隆了父进程 os.fork() 子进程运行时,os.fork方法会返回0;而父进程运行时,os.fork方法会返回子进程的PID号。 所以可以使用PID来区分两个进程:#!/usr/bin
2022-06-04

如何使linux shell实现守护进程脚本

这篇文章主要讲解了“如何使linux shell实现守护进程脚本”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“如何使linux shell实现守护进程脚本”吧!#! /bin/sh#进程名字
2023-06-09

Android通过继承Binder类实现多进程通信

AIDL的底层是通过Binder进行通信的,通过追踪.aidl编译后自动生成的文件我们知道,文件中的Stub类用于服务端,Proxy类用于客户端调用,那么可否直接通过继承Binder类实现多进程通信呢?下面就来试一试。 效果图:服务端代码,
2022-06-06

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

上一篇文章Android进程间通信(IPC)机制Binder简要介绍和学习计划简要介绍了Android系统进程间通信机制Binder的总体架构,它由Client、Server、Service Manager和驱动程序Binder四个组
2022-06-06

Python如何实现守护进程的方法示例

场景设置: 你编写了一个python服务程序,并且在命令行下启动,而你的命令行会话又被终端所控制,python服务成了终端程序的一个子进程。因此如果你关闭了终端,这个命令行程序也会随之关闭。要使你的python服务不受终端影响而常驻系统,就
2022-06-04

SAP ABAP守护进程的实现方式是什么

本篇文章给大家分享的是有关SAP ABAP守护进程的实现方式是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。Linux系统很多服务都通过守护进程实现,常见的守护进程有系统日
2023-06-03

Android通过RemoteViews实现跨进程更新UI示例

一、概述 前面一篇文章Android通过AIDL实现跨进程更新UI我们学习了aidl跨进程更新ui,这种传统方式实现跨进程更新UI是可行的,但有以下弊端:View中的方法数比较多,在IPC中需要增加对应的方法比较繁琐。View的每一个方法都
2022-06-06

Android 中通过实现线程更新Progressdialog (对话进度条)

作为开发者我们需要经常站在用户角度考虑问题,比如在应用商城下载软件时,当用户点击下载按钮,则会有下载进度提示页面出现,现在我们通过线程休眠的方式模拟下载进度更新的演示,如图(这里为了截图方便设置对话进度条位于屏幕上方):layout界面代码
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录