在不安装 apk 的情况下获取 Android .apk 文件 VersionName 或 VersionCode

Posted

技术标签:

【中文标题】在不安装 apk 的情况下获取 Android .apk 文件 VersionName 或 VersionCode【英文标题】:Get Android .apk file VersionName or VersionCode WITHOUT installing apk 【发布时间】:2012-11-08 06:44:18 【问题描述】:

在下载 androidManifest.xml 文件后,如何在不安装的情况下以编程方式获取我的 apk 的版本代码或版本名称。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xxx.xx.xxx"
    android:versionCode="1"
    android:versionName="1.1" >

例如我想检查是否有新版本上传到我的 IIS 服务上,安装到设备后,如果不是新版本我不想安装。

【问题讨论】:

查看***.com/a/4761689/1109425 顺便说一句,我希望没有人认为一个好的解决方案是下载文件,然后 然后 检查是否需要它 - 这就是第一句话所暗示的。大概您希望代码在在您的服务器上运行,并以可用的版本号进行响应。 ./aapt d badging release.apk | grep -Po "(?&lt;=\sversion(Code|Name)=')([0-9.]+)" aapt 将花费大约2.6M 磁盘空间,您还可以编写一个解析器来解析带有AXMLResource 的apk 文件,这将只花费大约52k。参考Java parse xml with undeclared namespace 【参考方案1】:
aapt dump badging test.apk | grep "versionName" | sed -e "s/.*versionName='//" -e "s/' .*//"

这通过仅返回版本号作为结果来回答问题。 不过……

如前所述,目标应该是在尝试下载或安装之前确定服务器上的 apk 是否比已安装的更新。最简单的方法是在服务器上托管的 apk 的文件名中包含版本号,例如 myapp_1.01.apk

您需要确定已安装应用程序的名称和版本号(如果已安装),以便进行比较。如果 rom 中尚未包含 aapt 二进制文件和 busybox,您将需要一个 root 设备或安装方法。

此脚本将从您的服务器获取应用程序列表并与任何已安装的应用程序进行比较。结果是一个标记为升级/安装的列表。

#/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 信息。这种方式在 android 中速度较慢且不简单,但只要您有工作的 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

还要考虑在您的服务器上放置一个放置文件夹。将 apk 上传到它,然后一个 cron 任务重命名并将它们移动到您的更新文件夹。

#!/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

【讨论】:

【参考方案2】:

使用现在是cmdline-tools 一部分的apkanalyzer

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

【讨论】:

也许题外话,但我得到? 作为输出。不知道为什么,但是旧的appt 让我物有所值【参考方案3】:

科特林:

var ver: String = packageManager.getPackageInfo(packageName, 0).versionName

【讨论】:

【参考方案4】:

目前,可以按如下方式完成

$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

【讨论】:

【参考方案5】:

以下从命令行为我工作:

aapt dump badging myapp.apk

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

<sdk_path>/build-tools/23.0.2/aapt.exe

【讨论】:

工作正常,这应该被标记为正确答案,作为参考,aapt 在 sdk 的 build-tools/XX 中。 我在“/build-tools/21.1.2”下找到了 在 Mac 上使用 Xamarin,它位于 ~/Library/Developer/Xamarin/android-sdk-macosx/build-tools/version 什么是platformBuildVersionName,当我使用这个命令时打印出来,为什么它是空的? 你可以创建bat文件aapt dump badging %1 pause来拖放任何apk【参考方案6】:

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

【讨论】:

OP 询问如何获取它programmatically 因为他不知道Android Studio中内置了这样的工具。他还问没有安装apk 他当然不知道,因为当被问到这个问题时,Android Studio 并不存在。此外,即使确实如此,这也不能解决他的问题——正如他在问题中所说,他希望他的设备以编程方式检查来自他的 IIS 服务器的更新。 Patrick Cho 的回答解释了如何在不安装的情况下以编程方式进行。【参考方案7】:

对于升级场景,另一种方法可能是使用提供当前版本号的 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 内部以查看其版本,将其存储在文件或数据库中,以便通过您讨论的方式检索。 是的,在这种情况下,它根本不需要匹配 APK,只是最新版本号可以公开访问。【参考方案8】:
    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();
    

【讨论】:

这不是问题的答案。这是在运行的应用程序中获取一些版本信息并显示它的代码。问题是如何从 APK 文件中获取此信息,无需安装应用程序 这仅适用于已安装的应用程序,不适用于 .apk 文件。【参考方案9】:
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();

【讨论】:

我如何在不安装应用程序的情况下阅读apk 内容? @PatrickCho 这确实有效。而且您不需要安装它。您只需在设备存储的某处有一个 apk,并将其路径提供给包管理器。它确实会返回包裹信息。 这是 imo 的最佳答案【参考方案10】:

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

这个话题是我得到答案的关键(我还添加了我的 Ribo 代码版本): How to parse the AndroidManifest.xml file inside an .apk package

另外,这是我写的 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://***.com/questions/2097813/how-to-parse-the-androidmanifest-xml-file-inside-an-apk-package/4761689#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;

版本比较(在之前的 sn-p 中显示为 SystemPackageTools.compareVersions) 注意:此代码的灵感来自以下主题:Efficient way to compare version strings in Java

/**
 * Compare 2 version strings and tell if the first is higher, equal or lower
 * Source: https://***.com/questions/6701948/efficient-way-to-compare-version-strings-in-java
 * 
 * @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。

以上是关于在不安装 apk 的情况下获取 Android .apk 文件 VersionName 或 VersionCode的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用 Eclipse 的情况下在 Android 上安装/替换

如何在不启动模拟器的情况下在 Eclipse 中将 Android 项目编译为 .apk 文件?

如何在不发布和Eclipse的情况下在真机上安装Android应用程序?

如何在不重建 apk 的情况下更新现有已安装应用程序的 Ionic 资产文件夹?

如何在不使用 github 中的秘密的情况下安全地签署 Android apk?

如何在不更新 APK 文件的情况下解决违反使用 Android 广告 ID 政策和第 4.8 节的问题 [重复]