1. 程式人生 > >Android如何監聽第三方應用的啟動

Android如何監聽第三方應用的啟動

個人總結的監聽第三方應用啟動的方法有以下幾種:

1.Root狀態下Shell監聽ActivityManager的log,或者ps程序

2.參考程式鎖的實現,監聽後臺執行的程序

3.深入framework,通過反射等方式注入hook

第一種方式監聽應用的啟動,restart都能監控到,不過這種方式限制很大,在最新的android版本中,再加上各手機廠商對於安全性的努力,使得Root變的很困難。

第二種方式採用不斷輪詢的方式檢查後臺程序,個人以為這樣非常的耗費效能,時間間隔長的話實時性又無法保證。所以最佳的方式還是採用監聽的方式,向ActivityManagerService中設定一個監聽,使用者啟動應用都會通知。

第三種方案看起來是最佳方案,不過它需要系統許可權,實現類似monkey的功能,setActivityController這個方法設定IActivityController介面。

package android.app;

import android.content.Intent;

/**
 * Testing interface to monitor what is happening in the activity manager
 * while tests are running.  Not for normal application development.
 * {@hide}
 */
interface IActivityController
{
    /**
     * The system is trying to start an activity.  Return true to allow
     * it to be started as normal, or false to cancel/reject this activity.
     */
    boolean activityStarting(in Intent intent, String pkg);
    
    /**
     * The system is trying to return to an activity.  Return true to allow
     * it to be resumed as normal, or false to cancel/reject this activity.
     */
    boolean activityResuming(String pkg);
    
    /**
     * An application process has crashed (in Java).  Return true for the
     * normal error recovery (app crash dialog) to occur, false to kill
     * it immediately.
     */
    boolean appCrashed(String processName, int pid,
            String shortMsg, String longMsg,
            long timeMillis, String stackTrace);
    
    /**
     * Early call as soon as an ANR is detected.
     */
    int appEarlyNotResponding(String processName, int pid, String annotation);

    /**
     * An application process is not responding.  Return 0 to show the "app
     * not responding" dialog, 1 to continue waiting, or -1 to kill it
     * immediately.
     */
    int appNotResponding(String processName, int pid, String processStats);

    /**
     * The system process watchdog has detected that the system seems to be
     * hung.  Return 1 to continue waiting, or -1 to let it continue with its
     * normal kill.
     */
    int systemNotResponding(String msg);
}
IActivityController.aidl的介面可以很容易監聽每個process。

綜上,普通APP開發,在沒有root和系統許可權的支援下,就只能老老實實的用第二種方案了,但是。。。Android 5.0之後對於ActivityManager獲取的RunningTask,RunningProcess,RunningServiceInfo等又有了諸多限制。

既然Android 5.0之後ActivityManager獲取的資訊稀少了,我們不如換一種思路,不再依賴ActivityManager,轉而去分析根目錄下額proc目錄,這個proc目錄是幹什麼的?

/proc是一個虛擬檔案系統,其下面的檔案不是真實存在的,不佔用實際儲存空間。
/proc/cmdline:顯示核心啟動的命令列。
/proc/cpuinfo:顯示系統cpu的資訊。
/proc/filesystems,顯示當前註冊了的檔案系統列表,nodev表示為虛擬檔案系統。
/proc/interrupts:顯示當前系統的中斷資訊.
/proc/ioports:被佔用的輸入/輸出地址範圍列表。
/proc/kmsg:輸出核心訊息日誌。
/proc/loadavg:監控cpu平均負載,其數值為所有核上cpu佔用的累加值,前三個分別表示最近1、5、15分鐘的平均負載,第四個表示當前執行程序數和程序總數,最後一個表示最近執行的程序id。
/proc/locks:開啟檔案上的加鎖資訊。
/proc/meminfo:顯示物理及虛擬記憶體使用情況。
/proc/misc:核心函式misc_register登記的裝置驅動程式。
/proc/modules:載入的核心模組列表。
Proc/mounts:當前系統所安裝的檔案系統資訊(包括手動安裝的)。
/proc/stat:系統簡要資訊。
/proc/uptime:分別表示系統啟動時間和系統空閒時間。
/proc/version:系統核心版本。
/proc/net:其實際掛載點是/proc/self/net,能夠顯示當前各種網路情況,例如通過tcp檔案可以檢視tcp連線數及連線情況。
/proc/sys 報告各種不同的核心引數,某些引數能在root的情況下進行修改。
/Proc/devices 當前掛載的所有軟硬體裝置(字元裝置和塊裝置),包括主裝置號和裝置名稱。
/proc/asound:音效卡相關的資訊。
/proc/buddyinfo:每個記憶體區中每個order有多少塊可用,和記憶體碎片問題有關。
/proc/bus:輸入裝置資訊。
/proc/cgroups:檢視cgroups子系統資訊。
/proc/diskstats:用於顯示磁碟、分割槽和統計資訊。
/proc/execdomains:安全相關的資訊。
/proc/fb:幀緩衝裝置資訊。
/proc/iomem:記錄實體地址的分配情況。
/proc/kallsyms:核心符號表資訊。
/proc/pagetypeinfo:記憶體分頁資訊。
/proc/partitions:分割槽資訊
/proc/sched_debug:cpu排程資訊。
/proc/softirqs:軟中斷情況。
/proc/vmallocinfo:vmalloc記憶體分配資訊。
/proc/vmstat:統計虛擬記憶體資訊。
/proc/pid:顯示進城相關的所有資訊。

/*
 * Copyright (C) 2015. Jared Rummler <[email protected]>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.jaredrummler.android.processes.models;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Parcel;

import java.io.IOException;

public class AndroidAppProcess extends AndroidProcess {

  /** {@code true} if the process is in the foreground */
  public boolean foreground;

  /** The user id of this process. */
  public int uid;

  private final Cgroup cgroup;

  public AndroidAppProcess(int pid) throws IOException, NotAndroidAppProcessException {
    super(pid);
    cgroup = super.cgroup();
    ControlGroup cpuacct = cgroup.getGroup("cpuacct");
    ControlGroup cpu = cgroup.getGroup("cpu");
    if (cpu == null || cpuacct == null || !cpuacct.group.contains("pid_")) {
      throw new NotAndroidAppProcessException(pid);
    }
    foreground = !cpu.group.contains("bg_non_interactive");
    try {
      uid = Integer.parseInt(cpuacct.group.split("/")[1].replace("uid_", ""));
    } catch (Exception e) {
      uid = status().getUid();
    }
  }

  /**
   * @return the app's package name
   * @see #name
   */
  public String getPackageName() {
    return name.split(":")[0];
  }

  /**
   * Retrieve overall information about the application package.
   *
   * <p>Throws {@link PackageManager.NameNotFoundException} if a package with the given name can
   * not be found on the system.</p>
   *
   * @param context
   *     the application context
   * @param flags
   *     Additional option flags. Use any combination of
   *     {@link PackageManager#GET_ACTIVITIES}, {@link PackageManager#GET_GIDS},
   *     {@link PackageManager#GET_CONFIGURATIONS}, {@link PackageManager#GET_INSTRUMENTATION},
   *     {@link PackageManager#GET_PERMISSIONS}, {@link PackageManager#GET_PROVIDERS},
   *     {@link PackageManager#GET_RECEIVERS}, {@link PackageManager#GET_SERVICES},
   *     {@link PackageManager#GET_SIGNATURES}, {@link PackageManager#GET_UNINSTALLED_PACKAGES}
   *     to modify the data returned.
   * @return a PackageInfo object containing information about the package.
   */
  public PackageInfo getPackageInfo(Context context, int flags)
      throws PackageManager.NameNotFoundException {
    return context.getPackageManager().getPackageInfo(getPackageName(), flags);
  }

  @Override public Cgroup cgroup() {
    return cgroup;
  }

  @Override public void writeToParcel(Parcel dest, int flags) {
    super.writeToParcel(dest, flags);
    dest.writeParcelable(cgroup, flags);
    dest.writeByte((byte) (foreground ? 0x01 : 0x00));
  }

  protected AndroidAppProcess(Parcel in) {
    super(in);
    cgroup = in.readParcelable(Cgroup.class.getClassLoader());
    foreground = in.readByte() != 0x00;
  }

  public static final Creator<AndroidAppProcess> CREATOR = new Creator<AndroidAppProcess>() {

    @Override public AndroidAppProcess createFromParcel(Parcel source) {
      return new AndroidAppProcess(source);
    }

    @Override public AndroidAppProcess[] newArray(int size) {
      return new AndroidAppProcess[size];
    }
  };

  public static final class NotAndroidAppProcessException extends Exception {

    public NotAndroidAppProcessException(int pid) {
      super(String.format("The process %d does not belong to any application", pid));
    }
  }

}

AndroidAppProcess根據proc資訊解析出forceground、uid、packageName等資訊,我們再看看基類AndroidProcess
/*
 * Copyright (C) 2015. Jared Rummler <[email protected]>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package com.jaredrummler.android.processes.models;

import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;

import java.io.IOException;

public class AndroidProcess implements Parcelable {

  /**
   * Get the name of a running process.
   *
   * @param pid
   *     the process id.
   * @return the name of the process.
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  static String getProcessName(int pid) throws IOException {
    String cmdline = null;
    try {
      cmdline = ProcFile.readFile(String.format("/proc/%d/cmdline", pid)).trim();
    } catch (IOException ignored) {
    }
    if (TextUtils.isEmpty(cmdline) || "null".equals(cmdline)) {
      return Stat.get(pid).getComm();
    }
    return cmdline;
  }

  /** the process name */
  public final String name;

  /** the process id */
  public final int pid;

  /**
   * AndroidProcess constructor
   *
   * @param pid
   *     the process id
   * @throws IOException
   *     if /proc/[pid] does not exist or we don't have read access.
   */
  public AndroidProcess(int pid) throws IOException {
    this.pid = pid;
    this.name = getProcessName(pid);
  }

  /**
   * Read the contents of a file in /proc/[pid]/[filename].
   *
   * @param filename
   *     the relative path to the file.
   * @return the contents of the file.
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public String read(String filename) throws IOException {
    return ProcFile.readFile(String.format("/proc/%d/%s", pid, filename));
  }

  /**
   * @return the contents of /proc/[pid]/attr/current
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public String attr_current() throws IOException {
    return read("attr/current");
  }

  /**
   * <p>/proc/[pid]/cmdline</p>
   *
   * <p>This read-only file holds the complete command line for the process, unless the process is
   * a zombie. In the latter case, there is nothing in this file: that is, a read on this file will
   * return 0 characters. The command-line arguments appear in this file as a set of strings
   * separated by null bytes ('\0'), with a further null byte after the last string.</p>
   *
   * @return the name of the process. (note: process name may be empty. In case it is empty get
   * the process name from /proc/[pid]/stat).
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   * @see #name
   */
  public String cmdline() throws IOException {
    return read("cmdline");
  }

  /**
   * @return the {@link Cgroup} for this process
   * @throws IOException
   */
  public Cgroup cgroup() throws IOException {
    return Cgroup.get(pid);
  }

  /**
   * @return the oom_adj value for this process
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public int oom_adj() throws IOException {
    return Integer.parseInt(read("oom_adj"));
  }

  /**
   * @return the oom_score_adj value for this process
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public int oom_score_adj() throws IOException {
    return Integer.parseInt(read("oom_score_adj"));
  }

  /**
   * @return the {@link Stat} for this process
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public Stat stat() throws IOException {
    return Stat.get(pid);
  }

  /**
   * @return the {@link Statm} for this process
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public Statm statm() throws IOException {
    return Statm.get(pid);
  }

  /**
   * @return the {@link Status} for this process
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public Status status() throws IOException {
    return Status.get(pid);
  }

  /**
   * The symbolic name corresponding to the location in the kernel where the process is sleeping.
   *
   * @return the contents of /proc/[pid]/wchan
   * @throws IOException
   *     if the file does not exist or we don't have read permissions.
   */
  public String wchan() throws IOException {
    return read("wchan");
  }

  @Override public int describeContents() {
    return 0;
  }

  @Override public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.name);
    dest.writeInt(this.pid);
  }

  protected AndroidProcess(Parcel in) {
    this.name = in.readString();
    this.pid = in.readInt();
  }

  public static final Creator<AndroidProcess> CREATOR = new Creator<AndroidProcess>() {

    @Override public AndroidProcess createFromParcel(Parcel source) {
      return new AndroidProcess(source);
    }

    @Override public AndroidProcess[] newArray(int size) {
      return new AndroidProcess[size];
    }
  };

}
AndroidProcess解析了pid,process name等資訊

參考文章:

github地址: