转自: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 字段的进程对探索一二
最后一点,希望开发者利用守护进程完及时关闭,不要耍流氓,本人十分讨厌一些以耍流氓为骄傲的行为