情景还原:
我的应用调用了Notification,但是如果被流氓清理软件杀死,在有些机型出现Notification没有被抹除的情况,因为丧失了对Notification的引用,用户也无法抹除这个Notification,这将大大降低用户体验。于是,我想出了如果我的应用可以不死,主动清除Notification。
既然开始做了,干脆做了个小调查。
那么个推可能是那样的,然后我从网上找到了一个有关Daemon进程,即守护进程。原作者分析地址http://coolerfall.com/android/android-app-daemon/,原作者github地址https://github.com/Coolerfall/Android-AppDaemon
使用方法
public class
YourDaemonService { 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 字段的进程对探索一二
最后一点,希望开发者利用守护进程完及时关闭,不要耍流氓,本人十分讨厌一些以耍流氓为骄傲的行为