情景還原:
我的應用調用了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 字段的進程對探索一二
最後一點,希望開發者利用守護進程完及時關閉,不要耍流氓,本人十分討厭一些以耍流氓爲驕傲的行爲