如何检查APK是否已签名或为“调试构建”?

132
据我所知,在安卓中,“发布版本”指的是已签名的APK。如何从代码中检查它或者Eclipse是否有某种秘密定义?
我需要这个来调试从Web服务数据填充ListView项目(不,logcat不是一个选项)。
我的想法:
- 应用程序的android:debuggable属性,但由于某些原因,这似乎不太可靠。 - 硬编码设备ID不是个好主意,因为我正在使用同一台设备测试已签名的APK。 - 在代码中使用手动标记?这是可行的,但我肯定会在某个时候忘记更改,而且所有程序员都很懒。

回滚了Phil的编辑。这不是关于程序是否在市场上合法分发的问题,而是关于程序是否仍处于“调试模式”的问题。 - Im0rtality
这是最简单的方法:https://dev59.com/A2Ag5IYBdhLWcg3wOoxQ#23844716 - MBH
10个回答

145

您可以使用以下代码来检查debuggable标志:

boolean isDebuggable =  ( 0 != ( getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ) );

Kotlin:

=>

Kotlin:

val isDebuggable = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE

了解更多信息,请参阅Securing Android LVL Applications

或者,如果您正确使用Gradle,则可以检查BuildConfig.DEBUG是否为真或假。


似乎这仍然检查清单的android:debuggable。 - xster
2
第一个测试Manifest debuggable,但这已经被弃用了。第二个测试对于库来说是不可能的,因为库将有自己的BuildConfig - 无法导入使用该库的应用程序的BuildConfig。因此,标记的答案是“可以的”。 - Christoph
无论是库项目还是应用程序项目,这个答案都适用。 - Lavekush Agrawal

134

由Mark Murphy回答

最简单且长期最佳的解决方案是使用BuildConfig.DEBUG。这是一个布尔值,对于调试版本将为true,否则将为false

if (BuildConfig.DEBUG) {
  // do something for a debug build
}

14
这种方法唯一的缺点是无法在库项目(aar 文件)中使用。在构建库时,会导致结果为 false,因此即使使用该库的应用程序处于调试模式,该检查也会在库代码内部导致结果为 false。 - Vito Andolini

81

有多种方法可以检查应用程序是使用调试还是发布证书构建的,但以下方式对我来说似乎是最好的。

根据Android文档中的信息签署您的应用程序,调试密钥包含以下主题可区分名称:" CN=Android Debug,O=Android,C=US". 我们可以使用此信息来测试该包是否使用调试密钥进行签名,而无需将调试密钥签名硬编码到我们的代码中。

给定:

import android.content.pm.Signature;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

您可以这样实现一个 isDebuggable 方法:

private static final X500Principal DEBUG_DN = new X500Principal("CN=Android Debug,O=Android,C=US");
private boolean isDebuggable(Context ctx)
{
    boolean debuggable = false;

    try
    {
        PackageInfo pinfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(),PackageManager.GET_SIGNATURES);
        Signature signatures[] = pinfo.signatures;

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        for ( int i = 0; i < signatures.length;i++)
        {   
            ByteArrayInputStream stream = new ByteArrayInputStream(signatures[i].toByteArray());
            X509Certificate cert = (X509Certificate) cf.generateCertificate(stream);       
            debuggable = cert.getSubjectX500Principal().equals(DEBUG_DN);
            if (debuggable)
                break;
        }
    }
    catch (NameNotFoundException e)
    {
        //debuggable variable will remain false
    }
    catch (CertificateException e)
    {
        //debuggable variable will remain false
    }
    return debuggable;
}

它的效率足够高,可以在应用程序类的onCreate方法中运行吗? - android developer
我没有记录执行时间,但我已经在我的应用程序中使用它,并且在效率方面没有任何问题。 - Omar Rehman
结果可以被缓存以提高效率。 - ftvs
对我来说,这个时间大约是2-3毫秒。 - Agamemnus
嗯,如果证书是发布证书而不是使用Android Studio或类似工具生成的证书,则此方法无法正常工作。 - Agamemnus
显示剩余3条评论

36

如果您想静态地检查一个 APK,您可以使用

aapt dump badging /path/to/apk | grep -c application-debuggable

如果 APK 未调试,则输出 0,如果已经调试,则输出 1


4
这是唯一的解决方案,需要验证最终apk文件。其他回复假设你拥有源代码。 - Guillermo Tobar
4
"aapt" 位于此处:/Users/USER_NAME/library/Android/sdk/build-tools/28.0.3/aapt - Casey

22

也许有点晚了,但是iosched使用了BuildConfig.DEBUG


现在可以安全使用吗?有一篇文章说它存在一些问题:http://www.digipom.com/be-careful-with-buildconfig-debug/ - android developer
这是最好的答案! - Peter Fortuin
如果你在编译时不知道BuildConfig的包,那么你就不能编写第三方库。 - Sam Dozor
Sam,你能详细说明一下吗? - Agamemnus

10
首先在您的build.gradle文件中添加以下内容,这将允许同时运行调试和发布版本:
buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

请添加这个方法:
public static boolean isDebug(Context context) {
    String pName = context.getPackageName();
    if (pName != null && pName.endsWith(".debug")) {
        return true;
    } else {
        return false;
    }
}

1
我更喜欢这个答案,因为它是可靠的。不过,我确实需要向我的Google Maps API密钥添加一个新的“允许的Android应用程序”条目(因为应用程序ID不同)。 - Baz
我也使用过这个方法,但我建议删除if语句,直接返回pName != null && pName.endsWith(".debug")。 - Manuel Rego

5

调试版本也是有签名的,只不过使用了不同的密钥。它是由 Eclipse 自动生成的,并且其证书仅有效期为一年。那么,android:debuggable 有什么问题呢?你可以使用 PackageManager 从代码中获取该值。


4

还有一个值得一提的选项。如果你需要在调试器附加时执行一些代码,可以使用以下代码:

if (Debug.isDebuggerConnected() || Debug.waitingForDebugger()) { 
    //code to be executed 
}

0

通过 android:debuggable 解决了问题。在某些情况下,读取项目时存在错误,其中项目的调试标志未存储在记录中,导致 if (m.debug && !App.isDebuggable(getContext())) 始终评估为 false。是我的错。


13
我知道这条消息已经超过一年了,但是你应该接受@Omar Rehman的答案,而不是这个。虽然你最终发布的内容与此相同,但它并没有真正回答你提出的问题,而Omar的解决方案似乎能够做到这一点,因此他应该获得被采纳的荣誉。 - mah
4
如果你认为我的回复是欺凌,那么我认为你没有经常上网。我支持你以评论的方式发表对立观点的权利,因此我已经检查了自己的评论以及这个问题中的其他帖子,我仍然坚持我的原始评论:发帖人提出了一个问题,另一个人给出了合理且正确的答案。根据他自己的“答案”,他最初提出的问题并不是他想问的... APK 的签名(发布版或调试版)对清单文件完全没有影响。 - mah
3
@ChrisStratton,提问者也需要询问实际想要获得答案的问题,但在这种情况下没有做到。您正确指出我忽略了实际回答发布的时间,然而这里没有任何惩罚,只是我表达个人观点的合理方式。如果您认为我在这里表现得像恃强凌弱者,请强烈要求您标记我发出的评论以示滥用。但在那之前,您可能希望审查一下自己在此处的发言。 - mah

0

我目前正在使用的 Kotlin 解决方案:

@SuppressLint("PackageManagerGetSignatures")
@Suppress("DEPRECATION")
fun isSigned(context: Context?): Boolean {
    return (context?.packageManager?.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)?.signatures?.firstOrNull()?.toByteArray()
            ?.let {
                return@let CertificateFactory.getInstance("X.509").generateCertificate(ByteArrayInputStream(it))
            } as? X509Certificate)
            ?.issuerDN
            ?.name
            ?.contains("O=Android", ignoreCase = false) ?: true
}

这样我仍然可以在调试中登录,这些将被报告给Crashlytics(例如,用于QA过程)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接