31

The short summary is: How do I build an APK and separate libraries (by which I mean sets of classes (and ideally, resources too) in some form, such as JAR, AAR or DEX files), but not include those libraries in the APK; instead, the app loads them at run time?

Detail

So my main question is how to build such an app (e.g. Gradle configuration). How do I specify which classes go into which JAR or DEX files? Do I create an Android Studio module for each DEX file I want to end up with?

A closely related question is how the Java code should then load the external libraries and access their classes at run time. For the latter, I'm hopeful that the approach shown at accessing to classes of app from dex file by classloader would work.

I've tried the instructions at https://developer.android.com/studio/projects/android-library.html, but that builds an APK that does include the dependency library.

I've also tried Multidex (https://developer.android.com/studio/build/multidex.html), but that doesn't seem to leave the developer any control over which classes go in which DEX file, and furthermore, packages them all into a single APK. AFAICT there is no way to control the loading of these DEX files at run time.

Background

There's a possibility of the "X-Y problem" here, so I'd better explain the background.

I'm building an app for a client. It's not going to be distributed through an app store, so it won't have access to the normal mechanism for updates. Instead, the client wants the app to be able to update itself by downloading new components of itself to replace the old components, without a need to manually sideload a new APK. The primary motive here is that the updates have to be easy for non-technical users. If the app can control the update process, it can make it smooth and guide the user.

Moreover, the app will be used in areas where internet access is scarce and expensive, so the client wants to be able to issue app updates in smaller chunks (e.g. 2MB) rather than forcing the user to re-download the whole app to receive a small update.

One aspect of the requirements I should mention, in case it matters, is that the libraries to be loaded at run time are supposed to live on a microSD card. This can also help with distribution of updates without internet access.

The current status of the app is that it's about 50% written: That is, a couple of earlier versions have been released, but the app now needs to be modified (restructured) to meet the above requirements, as well as others.

Community
  • 1
  • 1
LarsH
  • 27,481
  • 8
  • 94
  • 152
  • Consider building your own app installer - as apks are just zip files - it will be up to you to deliver them to device http://stackoverflow.com/questions/5803999/install-apps-silently-with-granted-install-packages-permission – Morrison Chang Aug 26 '16 at 22:21
  • It's an idea that has crossed my mind. But (a) how would I then break updates into small chunks -- would I have to make each chunk a separate app to be installed? How would this impact the process of developing the overall app (app collection)? (b) the accepted answer at the question you linked to uses reflection to access an undocumented feature. Is it reliable? (c) Will this let me install the apps to microSD card, even if the user hasn't chosen to install apps to SD card? – LarsH Aug 29 '16 at 10:46
  • 2
    I hope you have *great* insurance, as the lawsuits from the security problems your approach presents would bankrupt many people. "Do I create an Android Studio module for each DEX file I want to end up with?" -- I would assume so, getting the DEX out of the intermediate build results. "how the Java code should then load the external libraries and access their classes at run time" -- as you indicated, `DexClassLoader` is probably what you want. – CommonsWare Aug 29 '16 at 19:30
  • 2
    "but that builds an APK that does include the dependency library" -- you'll need a tiny library module in common between the APK module and what I'll call the DEX modules. That common module would define the Java interfaces that both sides will use. Then, you can remove the dependencies on the DEX modules from the APK module, using `DexClassLoader`, reflection, and so on to load your DEXes. At least, this is how I would approach it, if somebody was threatening to nuke Miami if I failed to deliver an implementation meeting your specs. I do not know if this will work, as I haven't tried it. – CommonsWare Aug 29 '16 at 19:32
  • 1
    Depending on what the app is and does, you might be better served using a hybrid app framework, arranging to load the HTML/CSS/JS from a cache that you can update. This avoids all of the DEX headaches, assuming that a reasonable percentage of the updates would be limited to those Web assets. – CommonsWare Aug 29 '16 at 19:36
  • @CommonsWare: Thanks for your response. Can you elaborate on the security problems? Why would this approach be more of a problem than just handing someone an APK on a microSD card? I.e. if I wanted to compromise their device, couldn't I do it just as well with a simple sideloaded app, as with a sideloaded app that dynamically loads classes? Are you thinking of the risk of a third party planting malicious modules to be loaded? I was planning to have the app core require dynamically loaded modules to be signed with a cert; does that help? – LarsH Aug 29 '16 at 20:14
  • 1
    @LarsH: "couldn't I do it just as well with a simple sideloaded app" -- if you mean a sideloaded update to your existing app, then no, unless the attacker stole your signing key. Android would refuse to install the update. "Are you thinking of the risk of a third party planting malicious modules to be loaded?" -- yes, or modifying existing modules. "does that help?" -- possibly, if done correctly from the outset. Then it becomes a timing attack. Having persistent read/write code is just very difficult to secure well. – CommonsWare Aug 29 '16 at 20:23
  • @CommonsWare: I didn't mean an update, but a sideloaded original app. Thanks again for your help. – LarsH Aug 29 '16 at 20:25
  • 1
    Both [JRebel for Android](https://zeroturnaround.com/software/jrebel-for-android) and [Bazel mobile-install](https://bazel.io/versions/master/docs/mobile-install.html) manage incremental updates of an app via adb. Maybe this can give you clues on how to do incremental updates. That being said, I think you shouldn't invest time in this complex task, and just rely on the Google Play store. – rds Aug 29 '16 at 21:31
  • An alternative approach would be to split you app in smaller more specialized apps, and let the update append on the full apk for those. Things like ContentProvider and intents can help you decouple the code. – rds Aug 29 '16 at 21:33
  • @rds: Thanks for these ideas. Can you clarify what you mean by "let the update append on the full apk for those"? – LarsH Aug 30 '16 at 02:27
  • if i understand you these two posts answer you: http://stackoverflow.com/questions/24135158/how-to-load-a-jar-from-assets-folder-at-runtime http://stackoverflow.com/questions/6857807/is-it-possible-to-dynamically-load-a-library-at-runtime-from-an-android-applicat – Amir Ziarati Sep 04 '16 at 04:44
  • silent side-loading https://paulononaka.wordpress.com/2011/07/02/how-to-install-a-application-in-background-on-android/ there are some workarounds I have used in the past like jni libraries that are really executables, I'll update you. – Jon Goodwin Sep 06 '16 at 18:53
  • 1
    Tested gradle build ok for the dexloader project. Loads com.example.toastlib.jar from the SDcard (not assets folder). ( you must read the README.md file in the project to build it). – Jon Goodwin Sep 11 '16 at 19:03
  • Is this of any use ? http://stackoverflow.com/questions/39513048/how-to-patch-an-application-in-emulator-device-similar-to-how-google-play-is-doi#comment66358683_39513048 – Jon Goodwin Sep 17 '16 at 13:35

2 Answers2

6

This tutorial is a good start for external loading of DEX files. Only three small files of source (MainActivity.java, LibraryInterface.java, LibraryProvider.java) and it copies secondary_dex.jar from the assets folder, into internal application storage [outdex/dex] (the internet is also stated as possible in the tutorial). You have to build it with ant, because it uses custom build steps. I tried it, it works fine. Worth a look.
custom class loading in Dalvik and ART


UPDATE this code has been ported to Android Studio gradle (no need for ant). https://github.com/timrae/custom-class-loader
Tested ok. Copies com.example.toastlib.jar from the SDcard into internal application storage [outdex/dex],(not assets folder). ( you must read the README.md file in the project to build it).

Q: How do I add an Activity, I cannot add it to the manifest ?
A: Use Fragments, they don't need entries in the manifest.

Q: A Jar with resources that is meant to be added to an existing project needs to be able to merge its resources with the project's own resources (R.).
A: Hacks are available, Data file...
Packaging Android resource files within a distributable Jar file

Q: The external file has wrong permissions.
A: Import it.

Q: I need to add uses-permission.
A: Use API23 you can programmatically add uses-permissions (but they still need to be declared in the Manifest, so the new permissions model is probably not much use to us).

This section is for more general users (@LarsH has more specific requirements about updates), The example above is 17kb apk and 1 kb jar. You could put the bulk of you code in the one-off jar, and updates would involve just loading an new Apk (and then importing the bulk code jar, to minimise the data transfer). When the Apk gets too big, start again with a small Apk and everything migrated to another jar (import 2 jar's). You need to balance coding effort, user experience, maintainability, supportability, bandwidth, android rules, play store rules (if these words even exist ;O)).

NOTE Dalvik is discontinued

The successor of Dalvik is Android Runtime (ART), which uses the same bytecode and .dex files (but not .odex files), with the succession aiming at performance improvements transparent to the end users. The new runtime environment was included for the first time in Android 4.4 "KitKat" as a technology preview, and replaced Dalvik entirely in later versions; Android 5.0 "Lollipop" is the first version in which ART is the only included runtime.

Community
  • 1
  • 1
Jon Goodwin
  • 9,053
  • 5
  • 35
  • 54
  • Thanks, I had seen this tutorial as a possible approach. Regressing to ant is a big drawback, as currently gradle takes care of a bunch of details that I don't need to worry about -- if we move to ant, we'll have to learn about them and how to get ant to do those, and maintain the build configuration moving forward after Google has moved away from it. Or, there might be a way to port the custom build steps into gradle? – LarsH Sep 02 '16 at 10:03
  • 1
    good news this code has already been ported to Android studio gradle https://github.com/timrae/custom-class-loader redundant now but quote "Gradle provides excellent integration with Ant." https://docs.gradle.org/current/userguide/ant.htmlALSO in gradle ant.importBuild 'build.xml' – Jon Goodwin Sep 02 '16 at 13:15
  • This port looks very helpful. Thanks ... I will check it out. – LarsH Sep 02 '16 at 14:51
  • I haven't been able to get this answer implemented in order to verify what it can do, but it's the most promising answer the question received during the time I had to award the bounty, and it does substantially answer the question. So, have a bounty! – LarsH Sep 06 '16 at 10:38
  • Thanks LarsH I tried my best, interesting question. I will verify it works on gradle without ant, and update. – Jon Goodwin Sep 06 '16 at 17:07
  • I bow to ur superior skilz. :-) Seriously, I've been pursuing several different options on this problem, as well as other assignments, and haven't yet gotten back to trying this one out. – LarsH Sep 07 '16 at 16:05
  • "Use API23 you can programmatically add uses-permissions" -- not as far as I am aware. – CommonsWare Sep 09 '16 at 22:23
  • Tested gradle build ok for the dexloader project. Loads com.example.toastlib.jar from the SDcard (not assets folder). ( you must read the README.md file in the project to build it). – Jon Goodwin Sep 11 '16 at 19:03
  • @JonGoodwin - Should I really just compile and build `apk` file and rename it to `jar` and then use `dx` tool to extract `dex` file as it states at https://stackoverflow.com/a/25748704/1215106 ? Will such `dex` file work when loaded? – Ωmega Jun 05 '19 at 12:21
  • @ΩmegaI have since discovered (bytebuddy.net) is probably the best way to do it.[https://stackoverflow.com/questions/47390022/how-to-start-a-non-existent-activity-mentioned-in-manifest/47437746#47437746} – Jon Goodwin Jul 05 '19 at 18:48
1

You could try to build multiple apk's with the same sharedUserId and the same process.

This is the plugin mechanism used by Threema

Edit: More about Theema

Threema has one main app and two plugins:

Doing so the main app does not need the permissions for accessing the camera or microphone

larsgrefer
  • 2,735
  • 19
  • 36
  • Interesting idea. Can you tell me more about what Threema does with this? What components are in the initially-launched APK/app, vs. the ones that are launched later? – LarsH Sep 02 '16 at 10:08
  • So Threema applies updates via the Play store. Unfortunately, as described in the original post, the Play store isn't an option, and sideloading new APK's for each update is seen as difficult for users. If there were a way for the app to install the updated APK's with minimal manual intervention required on the part of the user, this would be a good way to approach the problem. – LarsH Sep 02 '16 at 14:49
  • Your app could download the new apk and start the installation process. This way the user just have to click one button – larsgrefer Sep 02 '16 at 15:04
  • It's theoretically possible for an app to start the installation process on an APK, using special privileges ... I'm not sure what the restrictions are. Best case, unless the user has "Security > Unknown sources" turned on (which we have to recommend against), they would have to do that manually; then turn it off manually afterwards. I think there are also at least two or three button taps for the installation itself - Install; Use Package Installer (Once only / Always) (if you haven't already made a persistent choice); and approve access permissions. Which defeats the purpose of the question. – LarsH Sep 02 '16 at 15:16