/** * 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) { // 第二个子进程 // 这里实际上为了防止重复开启线程,应该要有相应处理 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! "); 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);}这里有几个重点需要理解一下:
void thread(char* srvname) { while(1){ check_and_restart_service(srvname); // 应该要去判断service状态,这里一直restart 是不足之处 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] = " "; fp = popen(command, "r"); if (fp) { fgets(out_result, resultBufferSize - 1, fp); out_result[resultBufferSize - 1] = " "; pclose(fp); } else { LOGI("popen null,so exit"); exit(0); }}这两个启动服务的函数,里面就涉及到一些Android和linux的命令了,这里我就不细说了。特别是am,挺强大的功能的,不仅可以开启服务,也可以开启广播等等...然后调用ndk-build命令进行编译,生成so库,ndk不会的,自行百度咯~
C/C++端关键的部分主要是以上这些,自然而然,Java端还得配合执行。
首先来看一下C/C++代码编译完的so库的加载类,以及native的调用:
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 + " "); // 保证在command在自己的数据目录里执行,才有权限写文件到当前目录 outputStream.writeBytes(command + " & "); // 让程序在后台运行,前台马上返回 outputStream.writeBytes("exit "); 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: " + 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(); } }}然后,我们在收到开机广播后,启动该服务。
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服务里面,就可以做该做的事情。
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:
<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。QQ为什么不会被杀死,是因为国内各大ROM不想让他死...
本文主要提供的是一个思路,实现还有诸多不足之处,菜鸟之作,不喜勿喷。
最后附上本例的源代码:Android 通过JNI实现双守护进程
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。