美文网首页
(转)Android 从底层实现让应用杀不死【失效Closed】

(转)Android 从底层实现让应用杀不死【失效Closed】

作者: f6a40ba32131 | 来源:发表于2016-07-06 09:22 被阅读490次

    转自: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

    使用方法

    ```
    public class YourDaemonService extend Service

    {

    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 字段的进程对探索一二

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

    相关文章

      网友评论

          本文标题:(转)Android 从底层实现让应用杀不死【失效Closed】

          本文链接:https://www.haomeiwen.com/subject/usgzdttx.html