我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。
有办法做到这一点吗?
我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。
有办法做到这一点吗?
当前回答
根据这里的一些答案,我合并了它们,并添加了一个检查是否安装了一些已知的根管理器应用程序:
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操作。
其他回答
以下是我的代码,基于这里的一些答案:
/**
* 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;
}
这里列出的许多答案都有内在的问题:
Checking for test-keys is correlated with root access but doesn't necessarily guarantee it "PATH" directories should be derived from the actual "PATH" environment variable instead of being hard coded The existence of the "su" executable doesn't necessarily mean the device has been rooted The "which" executable may or may not be installed, and you should let the system resolve its path if possible Just because the SuperUser app is installed on the device does not mean the device has root access yet
Stericson的RootTools库似乎可以更合理地检查root。它还有很多额外的工具和实用程序,所以我强烈推荐它。然而,并没有解释它是如何专门检查根目录的,而且它可能比大多数应用程序真正需要的要重一些。
我已经创建了几个基于RootTools库的实用程序方法。如果你只是想检查“su”可执行文件是否在设备上,你可以使用以下方法:
public static boolean isRootAvailable(){
for(String pathDir : System.getenv("PATH").split(":")){
if(new File(pathDir, "su").exists()) {
return true;
}
}
return false;
}
这个方法简单地遍历“PATH”环境变量中列出的目录,并检查其中是否存在“su”文件。
为了真正检查root访问权限,必须实际运行“su”命令。如果安装了一个像SuperUser这样的应用程序,那么在这一点上它可能会要求root访问,或者如果它已经被授予/拒绝,吐司可能会显示是否授予/拒绝访问。一个很好的命令是“id”,这样您可以验证用户id实际上是0(根)。
下面是一个示例方法来确定是否授予了根访问权:
public static boolean isRootGiven(){
if (isRootAvailable()) {
Process process = null;
try {
process = Runtime.getRuntime().exec(new String[]{"su", "-c", "id"});
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
String output = in.readLine();
if (output != null && output.toLowerCase().contains("uid=0"))
return true;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (process != null)
process.destroy();
}
}
return false;
}
实际测试运行"su"命令是很重要的,因为一些模拟器已经预安装了"su"可执行文件,但只允许某些用户像adb shell一样访问它。
在尝试运行“su”可执行文件之前检查它的存在也是很重要的,因为android已经知道不会正确地处理试图运行缺失命令的进程。随着时间的推移,这些幽灵进程会消耗大量内存。
RootTools库提供了简单的方法来检查根:
RootTools.isRootAvailable()
参考
如果你已经在使用Fabric/Firebase Crashlytics,你可以打电话
CommonUtils.isRooted(context)
这是该方法的当前实现:
public static boolean isRooted(Context context) {
boolean isEmulator = isEmulator(context);
String buildTags = Build.TAGS;
if (!isEmulator && buildTags != null && buildTags.contains("test-keys")) {
return true;
} else {
File file = new File("/system/app/Superuser.apk");
if (file.exists()) {
return true;
} else {
file = new File("/system/xbin/su");
return !isEmulator && file.exists();
}
}
}
public static boolean isEmulator(Context context) {
String androidId = Secure.getString(context.getContentResolver(), "android_id");
return "sdk".equals(Build.PRODUCT) || "google_sdk".equals(Build.PRODUCT) || androidId == null;
}
截至2021年(今天),似乎没有任何可靠的方法或方法来检测根目录,特别是在启用了MagiskHide等强大的隐藏工具的情况下。这里的大多数答案都不再相关,所以不要在生产中使用它。依靠可靠的检查,如SafetyNet,而不是额外的英里检测根,我建议在两个运行时保护你的应用程序,如防止调试器/检测,并确保使用混淆。