怎样使一个Android应用不被杀死
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怎样使一个Android应用不被杀死相关的知识,希望对你有一定的参考价值。
要让android应用不被杀死,可以开启一个service,一直检测是否关闭了应用,一旦关闭马上重新启动。当然首先要保证service不被杀死,应当提升service的优先级,设为前台运行。也可以开启两个service互相检测,一旦其中一个被关闭,另一个 马上重启对方。可以保证其生命稳定。这种方法也不是都行的,有些系统仍然能杀死。 参考技术A 推荐下载 高级任务管理器专业版, 当乐网就有下载,是个安卓机器人加个叉的图标。大概只有几百k,功能齐全,可以定时自动查杀设置黑名单白名单,白名单就不会自己查杀了。我从第一部安卓2.1的摩托罗拉里程碑到现在换了几个手机了,就这个查杀软件最好用一直保留的,别的打的查杀软件有点流氓行径的,这个功能很纯粹直接。本回答被提问者和网友采纳 参考技术B if ((info.flags&(ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
== (ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PERSISTENT))
app.persistent = true;
app.maxAdj = CORE_SERVER_ADJ; // 这个常数值为-12。
if (app.thread == null && mPersistentStartingProcesses.indexOf(app) < 0)
mPersistentStartingProcesses.add(app);
startProcessLocked(app, "added application", app.processName);
return app;
可见要想成为core service (即app.maxAdj = CORE_SERVER_ADJ(-12)),应用程序需要FLAG_SYSTEM和FLAG_PERSISTENT两个标志,FLAG_SYSTEM指的是应用位于/system/app下,FLAG_PERSISTENT就是指persistent属性。
而对于frameworks/base/services/java/com/android/server/SystemServer.java,则调用
ActivityManagerService.setSystemProcess();
把自己的 app.maxAdj 设置成SYSTEM_ADJ,即-16。
原理:
Android中的进程是托管的,当系统进程空间紧张的时候,会依照优先级自动进行进程的回收。由此带来三个问题:
1)回收规则: 什么时候回收与回收哪一个?
2)避免误杀: 如何阻止被回收?
3)数据恢复与保存: 被回收了怎么办?
Android将进程分为6个等级,它们按优先级顺序由高到低依次是:
1.前台进程( FOREGROUND_APP)
2.可视进程(VISIBLE_APP )
3. 次要服务进程(SECONDARY_SERVER )
4.后台进程 (HIDDEN_APP)
5.内容供应节点(CONTENT_PROVIDER)
6.空进程(EMPTY_APP)
特征:
1.如果一个进程里面同时包含service和可视的activity,那么这个进程应该归于可视进程,而不是service进程。
2.另外,如果其他进程依赖于它的话,一个进程的等级可以提高。例如,一个A进程里的service被绑定到B进程里的组件上,进程A将总被认为至少和B进程一样重要。
3.系统中的phone服务被划分到前台进程而不是次要服务进程.
在android中,进程的oom_adj值也就代表了它的优先级。oom_adj值越高代表该进程优先级越低。文件/init.rc中有以下属性设置:
setprop ro.FOREGROUND_APP_ADJ 0
setprop ro.VISIBLE_APP_ADJ 1
setprop ro.SECONDARY_SERVER_ADJ 2
setprop ro.HIDDEN_APP_MIN_ADJ 7
setprop ro.CONTENT_PROVIDER_ADJ 14
setprop ro.EMPTY_APP_ADJ 15
/init.rc中,将PID为1的进程(init进程)的oom_adj设置为SYSTEM_ADJ(-16):
# Set init its forked children's oom_adj.
write /proc/1/oom_adj -16
查看本机设置:
cat /sys/module/lowmemorykiller/parameters/adj
0,1,2,7,14,15
回收时机:
文件/init.rc中:
setprop ro.FOREGROUND_APP_MEM 1536 // 6M
setprop ro.VISIBLE_APP_MEM 2048 // 8M
setprop ro.SECONDARY_SERVER_MEM 4096 // 16M
setprop ro.HIDDEN_APP_MEM 5120 // 20M
setprop ro.CONTENT_PROVIDER_MEM 5632 // 22.4M
setprop ro.EMPTY_APP_MEM 6144 // 24M
这些数字也就是对应的内存阈值,一旦低于该值,Android便开始按顺序关闭相应等级的进程。
注意这些数字的单位是page: 1 page = 4 kB。所以上面的六个数字对应的就是(MB): 6,8,16,20,22,24。
查看现在的内存阈值设置:
cat /sys/module/lowmemorykiller/parameters/minfree
要想重新设置该值(对应不同的需求):
echo "1536,2048,4096,5120,15360,23040">/sys/module/lowmemorykiller/parameters/minfree
这样当可用内存低于90MB的时候便开始杀死"空进程",而当可用内存低于60MB的时候才开始杀死"内容供应节点"类进程。
具体的回收实现在ActivityManagerService.java中的函数trimApplications():
1.首先移除package已被卸载的无用进程;
2.基于进程当前状态,更新oom_adj值,然后进行以下操作:
1) 移除没有activity在运行的进程;
2) 如果AP已经保存了所有的activity状态,结束这个AP。
3. 最后,如果目前还是有很多activities 在运行,那么移除那些activity状态已经保存好的activity。
更新oom_adj的值:
在ActivityManagerService.java文件的ComputeOomAdjLocked() 中计算出进程的oom_adj,例如:
if (app == TOP_APP)
// The last app on the list is the foreground app.
adj = FOREGROUND_APP_ADJ;
app.adjType = "top-activity";
Android kernel中的low memory killer
Android的Low Memory Killer根据需要(当系统内存短缺时)杀死进程释放其内存,源代码在kernel/drivers/misc/lowmemorykiller.c中。简单说,就是寻找一个最合适的进程杀死,从而释放它占用的内存。
最合适的进程是:
? oom_adj越大
? 占用物理内存越多
一旦一个进程被选中,内核会发送SIGKILL信号将之杀死:
for_each_process(p)
……
if(selected == NULL || p->oomkilladj > selected->oomkilladj ||
(p->oomkilladj == selected->oomkilladj && tasksize > selected_tasksize))
selected = p;
if(selected != NULL)
force_sig(SIGKILL, selected);
查看LRU列表:adb shell dumpsys activity
当activitydemo在前台时:
包含Service的进程的优先级比较高,在computeOomAdjLocked中将其分为了两小类:
static final int MAX_SERVICE_INACTIVITY = 30*60*1000;
if (now < (s.lastActivity+MAX_SERVICE_INACTIVITY))
if (adj > SECONDARY_SERVER_ADJ)
adj = SECONDARY_SERVER_ADJ;
app.adjType = "started-services";
app.hidden = false;
if (adj > SECONDARY_SERVER_ADJ)
app.adjType = "started-bg-services";
完全让进程不被kill是不可能的,我们可以通过一些操作,使进程被kill的几率变小:
1) 提高进程的优先级:
* 后台操作采用运行于前台的Service形式,因为一个运行着service的进程比一个运行着后台activity的等级高;
* 按back键使得进程中的activity在后台运行而不是destory,需重载back按键(没有任何activity在运行的进程优先被杀).
* 依赖于其他优先级高的进程;
2) 强制修改进程属性:
* 在进程中设置:setPersistent(true);
* 在Manifest文件中设置(如上)。 参考技术C 加入白名单
Android 通过JNI实现守护进程,使得Service服务不被杀死
来自: http://finalshares.com/read-7306
转载请注明出处: http://blog.csdn.net/yyh352091626/article/details/50542554
开发一个需要常住后台的App其实是一件非常头疼的事情,不仅要应对国内各大厂商的ROM,还需要应对各类的安全管家...虽然不断的研究各式各样的方法,但是效果并不好,比如任务管理器把App干掉,服务就起不来了...
网上搜寻一番后,主要的方法有以下几种方法,但都是治标不治本:
1、提高Service的优先级:这个,也只能说在系统内存不足需要回收资源的时候,优先级较高,不容易被回收,然并卵...
2、提高Service所在进程的优先级:效果不是很明显
3、在onDestroy方法里重启service:这个倒还算挺有效的一个方法,但是,直接干掉进程的时候,onDestroy方法都进不来,更别想重启了
4、broadcast广播:和第3种一样,没进入onDestroy,就不知道什么时候发广播了,另外,在Android4.4以上,程序完全退出后,就不好接收广播了,需要在发广播的地方特定处理
5、放到System/app底下作为系统应用:这个也就是平时玩玩,没多大的实际意义。
6、Service的onStartCommand方法,返回START_STICKY,这个也主要是针对系统资源不足而导致的服务被关闭,还是有一定的道理的。
应对的方法是有,实现起来都比较繁琐。如果你自己可以定制ROM,那就有很多种办法了,比如把你的应用加入白名单,或是多安装一个没有图标的app作为守护进程...但是,哪能什么都是定制的,对于安卓开发者来说,这个难题必须攻破~
那么,有没有办法在一个APP里面,开启一个子线程,在主线程被干掉了之后,子线程通过监听、轮询等方式去判断服务是否存在,不存在的话则开启服务。答案自然是肯定的,通过JNI的方式(NDK编程),fork()出一个子线程作为守护进程,轮询监听服务状态。守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。而守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没有改变。
那么我们先来看看Android4.4的源码,ActivityManagerService(源码/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java)是如何关闭在应用退出后清理内存的:
[java] view plain copy
- Process.killProcessQuiet(pid);
应用退出后,ActivityManagerService就把主进程给杀死了,但是,在Android5.0中,ActivityManagerService却是这样处理的:
[java] view plain copy
- Process.killProcessQuiet(app.pid);
- Process.killProcessGroup(app.info.uid, app.pid);
就差了一句话,却差别很大。Android5.0在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了...要不怎么说Android5.0在安全方面做了很多更新呢...
那么,有没有办法让子进程脱离出来,不要受到主进程的影响,当然也是可以的。那么,在C/C++层是如何实现的呢?先上关键代码:
[cpp] view plain copy
- /**
- * srvname 进程名
- * sd 之前创建子进程的pid写入的文件路径
- */
- int start(int argc, char* srvname, char* sd) {
- pthread_t id;
- int ret;
- struct rlimit r;
- int pid = fork();
- LOGI("fork pid: %d", pid);
- if (pid < 0) {
- LOGI("first fork() error pid %d,so exit", pid);
- exit(0);
- } else if (pid != 0) {
- LOGI("first fork(): I‘am father pid=%d", getpid());
- //exit(0);
- } else { // 第一个子进程
- LOGI("first fork(): I‘am child pid=%d", getpid());
- setsid();
- LOGI("first fork(): setsid=%d", setsid());
- umask(0); //为文件赋予更多的权限,因为继承来的文件可能某些权限被屏蔽
- int pid = fork();
- if (pid == 0) { // 第二个子进程
- FILE *fp;
- sprintf(sd,"%s/pid",sd);
- if((fp=fopen(sd,"a"))==NULL) {//打开文件 没有就创建
- LOGI("%s文件还未创建!",sd);
- ftruncate(fp, 0);
- lseek(fp, 0, SEEK_SET);
- }
- fclose(fp);
- fp=fopen(sd,"rw");
- if(fp>0){
- char buff1[6];
- int p = 0;
- memset(buff1,0,sizeof(buff1));
- fseek(fp,0,SEEK_SET);
- fgets(buff1,6,fp); //读取一行(pid)
- LOGI("读取的进程号:%s",buff1);
- if(strlen(buff1)>1){ // 有值
- kill(atoi(buff1), SIGTERM); // 把上一次的进程干掉,防止重复执行
- LOGI("杀死进程,pid=%d",atoi(buff1));
- }
- }
- fclose(fp);
- fp=fopen(sd,"w");
- char buff[100];
- int k = 3;
- if(fp>0){
- sprintf(buff,"%lu",getpid());
- fprintf(fp,"%s\n",buff); // 把进程号写入文件
- }
- fclose(fp);
- fflush(fp);
- LOGI("I‘am child-child pid=%d", getpid());
- chdir("/"); //<span style="font-family: Arial, Helvetica, sans-serif;">修改进程工作目录为根目录,chdir(“/”)</span>
- //关闭不需要的从父进程继承过来的文件描述符。
- if (r.rlim_max == RLIM_INFINITY) {
- r.rlim_max = 1024;
- }
- int i;
- for (i = 0; i < r.rlim_max; i++) {
- close(i);
- }
- umask(0);
- ret = pthread_create(&id, NULL, (void *) thread, srvname); // 开启线程,轮询去监听启动服务
- if (ret != 0) {
- printf("Create pthread error!\n");
- exit(1);
- }
- int stdfd = open ("/dev/null", O_RDWR);
- dup2(stdfd, STDOUT_FILENO);
- dup2(stdfd, STDERR_FILENO);
- } else {
- exit(0);
- }
- }
- return 0;
- }
- /**
- * 启动Service
- */
- void Java_com_yyh_fork_NativeRuntime_startService(JNIEnv* env, jobject thiz,
- jstring cchrptr_ProcessName, jstring sdpath) {
- char * rtn = jstringTostring(env, cchrptr_ProcessName); // 得到进程名称
- char * sd = jstringTostring(env, sdpath);
- LOGI("Java_com_yyh_fork_NativeRuntime_startService run....ProcessName:%s", rtn);
- a = rtn;
- start(1, rtn, sd);
- }
这里有几个重点需要理解一下:
1、为什么要fork两次?第一次fork的作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader),而第一次调用的时候父进程是进程组组长。第二次调用后,把前面一次fork出来的子进程退出,这样第二次fork出来的子进程,就和他们脱离了关系。
2、setsid()作用是什么?setsid() 使得第二个子进程是会话组长(sid==pid),也是进程组组长(pgid == pid),并且脱离了原来控制终端。故不管控制终端怎么操作,新的进程正常情况下不会收到他发出来的这些信号。
3、umask(0)的作用:由于子进程从父进程继承下来的一些东西,可能并未把权限继承下来,所以要赋予他更高的权限,便于子进程操作。
4、chdir ("/");作用:进程活动时,其工作目录所在的文件系统不能卸下,一般需要将工作目录改变到根目录。
5、进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。所以在最后,记得关闭掉从父进程继承过来的文件描述符。
然后,在上面的代码中开启线程后做的事,就是循环去startService(),代码如下:
[cpp] view plain copy
- void thread(char* srvname) {
- while(1){
- check_and_restart_service(srvname);
- sleep(4);
- }
- }
- /**
- * 检测服务,如果不存在服务则启动.
- * 通过am命令启动一个laucher服务,由laucher服务负责进行主服务的检测,laucher服务在检测后自动退出
- */
- void check_and_restart_service(char* service) {
- LOGI("当前所在的进程pid=",getpid());
- char cmdline[200];
- sprintf(cmdline, "am startservice --user 0 -n %s", service);
- char tmp[200];
- sprintf(tmp, "cmd=%s", cmdline);
- ExecuteCommandWithPopen(cmdline, tmp, 200);
- LOGI( tmp, LOG);
- }
- /**
- * 执行命令
- */
- void ExecuteCommandWithPopen(char* command, char* out_result,
- int resultBufferSize) {
- FILE * fp;
- out_result[resultBufferSize - 1] = ‘\0‘;
- fp = popen(command, "r");
- if (fp) {
- fgets(out_result, resultBufferSize - 1, fp);
- out_result[resultBufferSize - 1] = ‘\0‘;
- pclose(fp);
- } else {
- LOGI("popen null,so exit");
- exit(0);
- }
- }
这两个启动服务的函数,里面就涉及到一些Android和linux的命令了,这里我就不细说了。特别是am,挺强大的功能的,不仅可以开启服务,也可以开启广播等等...然后调用ndk-build命令进行编译,生成so库,ndk不会的,自行百度咯~
C/C++端关键的部分主要是以上这些,自然而然,Java端还得配合执行。
首先来看一下C/C++代码编译完的so库的加载类,以及native的调用:
[java] view plain copy
- package com.yyh.fork;
- import java.io.DataInputStream;
- import java.io.DataOutputStream;
- import java.io.File;
- public class NativeRuntime {
- private static NativeRuntime theInstance = null;
- private NativeRuntime() {
- }
- public static NativeRuntime getInstance() {
- if (theInstance == null)
- theInstance = new NativeRuntime();
- return theInstance;
- }
- /**
- * RunExecutable 启动一个可自行的lib*.so文件
- * @date 2016-1-18 下午8:22:28
- * @param pacaageName
- * @param filename
- * @param alias 别名
- * @param args 参数
- * @return
- */
- public String RunExecutable(String pacaageName, String filename, String alias, String args) {
- String path = "/data/data/" + pacaageName;
- String cmd1 = path + "/lib/" + filename;
- String cmd2 = path + "/" + alias;
- String cmd2_a1 = path + "/" + alias + " " + args;
- String cmd3 = "chmod 777 " + cmd2;
- String cmd4 = "dd if=" + cmd1 + " of=" + cmd2;
- StringBuffer sb_result = new StringBuffer();
- if (!new File("/data/data/" + alias).exists()) {
- RunLocalUserCommand(pacaageName, cmd4, sb_result); // 拷贝lib/libtest.so到上一层目录,同时命名为test.
- sb_result.append(";");
- }
- RunLocalUserCommand(pacaageName, cmd3, sb_result); // 改变test的属性,让其变为可执行
- sb_result.append(";");
- RunLocalUserCommand(pacaageName, cmd2_a1, sb_result); // 执行test程序.
- sb_result.append(";");
- return sb_result.toString();
- }
- /**
- * 执行本地用户命令
- * @date 2016-1-18 下午8:23:01
- * @param pacaageName
- * @param command
- * @param sb_out_Result
- * @return
- */
- public boolean RunLocalUserCommand(String pacaageName, String command, StringBuffer sb_out_Result) {
- Process process = null;
- try {
- process = Runtime.getRuntime().exec("sh"); // 获得shell进程
- DataInputStream inputStream = new DataInputStream(process.getInputStream());
- DataOutputStream outputStream = new DataOutputStream(process.getOutputStream());
- outputStream.writeBytes("cd /data/data/" + pacaageName + "\n"); // 保证在command在自己的数据目录里执行,才有权限写文件到当前目录
- outputStream.writeBytes(command + " &\n"); // 让程序在后台运行,前台马上返回
- outputStream.writeBytes("exit\n");
- outputStream.flush();
- process.waitFor();
- byte[] buffer = new byte[inputStream.available()];
- inputStream.read(buffer);
- String s = new String(buffer);
- if (sb_out_Result != null)
- sb_out_Result.append("CMD Result:\n" + s);
- } catch (Exception e) {
- if (sb_out_Result != null)
- sb_out_Result.append("Exception:" + e.getMessage());
- return false;
- }
- return true;
- }
- public native void startActivity(String compname);
- public native String stringFromJNI();
- public native void startService(String srvname, String sdpath);
- public native int findProcess(String packname);
- public native int stopService();
- static {
- try {
- System.loadLibrary("helper"); // 加载so库
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
然后,我们在收到开机广播后,启动该服务。
[java] view plain copy
- package com.yyh.activity;
- import android.content.BroadcastReceiver;
- import android.content.Context;
- import android.content.Intent;
- import android.util.Log;
- import com.yyh.fork.NativeRuntime;
- import com.yyh.utils.FileUtils;
- public class PhoneStatReceiver extends BroadcastReceiver {
- private String TAG = "tag";
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- Log.i(TAG, "手机开机了~~");
- NativeRuntime.getInstance().startService(context.getPackageName() + "/com.yyh.service.HostMonitor", FileUtils.createRootPath());
- } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {
- }
- }
- }
Service服务里面,就可以做该做的事情。
[java] view plain copy
- package com.yyh.service;
- import android.app.Service;
- import android.content.Intent;
- import android.os.IBinder;
- import android.util.Log;
- public class HostMonitor extends Service {
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i("daemon_java", "HostMonitor: onCreate! I can not be Killed!");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i("daemon_java", "HostMonitor: onStartCommand! I can not be Killed!");
- return super.onStartCommand(intent, flags, startId);
- }
- @Override
- public IBinder onBind(Intent arg0) {
- return null;
- }
- }
当然,也不要忘记在Manifest.xml文件配置receiver和service:
[html] view plain copy
- <receiver
- android:name="com.yyh.activity.PhoneStatReceiver"
- android:enabled="true"
- android:permission="android.permission.RECEIVE_BOOT_COMPLETED" >
- <intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED" />
- <action android:name="android.intent.action.USER_PRESENT" />
- </intent-filter>
- </receiver>
- <service android:name="com.yyh.service.HostMonitor"
- android:enabled="true"
- android:exported="true">
- </service>
run起来,在程序应用里面,结束掉这个进程,不一会了,又自动起来了~~~~完美~~~~跟流氓软件一个样,没错,就是这么贱,就是这么霸道!!
这边是运行在谷歌的原生系统上,Android版本为5.0...不知道会不会被国内的各大ROM干掉,好紧张好紧张~~~所以,总结一下就是:服务常驻要应对的不是各种难的技术,而是各大ROM。QQ为什么不会被杀死,是因为国内各大ROM不想让他死...
最后附上本例的源代码: Android 通过JNI实现双守护进程,保证服务不被杀死 源码
以上是关于怎样使一个Android应用不被杀死的主要内容,如果未能解决你的问题,请参考以下文章