获取Android .apk文件的VersionName或VersionCode,而无需安装apk。

237

我如何在下载apk文件后,在不安装它的情况下,通过AndroidManifest.xml文件编程获取其版本代码或版本名称。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xxx.xx.xxx"
    android:versionCode="1"
    android:versionName="1.1" >
例如,我想检查是否有新版本上传到我的IIS服务,并在设备上安装后检查它是否是新版本。如果不是新版本,则不想安装它。

请查看此链接:https://dev59.com/u3I95IYBdhLWcg3w5iY4#4761689 - nandeesh
顺便说一句,我希望没有人认为一个好的解决方案是下载文件,然后再检查是否需要 - 这正是第一句话所暗示的。想必你会希望在你的服务器上运行代码,并响应可用的版本号。 - ToolmakerSteve
1
./aapt d badging release.apk | grep -Po "(?<=\sversion(Code|Name)=')([0-9.]+)" - LF00
aapt将占用约2.6M的磁盘空间,您还可以编写一个解析器来解析带有AXMLResource的apk文件,这只需要大约52k的空间。请参考Java parse xml with undeclared namespace - LF00
12个回答

508
以下是我在命令行中使用的方法:
aapt dump badging myapp.apk

注意:aapt.exe位于SDK的build-tools子文件夹中。例如:
<sdk_path>/build-tools/23.0.2/aapt.exe

29
运行良好,应将其标记为正确答案。顺便提一下,aapt在sdk的build-tools/XX目录中。 - Quentin Klein
8
我在"<sdk_path>/build-tools/21.1.2"下找到了它。 - gmuhammad
1
在 Mac 上使用 Xamarin,它位于 ~/Library/Developer/Xamarin/android-sdk-macosx/build-tools/{version} - cscott530
当我使用这个命令时,打印出来的platformBuildVersionName是什么,为什么它是空的? - Sevastyan Savanyuk
您可以创建一个批处理文件aapt dump badging %1 pause来拖放任何 APK。 - user924
显示剩余4条评论

103
final PackageManager pm = getPackageManager();
String apkName = "example.apk";
String fullPath = Environment.getExternalStorageDirectory() + "/" + apkName;        
PackageInfo info = pm.getPackageArchiveInfo(fullPath, 0);
Toast.makeText(this, "VersionCode : " + info.versionCode + ", VersionName : " + info.versionName, Toast.LENGTH_LONG).show();

6
如何在不安装应用程序的情况下阅读 apk 内容?@PatrickCho(翻译提示:保持简洁并尽可能准确地传达原文的意思。) - talha06
4
确实有效,而且无需安装。你只需要在设备存储中找到一个apk文件,并将其路径提供给包管理器。它确实会返回该应用程序的信息。 - Daniel Zolnai
2
这是我个人认为最好的答案。 - buradd

58

如果您使用的是 Android Studio2.2 及以上版本,则在 Android Studio 中使用 Build Analyze APK,然后选择 AndroidManifest.xml 文件。


14
OP 问如何通过程序来实现。 - forresthopkinsa
5
因为他不知道Android Studio内置了这样的工具。他还问道“不安装apk”的情况下可行吗? - vovahost
8
当这个问题被提出时,Android Studio还不存在,因此他显然不知道这一点。即使存在,也解决不了他的问题——他想让他的设备通过编程方式从他的IIS服务器检查更新,正如他在问题中所陈述的那样。Patrick Cho的答案解释了如何在不安装任何东西的情况下以编程方式完成这一操作。 - forresthopkinsa
谢谢!这是获取APK版本代码最简单的方法。这正是我所寻找的。 - antaki93

25
aapt dump badging test.apk | grep "versionName" | sed -e "s/.*versionName='//" -e "s/' .*//"

这个问题的答案是仅返回版本号作为结果。然而......
如先前所述,目标应该是在尝试下载或安装之前确定服务器上的apk是否比已安装的版本更新。最简单的方法是将版本号包含在托管在服务器上的apk文件名中,例如myapp_1.01.apk 为了进行比较,您需要确定已安装的应用程序的名称和版本号(如果已安装)。您需要一个已root的设备或一种安装aapt二进制文件和busybox的方法,如果它们尚未包含在rom中。
此脚本将从您的服务器获取应用程序列表,并与任何已安装的应用程序进行比较。结果是一个标记为升级/安装的列表。
#/system/bin/sh
SERVER_LIST=$(wget -qO- "http://demo.server.com/apk/" | grep 'href' | grep '\.apk' | sed 's/.*href="//' | \
              sed 's/".*//' | grep -v '\/' | sed -E "s/%/\\\\x/g" | sed -e "s/x20/ /g" -e "s/\\\\//g")
LOCAL_LIST=$(for APP in $(pm list packages -f | sed -e 's/package://' -e 's/=.*//' | sort -u); do \
              INFO=$(echo -n $(aapt dump badging $APP | grep -e 'package: name=' -e 'application: label=')) 2>/dev/null; \
              PACKAGE=$(echo $INFO | sed "s/.*package: name='//" | sed "s/'.*$//"); \
              LABEL=$(echo $INFO | sed "s/.*application: label='//" | sed "s/'.*$//"); if [ -z "$LABEL" ]; then LABEL="$PACKAGE"; fi; \
              VERSION=$(echo $INFO | sed -e "s/.*versionName='//" -e "s/' .*//"); \
              NAME=$LABEL"_"$VERSION".apk"; echo "$NAME"; \
              done;)
OFS=$IFS; IFS=$'\t\n'
for REMOTE in $SERVER_LIST; do
    INSTALLED=0
    REMOTE_NAME=$(echo $REMOTE | sed 's/_.*//'); REMOTE_VER=$(echo $REMOTE | sed 's/^[^_]*_//g' | sed 's/[^0-9]*//g')
    for LOCAL in $LOCAL_LIST; do
        LOCAL_NAME=$(echo $LOCAL | sed 's/_.*//'); LOCAL_VER=$(echo $LOCAL | sed 's/^[^_]*_//g' | sed 's/[^0-9]*//g')
        if [ "$REMOTE_NAME" == "$LOCAL_NAME" ]; then INSTALLED=1; fi
        if [ "$REMOTE_NAME" == "$LOCAL_NAME" ] && [ ! "$REMOTE_VER" == "$LOCAL_VER" ]; then echo remote=$REMOTE ver=$REMOTE_VER local=$LOCAL ver=$LOCAL_VER; fi
    done
    if [ "$INSTALLED" == "0" ]; then echo "$REMOTE"; fi
done
IFS=$OFS

有人问如何在不使用aapt的情况下完成这个任务。 也可以使用apktool和一些脚本来提取apk信息。这种方法在安卓上较慢且不简单,但只要你有一个可用的apktool设置,它就可以在Windows/Mac或Linux上运行。
#!/bin/sh
APK=/path/to/your.apk
TMPDIR=/tmp/apktool
rm -f -R $TMPDIR
apktool d -q -f -s --force-manifest -o $TMPDIR $APK
APK=$(basename $APK)
VERSION=$(cat $TMPDIR/apktool.yml | grep "versionName" | sed -e "s/versionName: //")
LABEL=$(cat $TMPDIR/res/values/strings.xml | grep 'string name="title"' | sed -e 's/.*">//' -e 's/<.*//')
rm -f -R $TMPDIR
echo ${LABEL}_$(echo $V).apk

还可以考虑在您的服务器上设置一个“drop”文件夹。将apk文件上传到该文件夹,然后通过定时任务将其重命名并移动到您的更新文件夹中。
#!/bin/sh
# Drop Folder script for renaming APKs
# Read apk file from SRC folder and move it to TGT folder while changing filename to APKLABEL_APKVERSION.apk
# If an existing version of the APK exists in the target folder then script will remove it
# Define METHOD as "aapt" or "apktool" depending upon what is available on server 

# Variables
METHOD="aapt"
SRC="/home/user/public_html/dropfolders/apk"
TGT="/home/user/public_html/apk"
if [ -d "$SRC" ];then mkdir -p $SRC
if [ -d "$TGT" ]then mkdir -p $TGT

# Functions
get_apk_filename () {
    if [ "$1" = "" ]; then return 1; fi
    local A="$1"
    case $METHOD in
        "apktool")
            local D=/tmp/apktool
            rm -f -R $D
            apktool d -q -f -s --force-manifest -o $D $A
            local A=$(basename $A)
            local V=$(cat $D/apktool.yml | grep "versionName" | sed -e "s/versionName: //")
            local T=$(cat $D/res/values/strings.xml | grep 'string name="title"' | sed -e 's/.*">//' -e 's/<.*//')
            rm -f -R $D<commands>
            ;;
        "aapt")
            local A=$(aapt dump badging $A | grep -e "application-label:" -e "VersionName")
            local V=$(echo $A | sed -e "s/.*versionName='//" -e "s/' .*//")
            local T=$(echo $A | sed -e "s/.*application-label:'//" -e "s/'.*//")
            ;;
        esac
    echo ${T}_$(echo $V).apk
}

# Begin script
for APK in $(ls "$SRC"/*.apk); do
    APKNAME=$(get_apk_filename "$APK")
    rm -f $TGT/$(echo APKNAME | sed "s/_.*//")_*.apk
    mv "$APK" "$TGT"/$APKNAME
done

10

目前,可以按以下方式完成此操作

$ANDROID_HOME/build-tools/28.0.3/aapt dump badging /<path to>/<app name>.apk

一般情况下,它将是:

$ANDROID_HOME/build-tools/<version_of_build_tools>/aapt dump badging /<path to>/<app name>.apk

6

我现在可以成功从APK文件的二进制XML数据中检索版本。

这个主题是我得到答案关键的地方(我也加入了Ribo代码的我的版本): 如何在.apk包中解析AndroidManifest.xml文件

另外,这是我编写的XML解析代码,专门用于获取版本:

XML解析

/**
 * Verifies at Conductor APK path if package version if newer 
 * 
 * @return True if package found is newer, false otherwise
 */
public static boolean checkIsNewVersion(String conductorApkPath) {

    boolean newVersionExists = false;

    // Decompress found APK's Manifest XML
    // Source: https://dev59.com/u3I95IYBdhLWcg3w5iY4#4761689
    try {

        if ((new File(conductorApkPath).exists())) {

            JarFile jf = new JarFile(conductorApkPath);
            InputStream is = jf.getInputStream(jf.getEntry("AndroidManifest.xml"));
            byte[] xml = new byte[is.available()];
            int br = is.read(xml);

            //Tree tr = TrunkFactory.newTree();
            String xmlResult = SystemPackageTools.decompressXML(xml);
            //prt("XML\n"+tr.list());

            if (!xmlResult.isEmpty()) {

                InputStream in = new ByteArrayInputStream(xmlResult.getBytes());

                // Source: http://developer.android.com/training/basics/network-ops/xml.html
                XmlPullParser parser = Xml.newPullParser();
                parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);

                parser.setInput(in, null);
                parser.nextTag();

                String name = parser.getName();
                if (name.equalsIgnoreCase("Manifest")) {

                    String pakVersion = parser.getAttributeValue(null, "versionName");
                            //NOTE: This is specific to my project. Replace with whatever is relevant on your side to fetch your project's version
                    String curVersion = SharedData.getPlayerVersion();

                    int isNewer = SystemPackageTools.compareVersions(pakVersion, curVersion); 

                    newVersionExists = (isNewer == 1); 
                }

            }
        }

    } catch (Exception ex) {
        android.util.Log.e(TAG, "getIntents, ex: "+ex);
        ex.printStackTrace();
    }

    return newVersionExists;
}

版本比较(在上一个代码片段中被视为SystemPackageTools.compareVersions) 注意:此代码受以下主题的启发:Java中比较版本字符串的有效方法

/**
 * Compare 2 version strings and tell if the first is higher, equal or lower
 * Source: https://dev59.com/Imw15IYBdhLWcg3wT55c
 * 
 * @param ver1 Reference version
 * @param ver2 Comparison version
 * 
 * @return 1 if ver1 is higher, 0 if equal, -1 if ver1 is lower
 */
public static final int compareVersions(String ver1, String ver2) {

    String[] vals1 = ver1.split("\\.");
    String[] vals2 = ver2.split("\\.");
    int i=0;
    while(i<vals1.length&&i<vals2.length&&vals1[i].equals(vals2[i])) {
      i++;
    }

    if (i<vals1.length&&i<vals2.length) {
        int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i]));
        return diff<0?-1:diff==0?0:1;
    }

    return vals1.length<vals2.length?-1:vals1.length==vals2.length?0:1;
}

我希望这有所帮助。


你升级到新的Gradle或Android Studio了吗?这还能用吗?Robio代码不能再为我解压XML。 - Brian S

6
  1. 将 .apk 文件拖入 Android Studio 中

  2. 然后双击 AndroidManifest.xml 文件。

  3. 查看版本代码和版本名称

enter image description here


4

针对升级方案,一个替代方法可能是使用Web服务来提供当前版本号,并检查该版本号,而不是下载整个apk文件来检查其版本。这将节省一些带宽,更加高效(如果大多数情况下不需要整个apk文件,则比下载apk文件快得多),并且实现起来更加简单。

在最简单的形式下,您可以在服务器上拥有一个简单的文本文件... http://some-place.com/current-app-version.txt

在该文本文件中,可以写入以下内容:

3.1.4

然后下载该文件并检查与当前安装版本对比。

构建更高级的解决方案是实现一个合适的 Web 服务,在启动时进行 API 调用,可返回一些 JSON 格式数据,例如:http://api.some-place.com/versionCheck

{
    "current_version": "3.1.4"
}

你正在正确的轨道上 - 不应该下载文件,只是为了获取它的版本。我只想提醒一下,我不建议手动创建文本文件 - 太容易导致它与实际的apk文件不匹配。相反,编写一个脚本(使用其他答案之一)来查看apk内部的版本,并将其存储在文件或数据库中,以便通过您讨论的方式检索。 - ToolmakerSteve
是的,在这种情况下,问题并不在于需要完全匹配APK,而是最新版本号可以在某个公开可访问的地方找到。 - grego

3

使用现在已成为cmdline-tools一部分的apkanalyzer

$ apkanalyzer manifest version-code my_app.apk
1
$ apkanalyzer manifest version-name my_app.apk
1.2.3.4

1
也许有点跑题,但我得到的输出是 ?。不确定为什么,但使用旧的 appt 可以得到正确的值。 - crgarridos

0
    EditText ET1 = (EditText) findViewById(R.id.editText1);

    PackageInfo pinfo;
    try {
        pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
        String versionName = pinfo.versionName;
        ET1.setText(versionName);
        //ET2.setText(versionNumber);
    } catch (NameNotFoundException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

11
这不是针对问题的回答,而是一个在运行应用程序中获取某些版本信息并显示它的代码。问题是如何从APK文件中获取此信息,而无需安装该应用程序 - ToolmakerSteve
这仅适用于已安装的应用程序,而不适用于 .apk 文件。 - Rhusfer

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