AndroidはJNIを通じて守護プロセスを実現します。


常に楽屋に住まわなければならないアプリを開発するのはとても頭が痛いことです。国内の大手メーカーのROMだけではなく、各種の安全管理者に対応する必要があります。いろいろな方法を研究していますが、効果はよくないです。例えば、タスクマネージャがAppを落としたら、サービスが起きられなくなります。
インターネットで検索した後、主な方法は以下のような方法がありますが、すべては治癒不能です。
1、Serviceの優先度を高める:これは、システムメモリが不足していて資源を回収する必要がある場合、優先度が高く、回収されにくい…
2、Serviceのあるプロセスの優先度を高める:効果があまりはっきりしない
3、on Destroy方法でserviceを再起動します。これはまだ有効な方法です。しかし、直接にプロセスを乾かす時、on Destroy方法は全部入れられません。更に再起動したくないです。
4、broadcast放送:第3種と同じで、on Destroyに入らないと、いつ放送されるか分かりません。また、Android d4.4以上で、プログラムが完全に終了したら、放送を受信できなくなります。放送を出すところで特定の処理が必要です。
5、System/apの下に置いてシステムとして応用します。これはつまり普段遊んでいるということです。
6、ServiceのonStartCommand方法は、START_に戻ります。STICKYは、これも主にシステムの資源不足によるサービスが閉鎖されるのか、それとも一定の理由があります。
対処の方法はありますが、実現するのはかなりややこしいです。自分でROMをカスタマイズできるなら、いろいろな方法があります。例えば、あなたのアプリをリストに入れたり、アイコンのないアプリを作ったりして、守護プロセスとして利用しています。しかし、何でもカスタマイズしています。Android開発者にとっては、この難問は突破しなければなりません。
じゃ、一つのAPPの中で、一つのスレッドを開けて、メインスレッドが干された後、サブスレッドは傍受、ポーリングなどでサービスが存在するかどうかを判断します。存在しない場合はサービスを開始します。答えはもちろん肯定的です。JNI方式(NDKプログラミング)によって、fork()は保護プロセスとしてサブスレッドを出して、サービス状態をポーリングします。デーモンはバックグラウンドで実行される特殊なプロセスです。それは制御端末と独立して、ある種のタスクを周期的に実行したり、ある種の発生したイベントを処理するのを待つ。デーモンの会話グループと現在のディレクトリ、ファイル記述子は独立しています。バックグラウンド運転は端末が一回forkしただけで、プログラムはバックグラウンドで実行されます。これらは変わりません。
まず、Android 4.4のソースコードを見てみます。ActivityManagerService(ソース/frame eworks/base/services/core/java/com/android/server/am/ActivityManagerService.java)はどうやってアプリケーションを終了したらメモリを整理できますか?
Process.killProcesssQuite(pid); 
アプリケーションが終了した後、ActivityManagerServiceはメインプロセスを殺しましたが、Android 5.0ではActivityManagerServiceはこのように処理されました。
Process.killProcesssQuit; 
Process.killProcess Group(ap.info.uid,ap.pid) 
たった一言の違いですが、大きな違いがあります。Android 5.0はアプリケーションが終了した後、ActivityManagerServiceはメインプロセスを殺しただけでなく、メインプロセスが属するプロセスグループを一緒に殺しました。そうすると、サブプロセスとメインプロセスが同じプロセスグループにあるため、サブプロセスが進行していることも止まってしまいます。
子供のプロセスを脱出させる方法がありますか?メインプロセスの影響を受けないでください。もちろんいいです。C/C++層ではどうやって実現されますか?キーコードを先に入力します。

/**
 * 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); }
ここにはいくつかのポイントがあります。
1、なぜforkを2回しますか?最初のforkの役割は後ろのsetsidサービスです。setsidの调节者はプロセスグループリーダーではなく、初めて呼び出したときに父プロセスはプロセスグループリーダーです。第二の呼び出し後、前回のforkからの子プロセスを終了します。このように、第二のforkからの子プロセスは彼らと関係を離れました。
2、setsid()の役割は何ですか?setsid()は、第二のサブプロセスが会話リーダー(sid=pid)であり、プロセスグループリーダー(pgid=pid)であり、元の制御端末から逸脱した。したがって、制御端末がどのように操作しても、新しいプロセスが正常に行われても、彼が送ったこれらの信号は受信されません。
3、umask(0)の役割:子プロセスは父プロセスから継承されたもので、権限を引き継いでいないかもしれないので、より高い権限を与えて、子プロセスの操作に便利です。
4、chdir(/);役割:プロセス活動時、その作業ディレクトリがあるファイルシステムは取り外せません。一般的に作業ディレクトリをルートディレクトリに変更する必要があります。
5、プロセスは、親プロセスの作成から、開いたファイル記述子を引き継いでいます。クローズしないと、システムリソースが無駄になり、プロセスがあるファイルシステムが取り外せなくなり、予期せぬエラーを引き起こします。最後に、親プロセスから引き継がれたファイルの記述子を閉じます。
そして、上記のコードの中でスレッドを開いて行うことは、startServiceにループします。コードは以下の通りです。

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] = '\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は、強力な機能を持っています。サービスを開始するだけでなく、放送を開始することもできます。

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())) {
 }
 }

 
}

サービスの中では、やるべきことができます。

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は起きて、プログラムの応用の中で、この過程を終わって、間もなく、また自動的に起きました~~完璧~~ごろつきのソフトウェアと同じに、間違いなくて、こんなに安くて、このように横暴です!

こちらはGoogleの原生システムで運営しています。Androidのバージョンは5.0です。まとめてみますと、サービスが常駐するのは難しい技術ではなく、各ROMです。QQはなぜ殺されないのですか?国内の各ROMは彼を死なせたくないからです。
本文は主に一つの考えを提供します。実現にはまだ多くの足りないところがあります。
最後に本例のソースコードを添付します。AndroidはJNIを通じてデュアルディフェンスを実現します。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。