Search code examples
androidgradleandroid-ndkprefab

Are there any disadvantages to Gradle Prefab AARs?


I'm primarily an Android Java developer, but I have a need for libyuv. I created a module and have it building under CMake. I read about prefab, and for developers like me this is a game changer. As such, I want to share my module as a prefab. I ran into an issue where the ANDROID_STL had to be set to "c++_shared" as opposed to the default (static). So I started reading some more about it. This statement got me thinking prefab might not be the best approach. "This [static runtime] allows the linker to inline and prune as much unused code as possible, leading to the most optimized and smallest application possible." https://developer.android.com/ndk/guides/cpp-support#static_runtimes

To me it sounds like the linker performs shrinking, like R8, but only for static. So, the question is it there a performance or size penalty for using prefabs in this way?

My repo (WIP): https://github.com/dburckh/libyuv-aar

Note: In this particular case, I think I can safely use "ANDROID_STL=none" for my libyuv library as it doesn't use C++.


Solution

  • The problems in your question aren't actually unique to prefab. See the following docs:

    If you distribute a shared library that uses libc++_shared, your users must also use libc++_shared. That's no different if you use prefab or distribute in another fashion; prefab is just the only tool that proactively defends you against these mistakes.

    Now, where prefab does come into this is the options it gives you to work around those constraints:

    1. Distribute a shared library with a dependency on libc++_shared. Any app that consumes your library is forbidden from using libc++_static.
    2. Distribute a shared library with no libc++ dependency (either the library is C and you use ANDROID_STL=none, or because you've carefully followed the advice in the middleware doc). Your consumers are free to use either libc++_shared or libc++_static (or neither), but they must still ship the entire dependency (dead-code-elimination and LTO cannot operate on a shared library dependency).
    3. Distribute a static library. This offers the most flexibility to your consumers. Since your library has not yet been linked the decision between libc++_shared and libc++_static has not been made yet, and the consumer can use either. This also allows the consumer to use LTO on your library, inlining and eliminating dead code as appropriate. The only downside is that if the consumer's app contains multiple shared libraries which each need to consume your library, they cannot use a static library (that would violate ODR).

    The best thing you can do as a package author is do both 3 and either 1 or 2. 3 is the best option for consumers that can safely use the static library, whereas 1 and 2 are good enough for those that can't.

    Note that to include both in the same package, you should rename the static version as libyuv_static or whatever so that the static and shared variants have unique names.

    One last thing, because AGP doesn't have enough context to know how your AAR will be used, you do need to intervene to prevent it from shipping libc++ in the wrong directory: https://github.com/android/ndk-samples/blob/eb8c57aaf8e4e8da8debfe7a6f44149630ac775e/prefab/prefab-publishing/mylibrary/build.gradle#L55-L62.

    Note: In this particular case, I think I can safely use "ANDROID_STL=none" for my libyuv library as it doesn't use C++.

    Correct. That's the right answer for this specific case (and all C-only libraries, which are quite common).