Pobierz plik .apk Androida VersionName lub VersionCode BEZ instalowania apk

184

Jak mogę uzyskać programowo kod wersji lub nazwę wersji mojego pliku APK z pliku AndroidManifest.xml po pobraniu i bez instalacji.

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

Na przykład chcę sprawdzić, czy nowa wersja jest załadowana do mojej usługi IIS, po zainstalowaniu jej na urządzeniu, jeśli nie jest to nowa wersja, nie chcę jej instalować.

Big.Child
źródło
sprawdź ten stackoverflow.com/a/4761689/1109425
nandeesh
BTW, mam nadzieję, że nikt nie uważa, że ​​dobrym rozwiązaniem jest pobranie pliku, a następnie sprawdzenie, czy jest on potrzebny - co sugeruje pierwsze zdanie. Przypuszczalnie wolisz kod działający na serwerze i odpowiada dostępnym numerem wersji.
ToolmakerSteve,
./aapt d badging release.apk | grep -Po "(?<=\sversion(Code|Name)=')([0-9.]+)"
Kris Roofe,
aapt będzie kosztować o 2.6Mmiejsce na dysku, możesz także napisać parser, aby przeanalizować plik apk, za AXMLResourcektóry to będzie kosztować 52k. Zapoznaj się z
plikiem

Odpowiedzi:

417

Następujące działało dla mnie z wiersza poleceń:

aapt dump badging myapp.apk

UWAGA: aapt.exe znajduje się w build-toolspodfolderze SDK. Na przykład:

<sdk_path>/build-tools/23.0.2/aapt.exe
Sileria
źródło
26
Działa dobrze, należy to zaznaczyć jako poprawną odpowiedź, dla informacji aaptznajduje się w build-tools / XX w sdk.
Quentin Klein
8
Znalazłem go pod „<ścieżka_dostępu> / build-tools/21.1.2”
gmuhammad
1
Używając Xamarin na Macu, był w~/Library/Developer/Xamarin/android-sdk-macosx/build-tools/{version}
cscott530
Co jest platformBuildVersionNamedrukowane, gdy używam tego polecenia i dlaczego jest puste?
Sevastyan Savanyuk
możesz utworzyć plik nietoperza, aapt dump badging %1 pauseaby przeciągnąć i upuścić dowolną aplikację
user924
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();
Patrick Cho
źródło
5
jak mogę czytać apkzawartość bez instalowania aplikacji? @PatrickCho
talha06
2
To rzeczywiście działa. I nie musisz go instalować. Po prostu masz apk gdzieś w pamięci urządzenia i podajesz ścieżkę do menedżera pakietów. Rzeczywiście zwróci informacje o pakiecie.
Daniel Zolnai
45

Jeśli używasz wersji 2.2 i powyżej z Android Studio następnie w Android Studio użytku Build Analyze APKnastępnie wybierz plik AndroidManifest.xml.

vovahost
źródło
7
OP zapytał, jak go zdobyćprogrammatically
forresthopkinsa
3
Ponieważ nie wiedział, że takie narzędzie jest wbudowane w Android Studio. Zapytał także bez instalowania apk .
vovahost
3
Oczywiście nie wiedział o tym, ponieważ Android Studio nie istniało, gdy zadano to pytanie. Dodatkowo, nawet jeśli tak, to nie rozwiązuje jego problemu - chce, aby jego urządzenie programowo sprawdzało dostępność aktualizacji z serwera IIS, jak stwierdził w pytaniu. Odpowiedź Patricka Cho wyjaśnia, jak to zrobić programowo bez instalowania.
forresthopkinsa
10
aapt dump badging test.apk | grep "VersionName" | sed -e "s/.*versionName='//" -e "s/' .*//"

To odpowiada na pytanie, zwracając w rezultacie tylko numer wersji. Jednak......

Jak wspomniano wcześniej, celem powinno być sprawdzenie, czy aplikacja na serwerze jest nowsza niż zainstalowana PRZED próbą jej pobrania lub zainstalowania. Najłatwiej to zrobić, umieszczając numer wersji w nazwie pliku apka hostowanego na serwerze, npmyapp_1.01.apk

Aby dokonać porównania, musisz ustalić nazwę i numer wersji aplikacji już zainstalowanych (jeśli jest zainstalowana). Będziesz potrzebował zrootowanego urządzenia lub sposobu instalacji aapt binarnego i busybox, jeśli nie są one jeszcze zawarte w ROM.

Ten skrypt pobierze listę aplikacji z twojego serwera i porówna z dowolnymi zainstalowanymi aplikacjami. Wynikiem jest lista oznaczona do aktualizacji / instalacji.

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

Jak ktoś zapytał, jak to zrobić bez użycia aapt. Możliwe jest również wyodrębnienie informacji apk za pomocą apktool i odrobiny skryptów. Ten sposób jest wolniejszy i nie jest prosty w Androidzie, ale będzie działał na systemach Windows / Mac lub Linux, o ile masz działającą konfigurację 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

Rozważ również folder upuszczania na swoim serwerze. Prześlij do niego apki, a zadanie cron zmieni nazwę i przeniesie je do folderu aktualizacji.

#!/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
źródło
8

W tej chwili można to zrobić w następujący sposób

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

Ogólnie będzie to:

$ANDROID_HOME/build-tools/<version_of_build_tools>/aapt dump badging /<path to>/<app name>.apk
SergeyUr
źródło
5

Teraz mogę pomyślnie pobrać wersję pliku APK z jego binarnych danych XML.

W tym temacie mam klucz do mojej odpowiedzi (dodałem również moją wersję kodu Ribo): Jak parsować plik AndroidManifest.xml w pakiecie .apk

Dodatkowo oto kod parsujący XML, który napisałem, a konkretnie w celu pobrania wersji:

Analiza 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;
}

Porównanie wersji (widziane jako SystemPackageTools.compareVersions w poprzednim fragmencie) UWAGA: Ten kod jest inspirowany następującym tematem: Skuteczny sposób porównywania ciągów wersji w Javie

/**
 * 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;
}

Mam nadzieję, że to pomoże.

Mathieu
źródło
Czy zaktualizowałeś się do nowej wersji Gradle lub Android Studio? Czy to nadal działa? Kod Robio nie działa już dla mnie w celu dekompresji pliku XML.
Wysłannik GR
4

W przypadku scenariusza aktualizacji alternatywnym podejściem może być posiadanie usługi sieci Web, która dostarcza bieżący numer wersji i sprawdzanie go zamiast pobierania całego apka tylko w celu sprawdzenia jego wersji. Zaoszczędziłoby to trochę przepustowości, byłoby nieco bardziej wydajne (znacznie szybsze do pobrania niż apk, jeśli cały apk nie jest potrzebny przez większość czasu) i znacznie prostsze do wdrożenia.

W najprostszej formie możesz mieć prosty plik tekstowy na swoim serwerze ... http://some-place.com/current-app-version.txt

Wewnątrz tego pliku tekstowego jest coś podobnego

3.1.4

a następnie pobierz ten plik i porównaj z aktualnie zainstalowaną wersją.

Budowanie bardziej zaawansowanego rozwiązania tego polegałoby na wdrożeniu odpowiedniej usługi internetowej i wywołaniu interfejsu API przy uruchomieniu, który mógłby zwrócić trochę JSON, tj . http://api.some-place.com/versionCheck:

{
    "current_version": "3.1.4"
}
grego
źródło
Jesteś na dobrej drodze - nie należy pobierać pliku, aby uzyskać jego wersję. Chcę tylko wspomnieć, że nie polecam RĘCZNIE tworzenia pliku tekstowego - zbyt łatwo, aby nie pasował do rzeczywistego pliku apk. Zamiast tego napisz skrypt (wykorzystujący jedną z pozostałych odpowiedzi), aby zajrzeć do apka i zobaczyć, jaka jest jego wersja, przechowując go w pliku lub bazie danych, aby można go było pobrać w sposób omawiany.
ToolmakerSteve,
Tak, w tym scenariuszu nie chodzi o to, że w ogóle musi on pasować do APK, tylko o to, że najnowszy numer wersji jest publicznie dostępny.
grego,
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();
    }
Wayne Len
źródło
10
To nie jest odpowiedź na pytanie. Jest to kod, który w uruchomionej aplikacji pobiera informacje o wersji i wyświetla je. Pytanie brzmi: jak uzyskać te informacje z pliku APK bez instalowania aplikacji .
ToolmakerSteve,
Dotyczy to tylko już zainstalowanej aplikacji, a nie pliku apk.
Rhusfer