我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。

有办法做到这一点吗?


当前回答

根据这里的一些答案,我合并了它们,并添加了一个检查是否安装了一些已知的根管理器应用程序:

fun isProbablyRooted(context: Context, alsoIncludeCheckingRootManagerApp: Boolean = false): Boolean {
    return hasRootManagerSystemApp(context) || (alsoIncludeCheckingRootManagerApp && hasRootManagerSystemApp(context))
}

fun hasRootManagerSystemApp(context: Context): Boolean {
    val rootAppsPackageNames = arrayOf("com.topjohnwu.magisk", "eu.chainfire.supersu", "com.koushikdutta.superuser", "com.noshufou.android.su", "me.phh.superuser")
    rootAppsPackageNames.forEach { rootAppPackageName ->
        try {
            context.packageManager.getApplicationInfo(rootAppPackageName, 0)
            return true
        } catch (e: Exception) {
        }
    }
    return false
}

fun hasSuBinary(): Boolean {
    return try {
        findBinary("su")
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

private fun findBinary(binaryName: String): Boolean {
    val paths = System.getenv("PATH")
    if (!paths.isNullOrBlank()) {
        val systemPlaces: List<String> = paths.split(":")
        return systemPlaces.firstOrNull { File(it, binaryName).exists() } != null
    }
    val places = arrayOf("/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/", "/data/local/bin/",
            "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/")
    return places.firstOrNull { File(it, binaryName).exists() } != null
}

清单:

<queries>
    <package android:name="com.topjohnwu.magisk" />
    <package android:name="eu.chainfire.supersu" />
    <package android:name="com.koushikdutta.superuser" />
    <package android:name="com.noshufou.android.su" />
    <package android:name="me.phh.superuser" />
</queries>

当然,这仍然是一个猜测,就像所有其他解一样。 例如,用户可以安装Magisk,而无需对设备进行root操作。

其他回答

下面是一个类,它将以三种方式之一检查Root。

/** @author Kevin Kowalewski */
public class RootUtil {
    public static boolean isDeviceRooted() {
        return checkRootMethod1() || checkRootMethod2() || checkRootMethod3();
    }

    private static boolean checkRootMethod1() {
        String buildTags = android.os.Build.TAGS;
        return buildTags != null && buildTags.contains("test-keys");
    }

    private static boolean checkRootMethod2() {
        String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
                "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
        for (String path : paths) {
            if (new File(path).exists()) return true;
        }
        return false;
    }

    private static boolean checkRootMethod3() {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" });
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            if (in.readLine() != null) return true;
            return false;
        } catch (Throwable t) {
            return false;
        } finally {
            if (process != null) process.destroy();
        }
    }
}

在我的应用程序中,我通过执行“su”命令检查设备是否根。但是今天我删除了这部分代码。为什么?

因为我的应用程序成了内存杀手。怎么做?让我告诉你我的故事。

There were some complaints that my application was slowing down devices(Of course I thought that can not be true). I tried to figure out why. So I used MAT to get heap dumps and analyze, and everything seemed perfect. But after relaunching my app many times I realized that device is really getting slower and stopping my application didn't make it faster (unless I restart device). I analyzed dump files again while device is very slow. But everything was still perfect for dump file. Then I did what must be done at first. I listed processes.

$ adb shell ps

使震惊;我的应用程序有许多进程(应用程序的进程标签在manifest上)。有些是僵尸,有些不是。

对于一个只有一个Activity并只执行“su”命令的示例应用程序,我意识到在每次启动应用程序时都会创建一个僵尸进程。起初,这些僵尸进程分配0KB,但后来发生了一些事情,僵尸进程与我的应用程序的主进程占用了几乎相同的kb,它们成为了标准进程。

在bugs.sun.com: http://bugs.sun.com/view_bug.do?bug_id=6474073上有一个关于相同问题的错误报告,这解释了如果命令没有找到,将使用exec()方法创建僵尸。但我仍然不明白它们为什么以及如何成为标准流程并保持重要的kb。(这并不是一直都在发生)

你可以尝试如果你想下面的代码示例;

String commandToExecute = "su";
executeShellCommand(commandToExecute);

简单的命令执行方法;

private boolean executeShellCommand(String command){
    Process process = null;            
    try{
        process = Runtime.getRuntime().exec(command);
        return true;
    } catch (Exception e) {
        return false;
    } finally{
        if(process != null){
            try{
                process.destroy();
            }catch (Exception e) {
            }
        }
    }
}

总结:总结;我没有建议你确定设备是否扎根。但如果我是你,我不会使用Runtime.getRuntime().exec()。

顺便说一下;RootTools.isRootAvailable()也会引起同样的问题。

if [[ "`adb shell which su | grep -io "permission denied"`" != "permission denied" ]]; then
   echo "Yes. Rooted device."
 else
   echo "No. Device not rooted. Only limited tasks can be performed. Done."
    zenity --warning --title="Device Not Rooted" --text="The connected Android Device is <b>NOT ROOTED</b>. Only limited tasks can be performed." --no-wrap
fi

RootTools库提供了简单的方法来检查根:

RootTools.isRootAvailable()

参考

以下是我的代码,基于这里的一些答案:

 /**
   * Checks if the phone is rooted.
   * 
   * @return <code>true</code> if the phone is rooted, <code>false</code>
   * otherwise.
   */
  public static boolean isPhoneRooted() {

    // get from build info
    String buildTags = android.os.Build.TAGS;
    if (buildTags != null && buildTags.contains("test-keys")) {
      return true;
    }

    // check if /system/app/Superuser.apk is present
    try {
      File file = new File("/system/app/Superuser.apk");
      if (file.exists()) {
        return true;
      }
    } catch (Throwable e1) {
      // ignore
    }

    return false;
  }