目前很多安全软件能够将我们开发的程序杀死,如果我们的程序在后台需要一直运行,那么就需要一种方法能够在服务被关掉的时候重新启动。
目前已知的方法:
(1)onDestroy中重启;
(2)AlarmManger定时扫描;
(3)onStartCommand返回值;
(4)setForeground;
(5)双进程互相保护;
这些方法均无法逃脱如小米的一键清理功能。难道没有路子可寻吗?大家也发现了如墨迹天气等一些软件都实现了自动启动,但是关于墨迹天气如何做到的,笔者并不知道,这里笔者提供一种很猥琐的方法,笔者通过不断地努力终于实现了,解决方法是通过NDK编一个本地非android程序,这个程序不会被清理,通过这个不被清理的程序来保护后台服务能够在杀死后复活。
具体实现如下:
(1)本地Native程序:与JNI的so不同,Android.mk文件需要重写
这是Android.mk内容
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= test.cpp
LOCAL_MODULE:= test1
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_STATIC_LIBRARIES := libc android
LOCAL_MODULE_PATH := $(TARGET_OUT_OPTIONAL_EXECUTABLES)
LOCAL_MODULE_TAGS := debug
LOCAL_MODULE_FILENAME := "libtest.so" #------------------注意这里的命令,必须编译成这种文件名,当然是为了逃过android安装
LOCAL_LDLIBS := -lm -llog -landroid
include $(BUILD_EXECUTABLE)
程序代码(该程序定时执行一个操作,那个操作用来启动后台):
int main(int argc, char* argv[]) {FILE *fp1;
char ch;
/********************************************
* 创建一个文件,确保当前的路径能够写入
* ******************************************/
if ((fp1 = fopen("textc.txt", "w")) == NULL) {
LOGI("hellofork: test.cpp/main: open file failed", LOG);exit(0);
}
fprintf(fp1, "test string1");
fclose(fp1);
char tmc[300];
sprintf(tmc, "hellofork: test.cpp/main: argc=%d", argc);
char* srvname = NULL;
if (argc >= 1) {
srvname = argv[1];
} else {
srvname = a;
}
while (1) {
//LOGI( "hellofork.test.main.while(1): Monitor Tick", LOG);
/********************************************
* 验证刚才创建的文件存在则不退出
* 否则可能是被卸载了
* ******************************************/
FILE* fpt = fopen("textc.txt", "r");
if (fpt)
fclose(fpt);
else
break;
check_and_restart_service(srvname);//这个函数负责重启服务
sleep(4);
}
return 0;
}
(2)JNI辅助函数:检查进程是否存在
检测代码可以用JNI调用:
#include <string.h>
#include <jni.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h> // for opendir(), readdir(), closedir()
#include <sys/stat.h> // for stat()
#define PROC_DIRECTORY "/proc/"
#define CASE_SENSITIVE 1
#define CASE_INSENSITIVE 0
#define EXACT_MATCH 1
#define INEXACT_MATCH 0
//是不是数字
int IsNumeric(const char* ccharptr_CharacterList) {
for (; *ccharptr_CharacterList; ccharptr_CharacterList++)
if (*ccharptr_CharacterList < '0' || *ccharptr_CharacterList > '9')
return 0; // false
return 1; // true
}
//intCaseSensitive=0大小写不敏感
int strcmp_Wrapper(const char *s1, const char *s2, int intCaseSensitive) {
if (intCaseSensitive)
return !strcmp(s1, s2);
else
return !strcasecmp(s1, s2);
}
//intCaseSensitive=0大小写不敏感
int strstr_Wrapper(const char* haystack, const char* needle,int intCaseSensitive) {
if (intCaseSensitive)
return (int) strstr(haystack, needle);
else
return (int) strcasestr(haystack, needle);
}
pid_t GetPIDbyName_implements(const char* cchrptr_ProcessName,int intCaseSensitiveness, int intExactMatch) {
char chrarry_CommandLinePath[100];
char chrarry_NameOfProcess[300];
char* chrptr_StringToCompare = NULL;
pid_t pid_ProcessIdentifier = (pid_t) -1;
struct dirent* de_DirEntity = NULL;
DIR* dir_proc = NULL;
int (*CompareFunction)(const char*, const char*, int);
if (intExactMatch)
CompareFunction = &strcmp_Wrapper;
else
CompareFunction = &strstr_Wrapper;
dir_proc = opendir(PROC_DIRECTORY);
if (dir_proc == NULL) {
perror("Couldn't open the " PROC_DIRECTORY " directory");
return (pid_t) -2;
}
// Loop while not NULL
while ((de_DirEntity = readdir(dir_proc))) {
if (de_DirEntity->d_type == DT_DIR) {
if (IsNumeric(de_DirEntity->d_name)) {
strcpy(chrarry_CommandLinePath, PROC_DIRECTORY);
strcat(chrarry_CommandLinePath, de_DirEntity->d_name);
strcat(chrarry_CommandLinePath, "/cmdline");
FILE* fd_CmdLineFile = fopen(chrarry_CommandLinePath, "rt"); //open the file for reading text
if (fd_CmdLineFile) {
fscanf(fd_CmdLineFile, "%s", chrarry_NameOfProcess); //read from /proc/<NR>/cmdline
fclose(fd_CmdLineFile); //close the file prior to exiting the routine
/*取最右边的除号右边的文件名称
if (strrchr(chrarry_NameOfProcess, '/'))
chrptr_StringToCompare = strrchr(chrarry_NameOfProcess,'/') + 1;
else
*/
chrptr_StringToCompare = chrarry_NameOfProcess;
//printf("Process name: %s\n", chrarry_NameOfProcess);
//这个是全路径,比如/bin/ls
//printf("Pure Process name: %s\n", chrptr_StringToCompare );
//这个是纯进程名,比如ls
//这里可以比较全路径名,设置为chrarry_NameOfProcess即可
if (CompareFunction(chrptr_StringToCompare,cchrptr_ProcessName, intCaseSensitiveness)) {
pid_ProcessIdentifier = (pid_t) atoi(de_DirEntity->d_name);
closedir(dir_proc);
return pid_ProcessIdentifier;
}
}
}
}
}
}closedir(dir_proc);
return pid_ProcessIdentifier;
//简单实现 // If -1 = not found, if -2 = proc fs access error
pid_t GetPIDbyName_Wrapper(const char* cchrptr_ProcessName) {
return GetPIDbyName_implements(cchrptr_ProcessName, 0, 0); //大小写不敏感 sub串匹配
}
(3)通过am启动service(am 命令格式在不同系统可能有所不同)
代码通过popen来执行:
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);
}
}
//检测服务,如果不存在服务则启动
void check_and_restart_service(char* service) {
/**********************************************************
* 为了简化问题,这里通过am命令启动一个laucher服务<br>
* 由laucher服务负责进行主服务的检测<br>
* 这个laucher服务在检测后自动退出
* ********************************************************/
if (service == NULL) {
service = a;
}
char cmdline[200];
//sprintf(cmdline, " /system/bin/sh -c \"am startservice %s\"", service);
sprintf(cmdline, "am startservice %s", service);
//writeFile(cmdline, "/data/data/com.qihoo.hellofork/srvname.txt");
char tmp[200];
sprintf(tmp, "hellofork: test.cpp/check_and_restart_service: cmd=%s",cmdline);
LOGI( tmp, LOG);
//system(tmp);
ExecuteCommandWithPopen(cmdline, tmp, 200);
LOGI( tmp, LOG);
}
(4)laucher服务(一个用来启动服务的服务,为了简化am的执行):
public class hostMonitor extends Service {
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Log.i("hellofork",
"hellofork: hostMonitor Came Back! I can not be Killed!!!");
//该服务启动后会立刻停止,在停止前可以执行一些操作,可以在这里通过ServiceManager来判断要不要启动其他服务
//这样am的执行会很省电
stopSelf();
}
}
(5)如何使本地程序启动呢(做了jni接口)?
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文件<br>
* 文件要全名filename<br>
* alias 是别名<br>
* args是参数
*********************************************** **/
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();
}
/****************************************************
* RunLocalUserCommand<br>
* 执行本地用户命令
* **************************************************/
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)
} catch (Exception e) {sb_out_Result.append("CMD Result:\n" + s);
if (sb_out_Result != null)
sb_out_Result.append("Exception:" + e.getMessage());
return false;
}
return true;
}}
public native String findProcess(String processname);
public native String getTestString();
static {
System.loadLibrary("hellofork");
}
界面类:
public class HelloforkActivity extends Activity {
/** Called when the activity is first created. */
TextView text1, text2;
TextView mTextView01;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);setContentView(R.layout.main);
text1 = (TextView) findViewById(R.id.text1);text2 = (TextView) findViewById(R.id.text2);mTextView01 = (TextView) findViewById(R.id.textView01);
text1.setText(NativeRuntime.getInstance().getTestString());
String executable = "libtest.so";String aliasfile = "test";String parafind = "/data/data/" + getPackageName() + "/" + aliasfile;String retx = NativeRuntime.getInstance().findProcess(parafind);if (!retx.substring(0, 4).equalsIgnoreCase("true")) {
String r = NativeRuntime.getInstance().RunExecutable(getPackageName(), executable, aliasfile,"com.qihoo.hellofork/.hostMonitor");text2.setText(r);mTextView01.setText("create new process " + retx);
} else {
mTextView01.setText("exist " + retx.substring(4) + " reload");NativeRuntime.getInstance().RunLocalUserCommand(getPackageName(),"kill -9 " + retx.substring(4), null);NativeRuntime.getInstance().RunExecutable(getPackageName(),executable, aliasfile, "com.qihoo.hellofork/.hostMonitor");
}
}
}
注:无需root权限
依赖:am命令,proc文件系统