Android 從底層實現讓應用殺不死(1)

情景還原:

我的應用調用了Notification,但是如果被流氓清理軟件殺死,在有些機型出現Notification沒有被抹除的情況,因爲喪失了對Notification的引用,用戶也無法抹除這個Notification,這將大大降低用戶體驗。於是,我想出瞭如果我的應用可以不死,主動清除Notification。

既然開始做了,乾脆做了個小調查。

經獲取Root權限TaskManager清除之後能重生的應用使用的方式(測試機型:魅藍Note  ) 

 

那麼個推可能是那樣的,然後我從網上找到了一個有關Daemon進程,即守護進程。原作者分析地址http://coolerfall.com/android/android-app-daemon/,原作者github地址https://github.com/Coolerfall/Android-AppDaemon

使用方法

publicclass YourDaemonService
 {
      publicvoid onCreate()
       {
         Daemon.run(this,YourDaemonService.class,60);
       }
}

 

原理分析:

 

一、首先調用這個函數 開啓守護進程

Daemon.run(this, DaemonService.class, Daemon.INTERVAL_ONE_MINUTE * 2);
publicclass Daemon {
    /**
     * Run daemon process.
     *
     * @param context            context
     * @param daemonServiceClazz the name of daemon service class
     * @param interval           the interval to check
     */
    publicstatic void run(finalContext context, finalClass<?> daemonServiceClazz,
                           finalint interval) {
        newThread(newRunnable() {
            @Override
            publicvoid run() {
                Command.install(context, BIN_DIR_NAME, DAEMON_BIN_NAME);
                start(context, daemonServiceClazz, interval);
            }
        }).start();
    }
}

二、install 安裝庫

publicclass 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")
    publicstatic boolean install(Context context, String destDir, String filename)
}

這個函數類似

   publicfinal class System {
   /**
     * See {@link Runtime#load}.
     */
 
    publicstatic void load(String pathName) {
        Runtime.getRuntime().load(pathName, VMStack.getCallingClassLoader());
    }
}

三、調用核心函數

publicclass Daemon {
    /** start daemon */
    privatestatic void start(Context context, Class<?> daemonClazzName, intinterval) {
        String cmd = context.getDir(BIN_DIR_NAME, Context.MODE_PRIVATE)
                .getAbsolutePath() + File.separator + DAEMON_BIN_NAME;
 
        /* create the command string */
        StringBuilder cmdBuilder = newStringBuilder();
        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)函數,指令加上幾個參數,

publicclass 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.
     */
    publicProcess exec(String prog) throwsjava.io.IOException {
        returnexec(prog, null,null);
    }
}

四、調用了Daemon的main函數

Daemon.c
intmain(intargc, char*argv[])
{
    inti;
    pid_t pid;
        //包名
    char*package_name = NULL;
        //Service名
    char*service_name = NULL;
        //daemon文件目錄
    char*daemon_file_dir = NULL;
        ////daemon休眠時間
    intinterval = 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);
    }
        //子
    elseif (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 */
        intpid_list[100];
        inttotal_num = find_pid_by_name(argv[0], pid_list);
        LOGD(LOG_TAG,"total num %d", total_num);
        for(i = 0; i < total_num; i ++)
        {
            intretval = 0;
            intdaemon_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 */
staticvoid start_service(char*package_name, char*service_name)
{
    /* get the sdk version */
    intversion = get_version();
 
    pid_t pid;
 
    if((pid = fork()) < 0)
    {
        exit(EXIT_SUCCESS);
    }
    elseif (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)
        {
            intret = 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
    publicvoid onCreate() {
        super.onCreate();
        Log.e(TAG,"onCreate");
        ACache cache = CustomApplication.getInstance().getCache();;
        intm=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 字段的進程對探索一二

最後一點,希望開發者利用守護進程完及時關閉,不要耍流氓,本人十分討厭一些以耍流氓爲驕傲的行爲

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章