Closed baltpeter closed 1 year ago
are you aware of the -s
flag of googleplay
?
-s single APK
@mal-tee I'm a bit wary of that because I don't really understand what it does (mostly: does that ask the server for a single APK or does it do some kind of merging client-side?). But definitely something to consider, thanks!
Interestingly, I just observed the same thing on a physical phone running Android 10.
ChatGPT suggested:
You can use a tool such as
aapt
orapksigner
to list the CPU architectures that an APK supports. For example, to list the supported CPU architectures of the de.hafas.android.db-221000003.apk file, you can use the following command:aapt dump badging de.hafas.android.db-221000003.apk | grep -E 'package|native'
This will output information about the package and the native libraries it contains. Look for the line that starts with
native-code
to see the supported CPU architectures.
And, indeed:
❯ for f in *.apk; do echo "$f:"; aapt dump badging $f | grep -E 'package|native'; echo "\n"; done
de.hafas.android.db-221200000.apk:
package: name='de.hafas.android.db' versionCode='221200000' versionName='22.12.p00.00' compileSdkVersion='33' compileSdkVersionCodename='13'
de.hafas.android.db-config.de-221200000.apk:
package: name='de.hafas.android.db' versionCode='221200000' versionName='' split='config.de'
de.hafas.android.db-config.mdpi-221200000.apk:
package: name='de.hafas.android.db' versionCode='221200000' versionName='' split='config.mdpi'
de.hafas.android.db-config.x86-221200000.apk:
package: name='de.hafas.android.db' versionCode='221200000' versionName='' split='config.x86'
native-code: 'x86'
I'll add support for parsing the architecture to parseAppMeta()
.
I noticed that the version is currently being parsed incorrectly for split APKs (where the version is empty):
{ id: 'de.hafas.android.db', version: "' split=", architecture: 'x86' }
I don't know yet what the possible values for native-code
are, and whether there can be native code for more than one architecture in an APK.
So, I ran this little script on ~9000 split APKs:
import glob from 'glob';
import { parseAppMeta } from './index';
(async () => {
const counts = {};
const files = glob.sync('/media/benni/storage2/tmp/ma-apps/android/**/*.apk');
for (const file of files) {
const appMeta = await parseAppMeta(file);
if (!appMeta) {
console.log('Invalid app:', file);
continue;
}
if (!counts[appMeta.architectures]) counts[appMeta.architectures] = 0;
counts[appMeta.architectures]++;
}
console.log();
console.log(counts);
})();
Result:
{
undefined: 6357,
x86: 1169,
'armeabi-v7a': 618,
'arm64-v8a': 705,
armeabi: 5,
'': 1,
'.DS_Store': 1
}
Actually, that wasn't quite correct. In parseAppMeta()
, I matched on /native-code: '(.+)'/
. But that way, I couldn't be sure whether multiple values are possible. So, I changed that to /native-code: (.+)/
and re-ran.
And indeed:
{
undefined: 6357,
"'x86'": 1168,
"'armeabi-v7a'": 616,
"'arm64-v8a' 'armeabi' 'armeabi-v7a' 'mips' 'mips64' 'x86' 'x86_64'": 64,
"'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'": 291,
"'arm64-v8a' 'armeabi-v7a' 'mips' 'mips64' 'x86' 'x86_64'": 8,
"'arm64-v8a' 'armeabi-v7a'": 179,
"'arm64-v8a' 'armeabi' 'armeabi-v7a' 'x86' 'x86_64'": 81,
"'arm64-v8a' 'armeabi' 'armeabi-v7a'": 11,
"'arm64-v8a' 'armeabi-v7a' 'x86_64'": 12,
"'arm64-v8a' 'armeabi' 'armeabi-v7a' 'mips' 'x86' 'x86_64'": 29,
"'armeabi'": 5,
"'arm64-v8a' 'armeabi-v7a' 'mips' 'x86' 'x86_64'": 15,
"'arm64-v8a' 'armeabi' 'mips' 'x86' 'x86_64'": 1,
"'arm64-v8a' 'armeabi'": 9,
"'arm64-v8a' 'armeabi' 'armeabi-v7a' 'x86'": 1,
"'arm64-v8a' 'armeabi-v7a' 'commons-io-2.4.jar' 'x86' 'x86_64'": 1,
"'arm64-v8a' 'armeabi' 'armeabi-v7a' 'x86_64'": 1,
"'armeabi-v7a' 'x86'": 2,
"'x86' 'x86_64'": 1,
"'' 'x86'": 1,
"'arm64-v8a' 'armeabi-v7a' 'commons-io-2.4.jar'": 1,
"'arm64-v8a' 'armeabi-v7a' 'commons-codec-1.8.jar' 'x86' 'x86_64'": 1,
"'.DS_Store' 'arm64-v8a' 'armeabi' 'armeabi-v7a' 'mips' 'x86' 'x86_64'": 1
}
So, there can be native code for multiple architectures in one APK.
My current implementation in #52 is not quite correct.
I am using getprop ro.product.cpu.abi
to check whether the device supports a particular architecture. But instead, I need to check ro.product.cpu.abilist
, because a device can support more than its primary architecture:
❯ adb shell getprop | grep ro.product.cpu.abi
[ro.product.cpu.abi]: [x86_64]
[ro.product.cpu.abilist]: [x86_64,x86,arm64-v8a,armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [x86,armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: [x86_64,arm64-v8a]
My observations in #54 explain why I initially thought this was a problem with Android 13 and was then confused when I observed the same thing on a physical Android 10 phone.
My assumption that install-multiple
automatically filtered out split APKs for the wrong architecture at any point was wrong. It never did. I only thought it did because the Android 11 emulator I used had support for all relevant architectures:
❯ adb shell getprop | grep ro.product.cpu.abi
[ro.product.cpu.abi]: [x86_64]
[ro.product.cpu.abilist]: [x86_64,x86,arm64-v8a,armeabi-v7a,armeabi]
[ro.product.cpu.abilist32]: [x86,armeabi-v7a,armeabi]
[ro.product.cpu.abilist64]: [x86_64,arm64-v8a]
As such, it was able to install split APKs for all those architectures just fine. Meanwhile the Android 13 emulator and the Android 10 phone only supported x86_64 or arm64-v8a, respectively.
This means that it is still relevant that we fix this problem, but for different reasons than I initially thought. :D
My current implementation in #52 is not quite correct.
We download Android apps as split APKs. For example, downloading DB Navigator using googleplay yields the following files:
These tend to include APKs for different architectures (
de.hafas.android.db-config.x86-221000003.apk
for x86 in this case). We were previously using an x86_64 emulator running Android 11 (API level 30). Here, we could just install all split APKs:This works even when there are split APKs for architectures not supported by the device/emulator. This seems to have changed with Android 13 (API level 33). Now, running the above command results in an error:
We now have to explicitly specify only the parts that are actually supported by the device. So, in this case for an x86_64 emulator:
That's quite annoying. Hopefully, there is a better way.
Update: I misunderstood things a little here.