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

使用方法

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

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

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值