Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

android build: Split up APKs per ABI #5296

Merged
merged 2 commits into from
Mar 15, 2022
Merged

Conversation

gnprice
Copy link
Member

@gnprice gnprice commented Mar 14, 2022

The great bulk of the size of our APK comes from the native
libraries in it (most of all libjsc.so). Those exist in a
separate copy for each of our 4 ABIs, but any given device
is going to use only one of those.

So, build a separate APK for each ABI. The new APK for armeabi-v7a
(the most widely-supported) is 35.1MB, compared to 132.3MB for the
old all-in-one APK.

This has no effect for the many Android users who get our app via
the Google Play Store: there, we already upload an AAB, which
Google splits up into several APKs still more finely. See #3547.

Fixes: #5295

/cc @IzzySoft

gnprice added 2 commits March 14, 2022 16:35
This bit of code doesn't currently do anything, because we build a
single APK for all devices.  It would if we started building split
APKs per ABI.

This sort of thing was required for publishing an app as multiple APKs
on Google Play.  (The docs link in the comment is broken; a current
version appears to be:
  https://developer.android.com/google/play/publishing/multiple-apks#VersionCodes
.)  Google Play would use the different version codes as tiebreakers
to decide which APK to give to a device when several were supported.

But on Google Play, publishing multiple APKs has been obsolete for
years.  Instead, one uploads a single AAB, "Android App Bundle", and
then Google slices and dices that into separate APKs for the per-ABI
files, the per-screen-density files, the per-screen-size files, and
so on.  Each device downloads the specific combination of those pieces
that applies to it.

We never did publish multiple APKs on Google Play, and because it's
now obsolete we never will; we've been using AABs since zulip#3547 in 2020.

We do intend to start publishing multiple APKs, which would cause this
bit of code to start having an effect.  But this convention doesn't
seem to be useful anywhere else: for a user installing directly from
our GitHub release it's invisible and unhelpful, and the one known
downstream distributor of our APKs plans to just distribute the one
most widely-supported of them anyway:
  zulip#1270 (comment)
which will make this hack have no use there either.

So it seems like messing with the version codes like this would only
cause confusion.  Instead, just let the different per-ABI APKs for a
given release have the same version code as each other, and as the AAB
does.  That way the next release after v27.181 will have version code
simply 182, rather than 182003 et al.
The great bulk of the size of our APK comes from the native
libraries in it (most of all libjsc.so).  Those exist in a
separate copy for each of our 4 ABIs, but any given device
is going to use only one of those.

So, build a separate APK for each ABI.  The new APK for armeabi-v7a
(the most widely-supported) is 35.1MB, compared to 132.3MB for the
old all-in-one APK.

This has no effect for the many Android users who get our app via
the Google Play Store: there, we already upload an AAB, which
Google splits up into several APKs still more finely.  See zulip#3547.

Fixes: zulip#5295
@chrisbobbe
Copy link
Contributor

Thanks! Merged.

@IzzySoft
Copy link

Thanks a lot! For clarification: does his mean the next release will have per-ABI builds attached, and one of them will carry the string armeabi-v7a in its name? Asking so I can prepare my config here to pick the correct APK once the next release shows up.

Another question goes towards versionCode. Flutter e.g. prefixes with 2 digits (10, 20, 30, 40). How will that be handled here? That's what I couldn't guess from the diffs (not that it matters much for my repo as long as versionCode is increased – but e.g. with Flutter one needs to make sure that the arm64 build gets the higher prefix so when e.g. moving from a 32bit device to a 64bit device, people could easily upgrade to the arm64 build if they wanted to).

@gnprice gnprice deleted the pr-split-apk branch March 15, 2022 16:35
@gnprice
Copy link
Member Author

gnprice commented Mar 15, 2022

Thanks a lot! For clarification: does his mean the next release will have per-ABI builds attached, and one of them will carry the string armeabi-v7a in its name? Asking so I can prepare my config here to pick the correct APK once the next release shows up.

Yep, that's right. Here's what the APKs look like, with sizes, in a test build after this change:

40317136 app-arm64-v8a-release.apk
35117794 app-armeabi-v7a-release.apk
41570699 app-x86_64-release.apk
38701598 app-x86-release.apk

(That wasn't quite a realistic simulation of a release -- I signed them with my development key, rather than the distribution key used for releases. But should be very close.)

Another question goes towards versionCode. Flutter e.g. prefixes with 2 digits (10, 20, 30, 40). How will that be handled here? That's what I couldn't guess from the diffs (not that it matters much for my repo as long as versionCode is increased – but e.g. with Flutter one needs to make sure that the arm64 build gets the higher prefix so when e.g. moving from a 32bit device to a 64bit device, people could easily upgrade to the arm64 build if they wanted to).

Hmm, I see. We actually had some code along those lines -- inherited from the React Native template app, where it originates with facebook/react-native@abb81eb27 from 2016 -- but as you see I took it out in this PR (in 0107348 ) because I thought it was mainly useful on Google Play, and it seems like it makes things more complicated.

So at the current version after this PR, the versionCode on the next release would be 182 on all four APKs, following the current release's versionCode of 181 on the all-in-one APK.

It's interesting that Flutter does a similar thing. Do you perhaps have a link where I can read more about what Flutter does?

In any case I think it should be fine if we make our next release with everything at simply 182, and then switch to a more complicated scheme in the next release or the next. The more complicated schemes will all have larger version codes, so it's easy to switch in that direction -- hard to do the reverse.

@gnprice
Copy link
Member Author

gnprice commented Mar 15, 2022

but e.g. with Flutter one needs to make sure that the arm64 build gets the higher prefix so when e.g. moving from a 32bit device to a 64bit device, people could easily upgrade to the arm64 build if they wanted to

Hmm, so, I believe if you have an app installed with a given versionCode, and then you try to install an APK with (the same package name and) the same versionCode, that works just fine. What you can't ordinarily do is a downgrade, to a smaller version code; but a cross-grade to the same version code is treated the same as an upgrade to a larger one.

In particular, when doing development, I'll regularly be making small changes and then installing a new version of the app, and leaving the versionCode constant, and it's fine; whereas if I make the versionCode smaller (like by switching to an old branch where it was smaller), I get an error. And that includes sometimes doing this with release builds, and on a physical device -- so I don't think it's something specific to development.

That means that if you're going to have the 32-bit and 64-bit versions have different version codes, it's important that the one for 64-bit be higher instead of lower, but it's fine for them to be the same.

Where it is useful for them to be different is if you've just upgraded the device and you're looking in an automated way to find which apps you can upgrade: you just check for any app you have where there's an installable APK with a higher version code than you have. It means that your app store/repository, or your browser for it, does need to understand how to filter which APKs are installable for your device, but doesn't need to understand anything about how to choose between several that are installable. Similarly when you're installing the app for the first time.

Based on that old Android docs page https://developer.android.com/google/play/publishing/multiple-apks , that design is apparently exactly how Google Play worked, back before they started doing AABs instead.

But that won't be relevant for people using your repository, as I understand it -- you're planning to publish just the armeabi-v7a version.

It also isn't relevant for people installing the app directly from our GitHub releases -- one doesn't even see the versionCode attributes there.

Perhaps in the future it could be useful for people on F-Droid? Wouldn't be surprising if F-Droid works the same way as Google Play used to in this respect.

@IzzySoft
Copy link

app-armeabi-v7a-release.apk

Thanks! I've just set this in your app's YAML config here, in preparation for the next release.

the versionCode on the next release would be 182 on all four APKs

Perfect, that way users can even update the other way around when e.g. switching devices.

Do you perhaps have a link where I can read more about what Flutter does?

Afraid not – I can just tell from what I see with other projects. I never read any Flutter docs TBH. But what I can do is give you a link to my notes.

I think it should be fine if we make our next release with everything at simply 182, and then switch to a more complicated scheme in the next release or the next.

I fully agree with the first part. The second part would just be needed if a repository would want to host more then one ABI build (as applicationId+versionCode is a unique identifier at least with F-Droid repos). Should that be needed, my personal opinion is it's much better to just "count with gaps" – e.g. add a digit at the end. Taking your example here: 182 is the current versionCode for the full build – so make that e.g. 1820 for the full one, 1821 for x86, 1822 for x64, 1823 for armeabi, 1824 for arm64. Then count up by e.g. 10 for the next release (1830-1834), and so on. Keeps them close together, guarantees a "unique key", and still makes cross-updates between "compatible archs" easy. That's a pattern I've seen a couple of times with F-Droid builds.

a cross-grade to the same version code is treated the same as an upgrade to a larger one.

Ack to that paragraph with a minor deviation: no update notifications, so you'd need to trigger that manually. But well, in case of updating to a related ABI, you'd do that manually anyway.

That means that if you're going to have the 32-bit and 64-bit versions have different version codes, it's important that the one for 64-bit be higher instead of lower, but it's fine for them to be the same.

Yupp, exactly. Same for the paragraph following that one 😄 In such a case, e.g. the F-Droid client would offer you to install the arm64 of the same version if you came from armeabi, as it has a higher VC and matches your arch.

But that won't be relevant for people using your repository, as I understand it

I can confirm that, yes 😄

Perhaps in the future it could be useful for people on F-Droid?

Point, match and victory – that was exactly what I had in mind 🤩 One day, Zulip might be listed there – and thanks to the work we did here, everything would be prepared well 🥳 Did I mention I'm one of the F-Droid maintainers as well? 🤔 🙈

@gnprice
Copy link
Member Author

gnprice commented Mar 17, 2022

Thanks! I've just set this in your app's YAML config here, in preparation for the next release.

Sounds good!

We've now tagged a 182 release and have a build running in alpha -- meaning a handful of us update our own devices to it. It should be out in beta very soon -- meaning it'll be a GitHub release marked "pre-release". Then probably next week we'll roll it out to production, so marking that GitHub release as no longer a pre-release.

Should that be needed, my personal opinion is it's much better to just "count with gaps" – e.g. add a digit at the end. Taking your example here: 182 is the current versionCode for the full build – so make that e.g. 1820 for the full one, 1821 for x86, 1822 for x64, 1823 for armeabi, 1824 for arm64. Then count up by e.g. 10 for the next release (1830-1834), and so on. Keeps them close together, guarantees a "unique key", and still makes cross-updates between "compatible archs" easy. That's a pattern I've seen a couple of times with F-Droid builds.

Cool, that sounds like a good plan.

a cross-grade to the same version code is treated the same as an upgrade to a larger one.

Ack to that paragraph with a minor deviation: no update notifications, so you'd need to trigger that manually.

Ah, yeah -- that paragraph was meant to only describe the behavior of the Android system itself, not any distribution channels. Android itself pays attention to the versionCode, and imposes the constraint that you can't downgrade. But Android doesn't know anything about a repository to check for updates in; that's the job of other systems on top of it, like Google Play or F-Droid.

Point, match and victory – that was exactly what I had in mind 🤩 One day, Zulip might be listed there – and thanks to the work we did here, everything would be prepared well 🥳 Did I mention I'm one of the F-Droid maintainers as well? 🤔 🙈

Cool 😄 Thanks for all your help with this!

@IzzySoft
Copy link

so marking that GitHub release as no longer a pre-release

Ah, good. Just to let you know: my updater is currently set to ignore pre-releases altogether – and as I've just noticed you name the tags v<versionName>, I've also adjusted it to only consider those. So if you want my updater skipping a release, you can either make it a pre-release or use a different "prefix" in the tag name (e.g. test-<versionCode>).

that paragraph was meant to only describe the behavior of the Android system itself …

Yes, so we're having the same things in mind – matches perfectly what I meant.

Thanks for all your help with this!

Gladly done! If there are any questions you need help with concerning F-Droid (or my repo, etc), just ring my bell – I try to help if I can (i.e. if time permits – to many pots on the stove 🙈)

gnprice added a commit to gnprice/zulip that referenced this pull request Apr 6, 2022
The old link here broke once we introduced separate APKs per ABI,
in zulip/zulip-mobile#5296.

We could make a direct link to app-armeabi-v7a-release.apk , the one
that's compatible with almost all devices.  But perhaps better is to
just go back to linking to the release page, where the user can
choose the best APK for their device.  (If they're in the habit of
downloading APKs manually to install on their device, then probably
that means they're going to be used to choosing the right one.)

User report and discussion:
  https://chat.zulip.org/#narrow/stream/48-mobile/topic/Direct.20apk.20download.20link.20is.20404/near/1358758
timabbott pushed a commit to zulip/zulip that referenced this pull request Apr 6, 2022
The old link here broke once we introduced separate APKs per ABI,
in zulip/zulip-mobile#5296.

We could make a direct link to app-armeabi-v7a-release.apk , the one
that's compatible with almost all devices.  But perhaps better is to
just go back to linking to the release page, where the user can
choose the best APK for their device.  (If they're in the habit of
downloading APKs manually to install on their device, then probably
that means they're going to be used to choosing the right one.)

User report and discussion:
  https://chat.zulip.org/#narrow/stream/48-mobile/topic/Direct.20apk.20download.20link.20is.20404/near/1358758
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Split APK up by ABI
3 participants