@Override
 protected void onDestroy() {
   super.onDestroy();
   ApplicationHelper.releaseImageView((ImageView) findViewById(R.id.recoverCommentImage));
   ApplicationHelper.releaseImageView((ImageView) findViewById(R.id.recoverBgImage));
   ApplicationHelper.releaseImageView((ImageButton) findViewById(R.id.recoverYesButton));
   ApplicationHelper.releaseImageView((ImageButton) findViewById(R.id.recoverNoButton));
   mBg.release();
 }
public class LogHelper {

  protected LogHelper() {
    // Empty
  }

  private static final int VERBOSE = Log.VERBOSE;
  private static final int DEBUG = Log.DEBUG;
  private static final int INFO = Log.INFO;
  private static final int WARN = Log.WARN;
  private static final int ERROR = Log.ERROR;
  private static final int WTF = Log.ASSERT;

  // Used to identify the source of a log message.
  // It usually identifies the class or activity where the log call occurs.
  protected static String TAG = ApplicationHelper.packageName();
  // Here I use the application's packageName

  protected static void log(
      @IntRange(from = VERBOSE, to = WTF) final int level,
      @Nullable final String msg,
      final @Nullable Throwable throwable) {
    final StackTraceElement[] elements = new Throwable().getStackTrace();
    String callerClassName = "?";
    String callerMethodName = "?";
    String callerLineNumber = "?";
    if (elements.length >= 3) {
      callerClassName = elements[2].getClassName();
      callerClassName = callerClassName.substring(callerClassName.lastIndexOf('.') + 1);
      if (callerClassName.indexOf("$") > 0) {
        callerClassName = callerClassName.substring(0, callerClassName.indexOf("$"));
      }
      callerMethodName = elements[2].getMethodName();
      callerMethodName = callerMethodName.substring(callerMethodName.lastIndexOf('_') + 1);
      if (callerMethodName.equals("<init>")) {
        callerMethodName = callerClassName;
      }
      callerLineNumber = String.valueOf(elements[2].getLineNumber());
    }
    final String stack =
        "["
            + callerClassName
            + "."
            + callerMethodName
            + "():"
            + callerLineNumber
            + "]"
            + (StringHelper.nullOrEmpty(msg) ? "" : " ");
    switch (level) {
      case VERBOSE:
        android.util.Log.v(TAG, stack + msg, throwable);
        break;
      case DEBUG:
        android.util.Log.d(TAG, stack + msg, throwable);
        break;
      case INFO:
        android.util.Log.i(TAG, stack + msg, throwable);
        break;
      case WARN:
        android.util.Log.w(TAG, stack + msg, throwable);
        break;
      case ERROR:
        android.util.Log.e(TAG, stack + msg, throwable);
        break;
      case WTF:
        android.util.Log.wtf(TAG, stack + msg, throwable);
        break;
      default:
        break;
    }
  }

  public static void debug(@NonNull final String msg) {
    log(DEBUG, msg, null);
  }

  public static void debug(@Nullable final String msg, @NonNull final Throwable throwable) {
    log(DEBUG, msg, throwable);
  }

  public static void verbose(@NonNull final String msg) {
    log(VERBOSE, msg, null);
  }

  public static void verbose(@Nullable final String msg, @NonNull final Throwable throwable) {
    log(VERBOSE, msg, throwable);
  }

  public static void info(@NonNull final String msg) {
    log(INFO, msg, null);
  }

  public static void info(@Nullable final String msg, @NonNull final Throwable throwable) {
    log(INFO, msg, throwable);
  }

  public static void warning(@NonNull final String msg) {
    log(WARN, msg, null);
  }

  public static void warning(@Nullable final String msg, @NonNull final Throwable throwable) {
    log(WARN, msg, throwable);
  }

  public static void error(@NonNull final String msg) {
    log(ERROR, msg, null);
  }

  public static void error(@Nullable final String msg, @NonNull final Throwable throwable) {
    log(ERROR, msg, throwable);
  }

  public static void wtf(@NonNull final String msg) {
    log(WTF, msg, null);
  }

  public static void wtf(@Nullable final String msg, @NonNull final Throwable throwable) {
    log(WTF, msg, throwable);
  }

  @Deprecated
  public static void wtf(@NonNull final Throwable throwable) {
    log(WTF, null, throwable);
  }
}
 @Deprecated
 public static boolean permission(@NonNull final String permission) {
   return RuntimePermissionsCompat.isGranted(ApplicationHelper.context(), permission);
 }
  /**
   * Get all running apps 如何判断多个进程是属于一个app? 1. 相同的前缀包名 返回的运行时应用列表, 按照内存占用量降序排序
   *
   * @param ctx
   * @return
   */
  public List<Map<String, Object>> getRunningApps(Context ctx) {

    // get all running app processes
    List<RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
    if (runningAppProcesses == null) return null;

    // sort all running app processes
    Collections.sort(
        runningAppProcesses,
        new Comparator<RunningAppProcessInfo>() {
          @Override
          public int compare(RunningAppProcessInfo lhs, RunningAppProcessInfo rhs) {
            String lhsPkgName = lhs.processName.split(":")[0];
            String rhsPkgName = rhs.processName.split(":")[0];
            return lhsPkgName.compareToIgnoreCase(rhsPkgName);
          }
        });

    Intent intent = new Intent(Intent.ACTION_MAIN);
    intent.addCategory(Intent.CATEGORY_HOME);
    ResolveInfo resolveInfo =
        appHelper.getPm().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
    String currentHomePackage = resolveInfo.activityInfo.packageName;
    IGNORE_PKGS.add(currentHomePackage);

    // remove all ignored packages
    Log.i(TAG, "before remove :" + runningAppProcesses.size());
    ArrayList<RunningAppProcessInfo> toBeRemoved = new ArrayList<RunningAppProcessInfo>();
    for (RunningAppProcessInfo runningProcessInfo : runningAppProcesses) {
      Log.i(TAG, "after sort:" + runningProcessInfo.processName);
      try {
        String appPkgname = runningProcessInfo.processName.split(":")[0];
        if (IGNORE_PKGS.contains(appPkgname)) {
          toBeRemoved.add(runningProcessInfo);
        }
      } catch (Exception e) {
        e.printStackTrace();
        Log.w(TAG, "exception raised while remove ignore list:" + e.toString());
      }
    }
    runningAppProcesses.removeAll(toBeRemoved);
    Log.i(
        TAG,
        "removed :"
            + toBeRemoved.size()
            + " ignored packages, after remove size:"
            + runningAppProcesses.size());

    Map<String, Map<String, Object>> runningApps = new HashMap<String, Map<String, Object>>();
    // iterate the running app process list
    for (RunningAppProcessInfo runningProcessInfo : runningAppProcesses) {
      String[] pkgNameSections = runningProcessInfo.processName.split(":");
      Log.i(
          TAG,
          "process name:"
              + runningProcessInfo.processName
              + ", pkgNameSections length:"
              + pkgNameSections.length);
      if (pkgNameSections.length == 0) {
        // TODO: process name is ""?
        continue;
      }
      String appPkgname = pkgNameSections[0];
      String subProcessName = (pkgNameSections.length == 2) ? pkgNameSections[1] : null;
      Log.i(TAG, "appPkgname:" + appPkgname + ", subProcessName:" + subProcessName);

      // get application info from package name
      ApplicationInfo appInfo = appHelper.getApplicationInfo(appPkgname);
      if (appInfo == null) appInfo = appHelper.getApplicationInfo(runningProcessInfo.processName);

      // get app info by app package name
      Map<String, Object> appInfoMap = runningApps.get(appPkgname);
      if (appInfoMap == null) appInfoMap = new HashMap<String, Object>();

      // put app package name
      if (!appInfoMap.containsKey(PKG_NAME)) {
        appInfoMap.put(PKG_NAME, appPkgname);
      }

      // put app uid
      if (!appInfoMap.containsKey(APP_UID)) {
        appInfoMap.put(APP_UID, runningProcessInfo.uid);
      }

      // put recommend flag
      if (!appInfoMap.containsKey(APP_RECOMMEND_CLEAN)) {
        appInfoMap.put(APP_RECOMMEND_CLEAN, !NOT_RECOMMAND_PKGS.contains(appPkgname));
      }

      // only main process can get human readable name
      if (appInfo != null) {
        appInfoMap.put(APP_NAME, appInfo.loadLabel(appHelper.getPm()).toString());
      } else if (!appInfoMap.containsKey(APP_NAME)) {
        appInfoMap.put(APP_NAME, appPkgname);
      }

      // get and put app icon
      if (appInfo != null) {
        appInfoMap.put(APP_ICON, appInfo.loadIcon(appHelper.getPm()));
      } else if (!appInfoMap.containsKey(APP_ICON)) {
        appInfoMap.put(APP_ICON, android.R.drawable.sym_def_app_icon);
      }

      // get memory size of app process, store mem info <Key(pid), Value(Debug.MemoryInfo)>
      SparseArray<Debug.MemoryInfo> memoryInfoArray =
          (SparseArray<Debug.MemoryInfo>) appInfoMap.get(APP_PROC_MEM);
      if (memoryInfoArray == null) memoryInfoArray = new SparseArray<Debug.MemoryInfo>();
      Debug.MemoryInfo memInfo = getDebugMemoryInfos(new int[] {runningProcessInfo.pid})[0];
      memoryInfoArray.put(runningProcessInfo.pid, memInfo);
      appInfoMap.put(APP_PROC_MEM, memoryInfoArray);

      // update total
      int totalPss = 0;
      if (appInfoMap.containsKey(APP_TOTAL_PSS)) totalPss = (Integer) appInfoMap.get(APP_TOTAL_PSS);
      totalPss += memInfo.getTotalPss();
      appInfoMap.put(APP_TOTAL_PSS, totalPss);

      // add process in process array
      List<RunningAppProcessInfo> processes =
          (ArrayList<RunningAppProcessInfo>) appInfoMap.get(APP_PROCS);
      if (processes == null) processes = new ArrayList<RunningAppProcessInfo>();
      processes.add(runningProcessInfo);
      appInfoMap.put(APP_PROCS, processes);

      runningApps.put(appPkgname, appInfoMap);
    }
    return sortMap(runningApps);
  }