Получить Android .apk файл VersionName или VersionCode БЕЗ установки apk

184

Как получить программным способом код версии или название версии моего 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, после установки на устройство, если это не новая версия, я не хочу ее устанавливать.

Big.Child
источник
проверить это stackoverflow.com/a/4761689/1109425
nandeesh
Кстати, я надеюсь, что никто не думает, что хорошее решение - это загрузить файл, а затем проверить, нужен ли он - что и предлагает первое предложение. Предположительно вместо этого вам нужен код, который запускается на вашем сервере и отвечает номером доступной версии.
ToolmakerSteve
./aapt d badging release.apk | grep -Po "(?<=\sversion(Code|Name)=')([0-9.]+)"
Крис Руф
aapt будет стоить около 2.6Mдискового пространства, вы также можете написать парсер для разбора файла apk, с AXMLResourceкоторым будет стоить около 52k. Обратитесь к Java-анализу xml с необъявленным пространством имен
Kris Roofe

Ответы:

417

Следующее сработало у меня из командной строки:

aapt dump badging myapp.apk

ПРИМЕЧАНИЕ: aapt.exe находится в подпапке build-toolsSDK. Например:

<sdk_path>/build-tools/23.0.2/aapt.exe
Sileria
источник
26
Работает нормально, это следует пометить как правильный ответ, для информации, aaptв build-tools / XX в sdk.
Квентин Кляйн
8
Я нашел его в "<sdk_path> /build-tools/21.1.2"
gmuhammad
1
Использование Xamarin на Mac, это было в~/Library/Developer/Xamarin/android-sdk-macosx/build-tools/{version}
cscott530
Что это platformBuildVersionNameпечатается, когда я использую эту команду, и почему она пуста?
Севастян Саванюк
Вы можете создать Bat файл, aapt dump badging %1 pauseчтобы перетащить любой
апк
85
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();
Патрик Чо
источник
5
как я могу читать apkконтент без установки приложения? @PatrickCho
talha06
2
Это действительно работает. И вам не нужно устанавливать его. У вас просто есть apk где-то в хранилище вашего устройства, и вы указываете путь к нему менеджеру пакетов. Это действительно вернет информацию о пакете.
Даниэль Золнай
45

Если вы используете версию 2.2 и выше в Android Studio , то используется Android Studio Build Analyze APKвыберите AndroidManifest.xml файл.

vovahost
источник
7
ОП спросил, как его достатьprogrammatically
форрестхопкинса
3
Потому что он не знал, что такой инструмент встроен в Android Studio. Он также спросил без установки апк .
vovahost
3
Конечно, он не знал этого, потому что Android Studio не существовало, когда задавался этот вопрос. Кроме того, даже если это произойдет, это не решит его проблему - он хочет, чтобы его устройство программно проверило наличие обновлений со своего сервера IIS, как он указал в вопросе. Ответ Патрика Чо объясняет, как сделать это программно без установки.
Форрестхопкинса
10
aapt dump badging test.apk | grep "VersionName" | sed -e "s/.*versionName='//" -e "s/' .*//"

Это отвечает на вопрос, возвращая в результате только номер версии. Тем не мение......

Цель, как было указано ранее, должна состоять в том, чтобы выяснить, является ли apk на сервере более новым, чем установленный, ПЕРЕД попыткой загрузить или установить его. Самый простой способ сделать это - включить номер версии в имя файла apk, размещенного на сервере, напримерmyapp_1.01.apk

Вам нужно будет установить имя и номер версии уже установленных приложений (если они установлены), чтобы сделать сравнение. Вам понадобится рутованное устройство или средство установки двоичного файла aapt и busybox, если они еще не включены в rom.

Этот скрипт получит список приложений с вашего сервера и сравнит их с любыми установленными приложениями. Результатом является список, помеченный для обновления / установки.

#/system/bin/sh
SERVER_LIST=$(wget -qO- "http://demo.server.com/apk/" | grep 'href' | grep '\.apk' | sed 's/.*href="https://' | \
              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. Также можно извлечь apk-информацию с помощью apktool и небольшого количества сценариев. Этот способ медленнее и не прост в Android, но будет работать на Windows / Mac или Linux, если у вас есть рабочая установка apktool.

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

Также рассмотрите папку для удаления на вашем сервере. Загрузите в него apks, а задача 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
5p0ng3b0b
источник
8

На данный момент это можно сделать следующим образом

$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
SergeyUr
источник
5

Теперь я могу успешно извлечь версию файла APK из его двоичных данных XML.

В этой теме я получил ключ к своему ответу (я также добавил свою версию кода Рибо): как проанализировать файл AndroidManifest.xml в пакете .apk

Кроме того, вот код 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: /programming/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;
}

Сравнение версий (рассматривается как SystemPackageTools.compareVersions в предыдущем фрагменте) ПРИМЕЧАНИЕ. Этот код основан на следующей теме: Эффективный способ сравнения строк версий в Java

/**
 * Compare 2 version strings and tell if the first is higher, equal or lower
 * Source: /programming/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 для меня.
посланник GR
4

В частности, для сценария обновления альтернативный подход может заключаться в том, чтобы иметь веб-службу, которая предоставляет текущий номер версии, и проверить, что вместо загрузки всего apk просто для проверки его версии. Это позволило бы сэкономить некоторую полосу пропускания, быть немного более производительным (намного быстрее загружаться, чем apk, если большую часть времени не нужен весь apk) и намного проще в реализации.

В простейшей форме у вас может быть простой текстовый файл на вашем сервере ... http://some-place.com/current-app-version.txt

Внутри этого текстового файла есть что-то вроде

3.1.4

а затем загрузите этот файл и сравните с текущей установленной версией.

Создание более продвинутого решения для этого состояло бы в том, чтобы реализовать надлежащий веб-сервис и иметь вызов API при запуске, который мог бы вернуть некоторый json, то есть http://api.some-place.com/versionCheck:

{
    "current_version": "3.1.4"
}
Grego
источник
Вы на правильном пути - не стоит скачивать файл, просто чтобы получить его версию. Я просто хочу отметить, что я не рекомендовал бы создавать текстовый файл ВРУЧНУЮ - слишком легко, чтобы он не соответствовал реальному файлу apk. Вместо этого, напишите скрипт (который использует один из других ответов), чтобы заглянуть внутрь apk, чтобы увидеть, какая у него версия, сохраняя ее в файле или базе данных, чтобы найти ее с помощью обсуждаемых вами средств.
ToolmakerSteve
Да, в этом сценарии дело не в том, что он вообще должен соответствовать APK, просто в том, что номер последней версии где-то общедоступен.
Грего
1
    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();
    }
Уэйн Лен
источник
10
Это не ответ на вопрос. Это код, который внутри запущенного приложения получает некоторую информацию о версии и отображает ее. Вопрос в том, как получить эту информацию из APK-файла, не устанавливая приложение .
ToolmakerSteve
Это только для уже установленного приложения, а не для файла .apk.
Rhusfer