(转)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

使用方法

1 public class YourDaemonService extend Service
2  {
3       public void onCreate()
4        {
5          Daemon.run(this,YourDaemonService.class,60);
6        }
7 }

原理分析:

一、首先调用这个函数 开启守护进程

Daemon.run(this, DaemonService.class, Daemon.INTERVAL_ONE_MINUTE * 2);
 1 public class Daemon {
 2     /**
 3      * Run daemon process.
 4      *
 5      * @param context            context
 6      * @param daemonServiceClazz the name of daemon service class
 7      * @param interval           the interval to check
 8      */
 9     public static void run(final Context context, final Class<?> daemonServiceClazz,
10                            final int interval) {
11         new Thread(new Runnable() {
12             @Override
13             public void run() {
14                 Command.install(context, BIN_DIR_NAME, DAEMON_BIN_NAME);
15                 start(context, daemonServiceClazz, interval);
16             }
17         }).start();
18     }
19 }

二、install 安装库

 1 public class Daemon {
 2     /**
 3      * Install specified binary into destination directory.
 4      *
 5      * @param  context  context
 6      * @param  destDir  destionation directory
 7      * @param  filename filename of binary
 8      * @return          true if install successfully, otherwise return false
 9      */
10     @SuppressWarnings("deprecation")
11     public static boolean install(Context context, String destDir, String filename)
12 }

这个函数类似

1 public final class System {
2    /**
3      * See {@link Runtime#load}.
4      */
5
6     public static void load(String pathName) {
7         Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
8     }
9 }

三、调用核心函数

 1 public class Daemon {
 2     /** start daemon */
 3     private static void start(Context context, Class<?> daemonClazzName, int interval) {
 4         String cmd = context.getDir(BIN_DIR_NAME, Context.MODE_PRIVATE)
 5                 .getAbsolutePath() + File.separator + DAEMON_BIN_NAME;
 6
 7         /* create the command string */
 8         StringBuilder cmdBuilder = new StringBuilder();
 9         cmdBuilder.append(cmd);
10         cmdBuilder.append(" -p ");
11         cmdBuilder.append(context.getPackageName());
12         cmdBuilder.append(" -s ");
13         cmdBuilder.append(daemonClazzName.getName());
14         cmdBuilder.append(" -t ");
15         cmdBuilder.append(interval);
16
17         try {
18             Runtime.getRuntime().exec(cmdBuilder.toString()).waitFor();
19         } catch (IOException | InterruptedException e) {
20             Log.e(TAG, "start daemon error: " + e.getMessage());
21         }
22     }
23 }

有必要解释一下Runtime.exec(String prog)函数,指令加上几个参数,

 1 public class Runtime {
 2  /**
 3      * Executes the specified program in a separate native process. The new
 4      * process inherits the environment of the caller. Calling this method is
 5      * equivalent to calling {@code exec(prog, null, null)}.
 6      *
 7      * @param prog
 8      *            the name of the program to execute.
 9      * @return the new {@code Process} object that represents the native
10      *         process.
11      * @throws IOException
12      *             if the requested program can not be executed.
13      */
14     public Process exec(String prog) throws java.io.IOException {
15         return exec(prog, null, null);
16     }
17 }

四、调用了Daemon的main函数

  1 Daemon.c
  2 int main(int argc, char *argv[])
  3 {
  4     int i;
  5     pid_t pid;
  6         //包名
  7     char *package_name = NULL;
  8         //Service名
  9     char *service_name = NULL;
 10         //daemon文件目录
 11     char *daemon_file_dir = NULL;
 12         ////daemon休眠时间
 13     int interval = SLEEP_INTERVAL;
 14
 15     if (argc < 7)
 16     {
 17         LOGE(LOG_TAG, "usage: %s -p package-name -s "
 18          "daemon-service-name -t interval-time", argv[0]);
 19         return;
 20     }
 21         //得到参数
 22     for (i = 0; i < argc; i ++)
 23     {
 24         if (!strcmp("-p", argv[i]))
 25         {
 26             package_name = argv[i + 1];
 27             LOGD(LOG_TAG, "package name: %s", package_name);
 28         }
 29
 30         if (!strcmp("-s", argv[i]))
 31         {
 32             service_name = argv[i + 1];
 33             LOGD(LOG_TAG, "service name: %s", service_name);
 34         }
 35
 36         if (!strcmp("-t", argv[i]))
 37         {
 38             interval = atoi(argv[i + 1]);
 39             LOGD(LOG_TAG, "interval: %d", interval);
 40         }
 41     }
 42
 43     /* package name and service name should not be null */
 44     if (package_name == NULL || service_name == NULL)
 45     {
 46         LOGE(LOG_TAG, "package name or service name is null");
 47         return;
 48     }
 49         //调用fork函数
 50     if ((pid = fork()) < 0)
 51     {
 52         exit(EXIT_SUCCESS);
 53     }
 54         //子
 55     else if (pid == 0)
 56     {
 57           /* add signal */
 58         /*  SIGTERM
 59         程序结束(terminate)信号
 60         */
 61               signal(SIGTERM, sigterm_handler);
 62         /* become session leader */
 63         setsid();
 64         /* change work directory */
 65         chdir("/");
 66
 67         for (i = 0; i < MAXFILE; i ++)
 68         {
 69             close(i);
 70         }
 71
 72         /* find pid by name and kill them */
 73         int pid_list[100];
 74         int total_num = find_pid_by_name(argv[0], pid_list);
 75         LOGD(LOG_TAG, "total num %d", total_num);
 76         for (i = 0; i < total_num; i ++)
 77         {
 78             int retval = 0;
 79             int daemon_pid = pid_list[i];
 80             if (daemon_pid > 1 && daemon_pid != getpid())
 81             {
 82                 retval = kill(daemon_pid, SIGTERM);
 83                 if (!retval)
 84                 {
 85                     LOGD(LOG_TAG, "kill daemon process success: %d", daemon_pid);
 86                 }
 87                 else
 88                 {
 89                     LOGD(LOG_TAG, "kill daemon process %d fail: %s", daemon_pid, strerror(errno));
 90                     exit(EXIT_SUCCESS);
 91                 }
 92             }
 93         }
 94
 95         LOGD(LOG_TAG, "child process fork ok, daemon start: %d", getpid());
 96
 97         while(sig_running)
 98         {
 99             select_sleep(interval < SLEEP_INTERVAL ? SLEEP_INTERVAL : interval, 0);
100
101             LOGD(LOG_TAG, "check the service once");
102
103             /* start service */
104             start_service(package_name, service_name);
105         }
106
107         exit(EXIT_SUCCESS);
108     }
109     else
110     {
111         /* parent process */
112         exit(EXIT_SUCCESS);
113     }

这里有必要其中的函数说明一下

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

 1 /* start daemon service */
 2 static void start_service(char *package_name, char *service_name)
 3 {
 4     /* get the sdk version */
 5     int version = get_version();
 6
 7     pid_t pid;
 8
 9     if ((pid = fork()) < 0)
10     {
11         exit(EXIT_SUCCESS);
12     }
13     else if (pid == 0)
14     {
15         if (package_name == NULL || service_name == NULL)
16         {
17             LOGE(LOG_TAG, "package name or service name is null");
18             return;
19         }
20
21         char *p_name = str_stitching(package_name, "/");
22         char *s_name = str_stitching(p_name, service_name);
23         LOGD(LOG_TAG, "service: %s", s_name);
24
25         if (version >= 17 || version == 0)
26         {
27             int ret = execlp("am", "am", "startservice",
28                         "--user", "0", "-n", s_name, (char *) NULL);
29             LOGD(LOG_TAG, "result %d", ret);
30         }
31         else
32         {
33             execlp("am", "am", "startservice", "-n", s_name, (char *) NULL);
34         }
35
36         LOGD(LOG_TAG , "exit start-service child process");
37         exit(EXIT_SUCCESS);
38     }
39 }

五、让程序彻底终止

如此一来你可能会发现你的程序根本就死不了
但是你又想要退出,那该怎么办呢?
我这里有个解决办法

 1 @Override
 2     public void onCreate() {
 3         super.onCreate();
 4         Log.e(TAG, "onCreate");
 5         ACache cache = CustomApplication.getInstance().getCache();;
 6         int m=2;
 7         if (cache.getAsString(Constant.IS_CLEAN) != null) {
 8             try {
 9                 m = Integer.parseInt(cache.getAsString(Constant.IS_CLEAN));
10 //                L.e(TAG,"   " +  m );
11             } catch (NumberFormatException e) {
12                 cache.remove(Constant.IS_CLEAN);
13                 //e.printStackTrace();
14                 m=1;
15             }
16         }
17         if (m>1) {
18             Daemon.run(this, NotificationCenter.class, 0);
19             cache.put(Constant.IS_CLEAN, m - 1 + "");
20          }
21         if(m==1)
22         {
23 //            L.e(TAG, "cancelAll");
24             NotificationManager notificationManager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
25             notificationManager.cancelAll();
26             cache.put(Constant.IS_CLEAN, 0);
27             stopSelf();
28         }
29         if (m == 0) {
30             stopSelf();
31         }
32
33     }

IS_CLEAN 是个标志位,我将它缓存为文件
每启动一次Service,会对进行一次V操作,即IS_CLEAN--
当IS_CLEAN <=1 时,那么不会再启动守护进程

ps

并不是所有手机都能用此方法实现进程守护,主要是因为现目前的进程清理软件不会清理c层fork出的进程,但有的手机(如小米),自带清理进程会清理掉应用相关的所有进程,故而不能实现进程守护。

如果自发探索守护进程,可以下载android 终端模拟器

输入 ps 命令查看进程

输入 su 命令获取root权限

输入 kill pid 可以杀死进程

看到其中的进程详情,可以对其中含有 Daemon 字段的进程对探索一二

最后一点,希望开发者利用守护进程完及时关闭,不要耍流氓,本人十分讨厌一些以耍流氓为骄傲的行为

时间: 2024-10-22 17:08:44

(转)Android 从底层实现让应用杀不死【失效Closed】(1)的相关文章

Android音频底层调试-基于tinyalsa

由于Android中默认并没有使用标准alsa,而是使用的是tinyalsa,所以就算基于命令行的测试也要使用libtinyalsa.Android系统在上层Audio千变万化的时候,可以能这些个工具实时查看到,比如音频通道的切换等等. 1.编译tinyalsa配套工具 $ mmm external/tinyalsa/ 编译完后会产生tinyplay/tinymix/tinycap等等工具. tinymix: 查看配置混音器 tinyplay: 播放音频 tinycap: 录音 2.查看当前系统

Android的底层库libutils介绍

Android的底层库libutils介绍 2008年12月5日11:38 来源:本站原创 我有话说(0人参与) 第一部分 libutils概述 libutils是Android的底层库,这个库以C++实现,它提供的API也是C++的.Android的层次的C语言程序和库,大都基于libutils开发. libutils中的头文件如下所示: frameworks/base/include/utils libutils的源文件: frameworks/base/libs/utils libutil

Android启动早于系统应用的第三方应用,杀不死自动重启的第三方应用

1.为什么第三方应用能早于System的app启动? Android应用的启动顺序网上有一大堆资料可以查阅了,这里就不细述了,这里不阐述ROM启动还有bootloader,软件启动的大致流程应该是 启动kernel 运行servicemanager 把一些native的服务用命令启动起来(包括wifi, power, rild, surfaceflinger, mediaserver等等) 启动Dalivk中的第一个进程Zygote -> 启动java 层的系统服务system_server(包

Android多媒体-底层流程简介

先上图,根据图做一个简单介绍 我们讲一下MediaPlayer 应用层的播放器首先调用framework层的MediaPlayer的类,接着FrameWork层会继续调用Native层的MediaPlayer类,然后通过Binder调用MediaPlayerService,MediaPlayerService调用OpenVC库解码成原始的视频流和音频流,视频流通过UI一帧一帧的显示出来,MediaPlayerService将解码后的音频流交给AudioTrack,接着交给AudioFlinger

杀不死的Service

Android应用开发笔记:杀不死的Service 2013年11月6日作者:cstriker1407 暂无评论 有时候我们希望我们程序中的Service不被杀死,即使杀死也能自动重启.下面简单的备忘下一种思路. github: [ https://github.com/cstriker1407/android/tree/master/UnkillService ] Contents [hide] 1 这次先备注下思路: 2 Service代码: 3  XML文件: 4 开机自动启动Servic

关于am force-stop杀不死某些app的原因

关于am force-stop杀不死某些app的原因 你在android8.1中,可能运行adb shell am force-stop XXX其中XXX是app的包名,发现进程还在,而且ps看进程号不变,也就是此包没有被stop掉.其实在android8.1中的代码中: if (app.persistent && !evenPersistent) {// we don't kill persistent processescontinue;} 应用中android:persistent=

Linux中杀不死的进程

前段时间,一哥们,去杀Linux服务器的进程,发现kill命令失灵了,怎么杀都杀不死. 然后上网查了下资料,原来是要被杀的进程,成为了僵尸进程. 僵尸进程的查看方法: 利用命令ps,可以看到有标记为Z的进程就是僵尸进程. 知道了原因,就想怎么去把这个僵尸进程干掉.网上说了两种方法,一种最简单的方法,重启服务器,相当于清理内存了.方法很简单,但是不是很实用,因为服务器,不是你一个人在用,服务器是不能随随便便重启的.第二种方法,杀掉其父进程,父进程干掉后,该僵尸进程也就消失了. 可以用  ps -e

安卓android杀不死进程,保护,双进程守护,驻留,Marsdaemon,保活

韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha  [email protected] =========== Android 进程常驻(0)----MarsDaemon使用说明

UltimateRecyclerView发布,Android下新Listview的大杀器

一个多功能的RecyclerView,包括了下拉刷新.加载更多,滑动删除,拖拽排序.多种动画.视差拖动.Toolbar渐变.Toolbar和FAB随着滚动出现消失等等效果,都可以放在同一个RecyclerVIew中并自由配置. 项目地址:https://github.com/cymcsg/UltimateRecyclerView Description UltimateRecyclerView is a RecyclerView(advanced and flexible version of