99热99这里只有精品6国产,亚洲中文字幕在线天天更新,在线观看亚洲精品国产福利片 ,久久久久综合网

歡迎加入QQ討論群258996829
麥子學(xué)院 頭像
蘋果6袋
6
麥子學(xué)院

Android中通過(guò)JNI實(shí)現(xiàn)守護(hù)進(jìn)程的方法詳解

發(fā)布時(shí)間:2016-10-29 23:37  回復(fù):0  查看:2186   最后回復(fù):2016-10-29 23:37  

android開發(fā)中,我們常常都會(huì)遇到各種各樣的進(jìn)程問(wèn)題,今天和大家分享的就是通過(guò)JNI實(shí)現(xiàn)守護(hù)進(jìn)程的相關(guān)方法,希望對(duì)大家有幫助。

  開發(fā)一個(gè)需要常住后臺(tái)的App其實(shí)是一件非常頭疼的事情,不僅要應(yīng)對(duì)國(guó)內(nèi)各大廠商的ROM,還需要應(yīng)對(duì)各類的安全管家… 雖然不斷的研究各式各樣的方法,但是效果并不好,比如任務(wù)管理器把App干掉,服務(wù)就起不來(lái)了

  網(wǎng)上搜尋一番后,主要的方法有以下幾種方法,但其實(shí)也都治標(biāo)不治本:

  1、提高Service的優(yōu)先級(jí):這個(gè),也只能說(shuō)在系統(tǒng)內(nèi)存不足需要回收資源的時(shí)候,優(yōu)先級(jí)較高,不容易被回收,然并卵

  2、提高Service所在進(jìn)程的優(yōu)先級(jí):效果不是很明顯

  3、在onDestroy方法里重啟service:這個(gè)倒還算挺有效的一個(gè)方法,但是,直接干掉進(jìn)程的時(shí)候,onDestroy()方法都進(jìn)不來(lái),更別想重啟了

  4、broadcast廣播:和第3種一樣,沒進(jìn)入onDestroy(),就不知道什么時(shí)候發(fā)廣播了,另外,在Android4.4以上,程序完全退出后,就不好接收廣播了,需要在發(fā)廣播的地方特定處理

  5、放到System/app底下作為系統(tǒng)應(yīng)用:這個(gè)也就是平時(shí)玩玩,沒多大的實(shí)際意義。

  6、ServiceonStartCommand()方法,返回START_STICKY,這個(gè)主要是針對(duì)系統(tǒng)資源不足而導(dǎo)致的服務(wù)被關(guān)閉,還是有一定的道理的。

  應(yīng)對(duì)的方法是有,實(shí)現(xiàn)起來(lái)都比較繁瑣,而且穩(wěn)定性也不好。如果你自己可以定制ROM,那就有很多種辦法了,比如把你的應(yīng)用加入白名單,或是多安裝一個(gè)沒有圖標(biāo)的app作為守護(hù)進(jìn)程… 不過(guò)這個(gè)思想大部分程序都不適用。

  那么,有沒有辦法在一個(gè)APP里面,開啟一個(gè)子線程,在主線程被干掉了之后,子線程通過(guò)監(jiān)聽、輪詢等方式去判斷服務(wù)是否存在,不存在的話則開啟服務(wù)。答案自然是肯定的,通過(guò)JNI的方式,fork()出一個(gè)子線程作為守護(hù)進(jìn)程,輪詢監(jiān)聽服務(wù)狀態(tài)。守護(hù)進(jìn)程(Daemon)是運(yùn)行在后臺(tái)的一種特殊進(jìn)程。它獨(dú)立于控制終端并且周期性地執(zhí)行某種任務(wù)或等待處理某些發(fā)生的事件。而守護(hù)進(jìn)程的會(huì)話組和當(dāng)前目錄,文件描述符都是獨(dú)立的。后臺(tái)運(yùn)行只是終端進(jìn)行了一次fork,讓程序在后臺(tái)執(zhí)行,這些都沒有改變。

  那么我們先來(lái)看看Android4.4的源碼,ActivityManagerService(源碼/frameworks/base/services/core/ Java /com/ Android /server/am/ActivityManagerService.java)是如何關(guān)閉在應(yīng)用退出后清理內(nèi)存的:

  Process.killProcessQuiet(pid);

  應(yīng)用退出后,ActivityManagerService就把主進(jìn)程給殺死了,但是,在Android5.0中,ActivityManagerService卻是這樣處理的:

  Process.killProcessQuiet(app.pid); Process.killProcessGroup(app.info.uid, app.pid);

  雖只差了一句代碼,差別卻很大。Android5.0在應(yīng)用退出后,ActivityManagerService不僅把主進(jìn)程給殺死,另外把主進(jìn)程所屬的進(jìn)程組一并殺死,這樣一來(lái),由于子進(jìn)程和主進(jìn)程在同一進(jìn)程組,子進(jìn)程在做的事情,也就停止了要不怎么說(shuō)Android5.0在安全方面做了很多更新呢

  那么,有沒有辦法讓子進(jìn)程脫離出來(lái),不要受到主進(jìn)程的影響,當(dāng)然也是可以的。那么,在C/C++層是如何實(shí)現(xiàn)的呢?先上關(guān)鍵代碼:

  /**

  * srvname 進(jìn)程名

  * sd 之前創(chuàng)建子進(jìn)程的pid寫入的文件路徑

  */ int start(int argc, char* srvname, char* sd) {

  pthread_tid;

  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 { // 第一個(gè)子進(jìn)程

  LOGI("first fork(): I'am child pid=%d", getpid());

  setsid();

  LOGI("first fork(): setsid=%d", setsid());

  umask(0); //為文件賦予更多的權(quán)限,因?yàn)槔^承來(lái)的文件可能某些權(quán)限被屏蔽

  int pid = fork();

  if (pid == 0) { // 第二個(gè)子進(jìn)程

  // 這里實(shí)際上為了防止重復(fù)開啟線程,應(yīng)該要有相應(yīng)處理

  LOGI("I'am child-child pid=%d", getpid());

  chdir("/"); //修改進(jìn)程工作目錄為根目錄,chdir(“/”)

  //關(guān)閉不需要的從父進(jìn)程繼承過(guò)來(lái)的文件描述符。

  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); // 開啟線程,輪詢?nèi)ケO(jiān)聽啟動(dòng)服務(wù)

  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;

  }

  /**

  * 啟動(dòng)Service

  */ void Java_com_yyh_fork_NativeRuntime_startService(JNIEnv* env, jobjectthiz,

  jstringcchrptr_ProcessName, jstringsdpath) {

  char * rtn = jstringTostring(env, cchrptr_ProcessName); // 得到進(jìn)程名稱

  char * sd = jstringTostring(env, sdpath);

  LOGI("Java_com_yyh_fork_NativeRuntime_startService run....ProcessName:%s", rtn);

  a = rtn;

  start(1, rtn, sd);

  }

  這里有幾個(gè)重點(diǎn)需要理解一下:

  1、為什么要fork兩次?第一次fork的作用是為后面setsid服務(wù)。setsid的調(diào)用者不能是進(jìn)程組組長(zhǎng)(group leader),而第一次調(diào)用的時(shí)候父進(jìn)程是進(jìn)程組組長(zhǎng)。第二次調(diào)用后,把前面一次fork出來(lái)的子進(jìn)程退出,這樣第二次fork出來(lái)的子進(jìn)程,就和他們脫離了關(guān)系。

  2、setsid()作用是什么?setsid() 使得第二個(gè)子進(jìn)程是會(huì)話組長(zhǎng)(sid==pid),也是進(jìn)程組組長(zhǎng)(pgid == pid),并且脫離了原來(lái)控制終端。故不管控制終端怎么操作,新的進(jìn)程正常情況下不會(huì)收到他發(fā)出來(lái)的這些信號(hào)。

  3、umask(0)的作用:由于子進(jìn)程從父進(jìn)程繼承下來(lái)的一些東西,可能并未把權(quán)限繼承下來(lái),所以要賦予他更高的權(quán)限,便于子進(jìn)程操作。

  4chdir (“/”);作用:進(jìn)程活動(dòng)時(shí),其工作目錄所在的文件系統(tǒng)不能卸下,一般需要將工作目錄改變到根目錄。

  5、進(jìn)程從創(chuàng)建它的父進(jìn)程那里繼承了打開的文件描述符。如不關(guān)閉,將會(huì)浪費(fèi)系統(tǒng)資源,造成進(jìn)程所在的文件系統(tǒng)無(wú)法卸下以及引起無(wú)法預(yù)料的錯(cuò)誤。所以在最后,記得關(guān)閉掉從父進(jìn)程繼承過(guò)來(lái)的文件描述符。

  然后,在上面的代碼中開啟線程后做的事,就是循環(huán)去startService(),代碼如下:

  void thread(char* srvname) {

  while(1){

  check_and_restart_service(srvname); // 應(yīng)該要去判斷service狀態(tài),這里一直restart 是不足之處

  sleep(4);

  }

  }

  /**

  * 檢測(cè)服務(wù),如果不存在服務(wù)則啟動(dòng).

  * 通過(guò)am命令啟動(dòng)一個(gè)laucher服務(wù),laucher服務(wù)負(fù)責(zé)進(jìn)行主服務(wù)的檢測(cè),laucher服務(wù)在檢測(cè)后自動(dòng)退出

  */ void check_and_restart_service(char* service) {

  LOGI("當(dāng)前所在的進(jìn)程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);

  }

  /**

  * 執(zhí)行命令

  */ 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);

  }

  }

  這兩個(gè)啟動(dòng)服務(wù)的函數(shù),里面就涉及到一些Android和 Linux 的命令了,這里我就不細(xì)說(shuō)了。特別是am,挺強(qiáng)大的功能的,不僅可以開啟服務(wù),也可以開啟廣播等等然后調(diào)用ndk-build命令進(jìn)行編譯,生成so庫(kù)。

Android中通過(guò)JNI實(shí)現(xiàn)守護(hù)進(jìn)程的方法詳解

C/C++端關(guān)鍵的部分主要是以上這些,接下來(lái)就是Java端調(diào)用。

  首先來(lái)看一下so庫(kù)的加載類,以及C++函數(shù)的調(diào)用:

  package com.yyh.fork;

  import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File;

  public class NativeRuntime {

  private static NativeRuntimetheInstance = null;

  private NativeRuntime() {

  }

  public static NativeRuntimegetInstance() {

  if (theInstance == null)

  theInstance = new NativeRuntime();

  return theInstance;

  }

  /**

  * RunExecutable 啟動(dòng)一個(gè)可自行的lib*.so文件

  * @date 2016-1-18 下午8:22:28

  * @param pacaageName

  * @param filename

  * @param alias 別名

  * @param args 參數(shù)

  * @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;

  StringBuffersb_result = new StringBuffer();

  if (!new File("/data/data/" + alias).exists()) {

  RunLocalUserCommand(pacaageName, cmd4, sb_result); // 拷貝lib/libtest.so到上一層目錄,同時(shí)命名為test.

  sb_result.append(";");

  }

  RunLocalUserCommand(pacaageName, cmd3, sb_result); // 改變test的屬性,讓其變?yōu)榭蓤?zhí)行

 sb_result.append(";");

  RunLocalUserCommand(pacaageName, cmd2_a1, sb_result); // 執(zhí)行test程序.

  sb_result.append(";");

  return sb_result.toString();

  }

  /**

  * 執(zhí)行本地用戶命令

  * @date 2016-1-18 下午8:23:01

  * @param pacaageName

  * @param command

  * @param sb_out_Result

  * @return

  */

  public boolean RunLocalUserCommand(String pacaageName, String command, StringBuffersb_out_Result) {

  Processprocess = null;

  try {

  process = Runtime.getRuntime().exec("sh"); // 獲得shell進(jìn)程

  DataInputStreaminputStream = new DataInputStream(process.getInputStream());

  DataOutputStreamoutputStream = new DataOutputStream(process.getOutputStream());

  outputStream.writeBytes("cd /data/data/" + pacaageName + "\\n"); // 保證在command在自己的數(shù)據(jù)目錄里執(zhí)行,才有權(quán)限寫文件到當(dāng)前目錄

  outputStream.writeBytes(command + " &\\n"); // 讓程序在后臺(tái)運(yùn)行,前臺(tái)馬上返回

  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庫(kù)

  } catch (Exception e) {

  e.printStackTrace();

  }

  }

  }

  然后,我們?cè)谑盏介_機(jī)廣播后,啟動(dòng)該服務(wù)。

  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(Contextcontext, Intentintent) {

  if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {

  Log.i(TAG, "手機(jī)開機(jī)了~~");

  NativeRuntime.getInstance().startService(context.getPackageName() + "/com.yyh.service.HostMonitor", FileUtils.createRootPath());

  } else if (Intent.ACTION_USER_PRESENT.equals(intent.getAction())) {

  }

  }

  }

  Service服務(wù)里面,就可以做該做的事情。

  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(Intentintent, int flags, int startId) {

  Log.i("daemon_java", "HostMonitor: onStartCommand! I can not be Killed!");

  return super.onStartCommand(intent, flags, startId);

  }

  @Override

  public IBinderonBind(Intentarg0) {

  return null;

  }

  }

  當(dāng)然,也不要忘記在Manifest.xml文件配置receiverservice

  <receiver

  android:name="com.yyh.activity.PhoneStatReceiver"

  android:enabled="true"

  android:permission="android.permission.RECEIVE_BOOT_COMPLETED" >

  <intent-filter>

  <actionandroid:name="android.intent.action.BOOT_COMPLETED" />

  <actionandroid:name="android.intent.action.USER_PRESENT" />

  intent-filter>

  receiver>

  <service android:name="com.yyh.service.HostMonitor"

  android:enabled="true"

  android:exported="true">

  service>

  run起來(lái),在程序應(yīng)用里面,結(jié)束掉這個(gè)進(jìn)程,不一會(huì)了,又自動(dòng)起來(lái)了~~~~跟流氓軟件一個(gè)樣,沒錯(cuò),就是這么賤

Android中通過(guò)JNI實(shí)現(xiàn)守護(hù)進(jìn)程的方法詳解

這邊是運(yùn)行在谷歌的原生系統(tǒng)上,Android版本為5.0… 在其他系統(tǒng)下穩(wěn)定性還遠(yuǎn)遠(yuǎn)不足,但是要真正做到殺不死服務(wù)幾乎是不可能的。 總結(jié)一下就是:服務(wù)常駐要應(yīng)對(duì)的不是各種難的技術(shù),而是各大ROMQQ為什么不會(huì)被殺死,是因?yàn)閲?guó)內(nèi)各大ROM不想讓他死

 

文章來(lái)源:伯樂在線

您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?