私のアプリには、ルートが利用可能なデバイスでのみ機能する特定の機能があります。この機能を使用すると失敗する (そして、適切なエラー メッセージをユーザーに表示する) よりも、最初に root が利用可能かどうかを黙ってチェックし、そうでない場合は、最初にそれぞれのオプションを非表示にする機能を希望します。 .
これを行う方法はありますか?
これは、3つの方法のいずれかでルートをチェックするクラスです。
/** @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」コマンドを実行して、デバイスがルート化されているかどうかを確認していました。しかし、今日、コードのこの部分を削除しました。なんで?
私のアプリケーションがメモリキラーになったからです。どのように?私の話をしましょう。
私のアプリケーションがデバイスの速度を低下させているという苦情がいくつかありました (もちろん、そんなことはあり得ないと思いました)。私はその理由を理解しようとしました。そこで、MAT を使用してヒープ ダンプを取得して分析したところ、すべてが完璧に見えました。しかし、アプリを何度も再起動した後、デバイスが本当に遅くなり、アプリケーションを停止しても速度が速くならないことに気付きました (デバイスを再起動しない限り)。デバイスが非常に遅いときに、再度ダンプ ファイルを分析しました。しかし、すべてがダンプファイルにとって完璧でした。そして、まずやるべきことをやった。プロセスをリストしました。
$ adb shell ps
驚きます。私のアプリケーションには多くのプロセスがありました (私のアプリケーションのプロセス タグがマニフェストにあります)。それらのいくつかはゾンビであり、いくつかはそうではありませんでした。
単一のアクティビティを持ち、「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() も同じ問題を引き起こします。
ここにリストされている回答の多くには、固有の問題があります。
StericsonのRootToolsライブラリは、より正当にルートをチェックしているようです。また、追加のツールやユーティリティがたくさんあるので、強くお勧めします。ただし、ルートを具体的にどのようにチェックするかについての説明はなく、ほとんどのアプリが実際に必要とするよりも少し重いかもしれません.
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 のようなアプリがインストールされている場合、この時点でルート アクセスを要求するか、すでに許可/拒否されている場合は、アクセスが許可/拒否されたかどうかを示すトーストが表示されることがあります。実行するのに適したコマンドは「id」です。これにより、ユーザー ID が実際に 0 (root) であることを確認できます。
root アクセス権が付与されているかどうかを判断する方法の例を次に示します。
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」実行可能ファイルがプリインストールされていますが、adb シェルのように特定のユーザーのみがアクセスできるため、実際に「su」コマンドの実行をテストすることが重要です。
また、「su」実行可能ファイルを実行する前にその存在を確認することも重要です。Android は、欠落しているコマンドを実行しようとするプロセスを適切に破棄しないことが知られているためです。これらのゴースト プロセスは、時間の経過とともにメモリ消費を増大させる可能性があります。
http://code.google.com/p/roottools/
jar ファイルを使用したくない場合は、次のコードを使用してください。
public static boolean findBinary(String binaryName) {
boolean found = false;
if (!found) {
String[] places = { "/sbin/", "/system/bin/", "/system/xbin/",
"/data/local/xbin/", "/data/local/bin/",
"/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/" };
for (String where : places) {
if (new File(where + binaryName).exists()) {
found = true;
break;
}
}
}
return found;
}
プログラムは su フォルダーを見つけようとします。
private static boolean isRooted() {
return findBinary("su");
}
例:
if (isRooted()) {
textView.setText("Device Rooted");
} else {
textView.setText("Device Unrooted");
}
isRootAvailable() を使用する代わりに、isAccessGiven() を使用できます。RootTools wikiから直接:
if (RootTools.isAccessGiven()) {
// your app has been granted root access
}
RootTools.isAccessGiven() は、デバイスがルート化されていることを確認するだけでなく、アプリに対して su を呼び出し、アクセス許可を要求し、アプリにルート アクセス許可が正常に付与された場合は true を返します。これは、必要なときにアクセスが許可されることを確認するために、アプリの最初のチェックとして使用できます。
この目的でシステム プロパティ を設定するために使用される一部の変更されたビルド。ro.modversion
物事は進んでいるようです。数か月前の TheDude からの私のビルドには次のものがあります。
cmb@apollo:~$ adb -d shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [htc_dream-eng 1.5 CUPCAKE eng.TheDudeAbides.20090427.235325 test-keys]
[ro.build.version.incremental]: [eng.TheDude.2009027.235325]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Mon Apr 20 01:42:32 CDT 2009]
[ro.build.date.utc]: [1240209752]
[ro.build.type]: [eng]
[ro.build.user]: [TheDude]
[ro.build.host]: [ender]
[ro.build.tags]: [test-keys]
[ro.build.product]: [dream]
[ro.build.description]: [kila-user 1.1 PLAT-RC33 126986 ota-rel-keys,release-keys]
[ro.build.fingerprint]: [tmobile/kila/dream/trout:1.1/PLAT-RC33/126986:user/ota-rel-keys,release-keys]
[ro.build.changelist]: [17615# end build properties]
一方、1.5 イメージを実行している 1.5 SDK のエミュレーターにもルートがあり、おそらくAndroid Dev Phone 1 (おそらく許可したい) に似ており、これがあります。
cmb@apollo:~$ adb -e shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.version.incremental]: [148875]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Thu May 14 18:09:10 PDT 2009]
[ro.build.date.utc]: [1242349750]
[ro.build.type]: [eng]
[ro.build.user]: [android-build]
[ro.build.host]: [undroid16.mtv.corp.google.com]
[ro.build.tags]: [test-keys]
[ro.build.product]: [generic]
[ro.build.description]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.fingerprint]: [generic/sdk/generic/:1.5/CUPCAKE/148875:eng/test-keys]
小売ビルドに関しては、手元にあるものはありませんが、下のさまざまな検索site:xda-developers.com
が参考になります。これはオランダのG1 です。これには が含まれてro.build.tags
いないことがわかりtest-keys
ます。おそらく、これが使用する最も信頼できるプロパティだと思います。
ここにいくつかの答えに基づいた私のコードがあります:
/**
* 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;
}
デバイスがアプリからルート対応かどうかを確認する場合は、次の 2 つの追加のアイデアがあります。
Runtime.getRuntime().exec()
/system/app/Superuser.apk
場所で SuperUser.apk を探しますGoogle SafetyNet Attestation API を使用すると、デバイスがルート化されているかどうかを簡単に確認できます。
build.gradle(:app) に依存関係を追加
実装「com.google.android.gms:play-services-safetynet:17.0.0」
public static void sendSafetyNetRequest(Activity context) {
if(GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context, 13000000) == ConnectionResult.SUCCESS) {
Log.e(TAG, "The SafetyNet Attestation API is available");
// TODO(developer): Change the nonce generation to include your own, used once value,
// ideally from your remote server.
String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
Random mRandom = new SecureRandom();
byte[] bytes = new byte[24];
mRandom.nextBytes(bytes);
try {
byteStream.write(bytes);
byteStream.write(nonceData.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
byte[] nonce = byteStream.toByteArray();
SafetyNetClient client = SafetyNet.getClient(context);
Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, API_KEY_FROM_STEP_2_LINK);
task.addOnSuccessListener(context, attestationResponse -> {
/*
TODO(developer): Forward this result to your server together with
the nonce for verification.
You can also parse the JwsResult locally to confirm that the API
returned a response by checking for an 'error' field first and before
retrying the request with an exponential backoff.
NOTE: Do NOT rely on a local, client-side only check for security, you
must verify the response on a remote server!
*/
String jwsResult = attestationResponse.getJwsResult();
Log.e(TAG, "Success! SafetyNet result:\n" + jwsResult + "\n");
if (jwsResult == null) {
Log.e(TAG, "jwsResult Null");
}
final String[] jwtParts = jwsResult.split("\\.");
if (jwtParts.length == 3) {
String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT));
Log.e(TAG, "decodedPayload : " + decodedPayload);
}
});
task.addOnFailureListener(context, e -> {
// An error occurred while communicating with the service.
String mResult = null;
if (e instanceof ApiException) {
// An error with the Google Play Services API contains some additional details.
ApiException apiException = (ApiException) e;
Util.showLog(TAG, "Error: " +
CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " +
apiException.getStatusMessage());
} else {
// A different, unknown type of error occurred.
Log.e(TAG, "ERROR! " + e.getMessage());
}
});
} else {
Log.e(TAG, "Prompt user to update Google Play services.";
}
} `
ctsProfileMatch と basicIntegrity の両方が false である場合は、ログで decodedPayload を確認してください。これは、デバイスがルート化されていることを意味します。Attestation API は、次のような JWS 応答を返します。
{ "nonce": "6pLrr9zWyl6TNzj+kpbR4LZcfPY3U2FmZXR5IE5ldCBTYW1wbGU6IDE2MTQ2NzkwMTIzNjc=", "timestampMs": 9860437986543, "apkPackageName": " your package name will be displayed here", "ctsProfileMatch": true, "apkDigestSha256": [ "base64 encoded, SHA-256 hash of the certificate used to sign requesting app" ], "basicIntegrity": true, "evaluationType": "BASIC" }
詳細については、このリンクを確認してください。
確かにそれは興味深い質問であり、これまで誰も賞に値するものはありませんでした. 次のコードを使用します。
boolean isRooted() {
try {
ServerSocket ss = new ServerSocket(81);
ss.close();
return true;
} catch (Exception e) {
// not sure
}
return false;
}
ネットワークが利用できないため、例外が発生する可能性があるため、コードは確かに防弾ではありません。このメソッドが true を返した場合は 99% と確信できますが、それ以外の場合は 50% でないと確信できます。ネットワークの許可もソリューションを台無しにする可能性があります。