(转)Android 从底层实现让应用杀不死【失效Closed】(1)
转自:http://klob.diandi.life/?p=21#symple-tab-%e8%b0%83%e6%9f%a5%e5%af%b9%e8%b1%a1
情景还原:
我的应用调用了Notification,但是如果被流氓清理软件杀死,在有些机型出现Notification没有被抹除的情况,因为丧失了对Notification的引用,用户也无法抹除这个Notification,这将大大降低用户体验。于是,我想出了如果我的应用可以不死,主动清除Notification。
既然开始做了,干脆做了个小调查。
调查内容:
经获取Root权限TaskManager清除之后能重生的应用使用的方式(测试机型:魅蓝Note )
调查对象:
UC浏览器,网易邮箱,课程格子,恋爱笔记,今日头条,练练
调查结果:
UC浏览器(未知),网易邮箱(自写),课程格子,恋爱笔记,今日头条,练练四个应用全部重生,且都重启了一个名为NotificationCenter的进程
结果分析:
课程格子,恋爱笔记,今日头条,练练四个应用全部重生,且都采用了个推的推送服务
那么个推可能是那样的,然后我从网上找到了一个有关Daemon进程,即守护进程。原作者分析地址http://coolerfall.com/android/android-app-daemon/,原作者github地址https://github.com/Coolerfall/Android-AppDaemon
使用方法
public class YourDaemonService extend Service
{
public void onCreate()
{
Daemon.run(this,YourDaemonService.class,60);
}
}
原理分析:
一、首先调用这个函数 开启守护进程
Daemon.run(this, DaemonService.class, Daemon.INTERVAL_ONE_MINUTE * 2);
public class Daemon {
/**
* Run daemon process.
*
* @param context context
* @param daemonServiceClazz the name of daemon service class
* @param interval the interval to check
*/
public static void run(final Context context, final Class<?> daemonServiceClazz,
final int interval) {
new Thread(new Runnable() {
@Override
public void run() {
Command.install(context, BIN_DIR_NAME, DAEMON_BIN_NAME);
start(context, daemonServiceClazz, interval);
}
}).start();
}
}
二、install 安装库
public class Daemon {
/**
* Install specified binary into destination directory.
*
* @param context context
* @param destDir destionation directory
* @param filename filename of binary
* @return true if install successfully, otherwise return false
*/
@SuppressWarnings("deprecation")
public static boolean install(Context context, String destDir, String filename)
}
这个函数类似
public final class System {
/**
* See {@link Runtime#load}.
*/ public static void load(String pathName) {
Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
}
}
三、调用核心函数
public class Daemon {
/** start daemon */
private static void start(Context context, Class<?> daemonClazzName, int interval) {
String cmd = context.getDir(BIN_DIR_NAME, Context.MODE_PRIVATE)
.getAbsolutePath() + File.separator + DAEMON_BIN_NAME; /* create the command string */
StringBuilder cmdBuilder = new StringBuilder();
cmdBuilder.append(cmd);
cmdBuilder.append(" -p ");
cmdBuilder.append(context.getPackageName());
cmdBuilder.append(" -s ");
cmdBuilder.append(daemonClazzName.getName());
cmdBuilder.append(" -t ");
cmdBuilder.append(interval); try {
Runtime.getRuntime().exec(cmdBuilder.toString()).waitFor();
} catch (IOException | InterruptedException e) {
Log.e(TAG, "start daemon error: " + e.getMessage());
}
}
}
有必要解释一下Runtime.exec(String prog)函数,指令加上几个参数,
public class Runtime {
/**
* Executes the specified program in a separate native process. The new
* process inherits the environment of the caller. Calling this method is
* equivalent to calling {@code exec(prog, null, null)}.
*
* @param prog
* the name of the program to execute.
* @return the new {@code Process} object that represents the native
* process.
* @throws IOException
* if the requested program can not be executed.
*/
public Process exec(String prog) throws java.io.IOException {
return exec(prog, null, null);
}
}
四、调用了Daemon的main函数
Daemon.c
int main(int argc, char *argv[])
{
int i;
pid_t pid;
//包名
char *package_name = NULL;
//Service名
char *service_name = NULL;
//daemon文件目录
char *daemon_file_dir = NULL;
////daemon休眠时间
int interval = SLEEP_INTERVAL; if (argc < 7)
{
LOGE(LOG_TAG, "usage: %s -p package-name -s "
"daemon-service-name -t interval-time", argv[0]);
return;
}
//得到参数
for (i = 0; i < argc; i ++)
{
if (!strcmp("-p", argv[i]))
{
package_name = argv[i + 1];
LOGD(LOG_TAG, "package name: %s", package_name);
} if (!strcmp("-s", argv[i]))
{
service_name = argv[i + 1];
LOGD(LOG_TAG, "service name: %s", service_name);
} if (!strcmp("-t", argv[i]))
{
interval = atoi(argv[i + 1]);
LOGD(LOG_TAG, "interval: %d", interval);
}
} /* package name and service name should not be null */
if (package_name == NULL || service_name == NULL)
{
LOGE(LOG_TAG, "package name or service name is null");
return;
}
//调用fork函数
if ((pid = fork()) < 0)
{
exit(EXIT_SUCCESS);
}
//子
else if (pid == 0)
{
/* add signal */
/* SIGTERM
程序结束(terminate)信号
*/
signal(SIGTERM, sigterm_handler);
/* become session leader */
setsid();
/* change work directory */
chdir("/"); for (i = 0; i < MAXFILE; i ++)
{
close(i);
} /* find pid by name and kill them */
int pid_list[100];
int total_num = find_pid_by_name(argv[0], pid_list);
LOGD(LOG_TAG, "total num %d", total_num);
for (i = 0; i < total_num; i ++)
{
int retval = 0;
int daemon_pid = pid_list[i];
if (daemon_pid > 1 && daemon_pid != getpid())
{
retval = kill(daemon_pid, SIGTERM);
if (!retval)
{
LOGD(LOG_TAG, "kill daemon process success: %d", daemon_pid);
}
else
{
LOGD(LOG_TAG, "kill daemon process %d fail: %s", daemon_pid, strerror(errno));
exit(EXIT_SUCCESS);
}
}
} LOGD(LOG_TAG, "child process fork ok, daemon start: %d", getpid()); while(sig_running)
{
select_sleep(interval < SLEEP_INTERVAL ? SLEEP_INTERVAL : interval, 0); LOGD(LOG_TAG, "check the service once"); /* start service */
start_service(package_name, service_name);
} exit(EXIT_SUCCESS);
}
else
{
/* parent process */
exit(EXIT_SUCCESS);
}
这里有必要其中的函数说明一下
1.signal()函数
void (*signal(int signum, void (*handler))(int)))(int);
该函数有两个参数, signum指定要安装的信号, handler指定信号的处理函数.
SIGTERM 是程序结束(terminate)信号
当程序终止会调用
2.fork()函数:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,两个进程可以做相同的事,相当于自己生了个儿子,如果初始参数或者传入的参数不一样,两个进程做的事情也不一样。当前进程调用fork函数之后,系统先给当前进程分配资源,然后再将当前进程的所有变量的值复制到新进程中(只有少数值不一样),相当于克隆了一个自己。
pid_t fpid = fork()被调用前,就一个进程执行该段代码,这条语句执行之后,就将有两个进程执行代码,两个进程执行没有固定先后顺序,主要看系统调度策略,fork函数的特别之处在于调用一次,但是却可以返回两次,甚至是三种的结果
(1)在父进程中返回子进程的进程id(pid)
(2)在子进程中返回0
(3)出现错误,返回小于0的负值
出现错误原因:(1)进程数已经达到系统规定 (2)内存不足,此时返回
3.AM命令
Android系统提供的adb工具,在adb的基础上执行adb shell就可以直接对android系统执行shell命令
am命令:在Android系统中通过adb shell 启动某个Activity、Service、拨打电话、启动浏览器等操作Android的命令。
am命令的源码在Am.java中,在shell环境下执行am命令实际是启动一个线程执行Am.java中的主函数(main方法),am命令后跟的参数都会当做运行时参数传递到主函数中,主要实现在Am.java的run方法中。
am命令可以用start子命令,和带指定的参数,start是子命令,不是参数
常见参数:-a:表示动作,-d:表示携带的数据,-t:表示传入的类型,-n:指定的组件名
例如,我们现在在命令行模式下进入adb shell下,使用这个命令去打开一个网页
类似的命令还有这些:
拨打电话
命令:am start -a android.intent.action.CALL -d tel:电话号码
示例:am start -a android.intent.action.CALL -d tel:10086
打开一个网页
命令:am start -a android.intent.action.VIEW -d 网址
示例:am start -a android.intent.action.VIEW -d http://www.baidu.com
启动一个服务
命令:am startservice <服务名称>
示例:am startservice -n com.android.music/com.android.music.MediaPlaybackService
/* start daemon service */
static void start_service(char *package_name, char *service_name)
{
/* get the sdk version */
int version = get_version(); pid_t pid; if ((pid = fork()) < 0)
{
exit(EXIT_SUCCESS);
}
else if (pid == 0)
{
if (package_name == NULL || service_name == NULL)
{
LOGE(LOG_TAG, "package name or service name is null");
return;
} char *p_name = str_stitching(package_name, "/");
char *s_name = str_stitching(p_name, service_name);
LOGD(LOG_TAG, "service: %s", s_name); if (version >= 17 || version == 0)
{
int ret = execlp("am", "am", "startservice",
"--user", "0", "-n", s_name, (char *) NULL);
LOGD(LOG_TAG, "result %d", ret);
}
else
{
execlp("am", "am", "startservice", "-n", s_name, (char *) NULL);
} LOGD(LOG_TAG , "exit start-service child process");
exit(EXIT_SUCCESS);
}
}
五、让程序彻底终止
如此一来你可能会发现你的程序根本就死不了
但是你又想要退出,那该怎么办呢?
我这里有个解决办法
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "onCreate");
ACache cache = CustomApplication.getInstance().getCache();;
int m=2;
if (cache.getAsString(Constant.IS_CLEAN) != null) {
try {
m = Integer.parseInt(cache.getAsString(Constant.IS_CLEAN));
// L.e(TAG," " + m );
} catch (NumberFormatException e) {
cache.remove(Constant.IS_CLEAN);
//e.printStackTrace();
m=1;
}
}
if (m>1) {
Daemon.run(this, NotificationCenter.class, 0);
cache.put(Constant.IS_CLEAN, m - 1 + "");
}
if(m==1)
{
// L.e(TAG, "cancelAll");
NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancelAll();
cache.put(Constant.IS_CLEAN, 0);
stopSelf();
}
if (m == 0) {
stopSelf();
} }
IS_CLEAN 是个标志位,我将它缓存为文件
每启动一次Service,会对进行一次V操作,即IS_CLEAN--
当IS_CLEAN <=1 时,那么不会再启动守护进程
ps
并不是所有手机都能用此方法实现进程守护,主要是因为现目前的进程清理软件不会清理c层fork出的进程,但有的手机(如小米),自带清理进程会清理掉应用相关的所有进程,故而不能实现进程守护。
如果自发探索守护进程,可以下载android 终端模拟器
输入 ps 命令查看进程
输入 su 命令获取root权限
输入 kill pid 可以杀死进程
看到其中的进程详情,可以对其中含有 Daemon 字段的进程对探索一二
最后一点,希望开发者利用守护进程完及时关闭,不要耍流氓,本人十分讨厌一些以耍流氓为骄傲的行为
(转)Android 从底层实现让应用杀不死【失效Closed】(1)的更多相关文章
- Android的底层库libutils介绍
第一部分 libutils概述 libutils是Android的底层库,这个库以C++实现,它提供的API也是C++的.Android的层次的C语言程序和库,大都基于libutils开发. libu ...
- Parse发布Bolts,一个面向iOS和Android的底层库集合
转载自:http://www.infoq.com/cn/news/2014/02/parse-announces-bolts 数月前,Parse被Facebook收购.最近,它开源了一个面向iOS和A ...
- Linux下tomcat的shutdown命令可以关闭服务但是杀不死进程
Linux下tomcat的shutdown命令可以关闭服务但是杀不死进程 原因: 一般造成这种原因是因为项目中有非守护线程的存在: 解决方案: 一.从Tomcat上解决 方案1:(推荐的方案:因为一台 ...
- Linux中杀不死的进程
前段时间,一哥们,去杀Linux服务器的进程,发现kill命令失灵了,怎么杀都杀不死. 然后上网查了下资料,原来是要被杀的进程,成为了僵尸进程. 僵尸进程的查看方法: 利用命令ps,可以看到有标记为Z ...
- paip.杀不死进程的原因--僵尸进程的解决.txt
paip.杀不死进程的原因--僵尸进程的解决.txt 作者Attilax 艾龙, EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn ...
- 手把手教你写LKM rookit! 之 杀不死的pid&root后门
......上一节,我们编写了一个基本的lkm模块,从功能上来说它还没有rootkit的特征,这次我们给它添加一点有意思的功能.我们让一个指定的进程杀不死, 曾经,想写一个谁也杀不死的进程,进程能捕捉 ...
- 如何让Java应用成为杀不死的小强?(上篇)
各位坐稳扶好,我们要开车了.不过在开车之前,我们还是例行回顾一下上期分享的要点. 项庄舞剑意在沛公,而咱们上期主要借助应用服务器 Resin 的源码,体验了一次 JMX 的真实应用.鉴于 9012 年 ...
- 如何让Java应用成为杀不死的小强?(中篇)
各位坐稳扶好,我们要开车了.不过在开车之前,我们还是例行回顾一下上期分享的要点. 上期我们抛了一个砖:“如何实现 Java 应用进程的状态监控,如果被监控的进程 down 掉,是否有机制能启动起来?” ...
- android webview 底层实现的逻辑
其实在不同版本上,webview底层是有所不同的. 先提供个地址给大家查:http://grepcode.com/file/repository.grepcode.com/java/ext/com.g ...
随机推荐
- bzoj 1493: [NOI2007]项链工厂(线段树)
1493: [NOI2007]项链工厂 Time Limit: 30 Sec Memory Limit: 64 MBSubmit: 1256 Solved: 545[Submit][Status] ...
- Linux I2C设备驱动编写(三)-实例分析AM3359
TI-AM3359 I2C适配器实例分析 I2C Spec简述 特性: 兼容飞利浦I2C 2.1版本规格 支持标准模式(100K bits/s)和快速模式(400K bits/s) 多路接收.发送模式 ...
- HW4.13
public class Solution { public static void main(String[] args) { int n = 0; while(n * n * n < 120 ...
- java-mina(nio 框架)
mina是对nio的具体实现.是目前比较高效和流行的nio框架了. 下面是对使用mina进行通讯的一个简单demo,后面再用mina写一个RPC的简单框架. mina主要包括: (使用的mina版 ...
- C++ primer(八)--内联函数 引用变量 引用传递函数参数 函数重载/模板/模板具体化
一.内联函数 常规函数和内联函数的区别在于C++编译器如何将他们组合到程序中.编译过程的最终产品是可执行程序--由一组机器语言指令组成.运行程序时,操作系统将这些指令载入到计算机内存中,因此每 ...
- canvas 时钟
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【JAVA - SSM】之MyBatis输出映射
MyBatis中的输出映射有两种:resultType和resultMap. 1.resultType 使用resultType进行结果映射时,只有当查询结果中有至少一列的名称和resultType指 ...
- Microsoft SyncToy 文件同步工具
Microsoft SyncToy SyncToy 是由 微软 推出的一款免费的文件夹同步工具.虽然名字中有一个 Toy,但是大家可千万不要误以为它的功能弱爆了.实际上,我感觉这款软件还真是摆脱了微软 ...
- [TypeScript ] What Happens to Compiled Interfaces
This lesson covers using your first TypeScript Interface and what happens to the Interface when it i ...
- NSURLSessionDownloadTask 断点下载
#import "ViewController.h" #import "ASIHTTPRequest.h" #import <AFNetworking/A ...