Skip to content

Possibly reduce minimum Android API Level #5

@BenjaminAmos

Description

@BenjaminAmos

Currently, the minimum API level has been raised to API 24 (Android 7.0). According to the Google Play survey, this would allow the app to run on ~57.9% of Android devices still out there. Due to the intergration of gestalt, by-default the API level cannot be lowered, as this would disallow usage of Java 8 APIs in general. The previous Android release of the app targeted API 10 (Android 2.3.3), which would support 100% of the surveyed devices (although that is biased, as I don't think that the Google Play store supports versions of Android that are any older). This may cause significant reduction in the number of new potential players, simply because they have not updated their devices.

(source: https://developer.android.com/about/dashboards)

My primary physical testing device runs on Android API 16 (99.4% coverage), which is what I would plan to support as a minimum. So far as I can tell, there are two primary approaches to reduce the minimum API level:

  • Re-write all the code to be Java 7 API compliant
  • Bytecode manipulation

Java 7 re-write

I did attempt to re-write all the code to be Java 7 API compliant a while back, although it was a considerable amount of work to achieve and would have been a step backwards in terms of desktop support. It involved changes to gestalt, the Android facade and the main DestinationSol repository. It also required me to modify my warp module in order for it to run. The primary issue here is that the code always compiles. All the missing methods fail to resolve at runtime, meaning that you constantly need to re-compile and deploy the apk to test any changes. The primary APIs that I had to re-write are as follows (this is from memory, so it is not fully comprehensive):

  • Streams API (.stream().x()) - This was used extensively in the code and usages would probably have been re-added again in the code with future contributions (Even with the current Android code, JSONObject.*Float does not exist on Android and is a recurring issue).
  • .<operation>If methods on collections (e.g. myList.removeIf(x -> true))
  • .foreach methods on collections (myList.foreach(x -> doStuff(x)))
  • Any uses of the java.util.function package
  • Any uses of the java.util.stream package
  • Any uses of the Map.compute* methods, as well as many other methods in the Map interface
  • SimpleDateFormat format strings containing the "X" character (this is not supported on Android until API 24) - This issue primarily affected gestalt, rather than Destination Sol directly

The code ran but it seems that I deleted it at some point, so I can't really demonstrate the extent of the changes needed.

Bytecode manipulation

My second attempt at investigating this involved java bytecode manipulation at compile time. This worked far better, as I was able to keep most of the code the same.

I used a tool for Android builds called ProGuard, which has the ability to re-write java bytecode to run on older JVMs. Recently, it also added the ability to back-port any Java 8 Streams API usages in the code, which helped immensely. Unfortunately, some of the required newer APIs that were not back-ported by ProGuard. In these cases, I wrote some code that manually manipulated the bytecode as part of the Android build process (none of this happens when doing non-Android builds).

There were very few cases where this needed to be done. The bytecode-manipulator simply looked for the usages of certain functions and classes and redirected them to the relevant compatibility classes, many of which I included within the Android facade source. You can see the source code for it here. The following items needed to be re-directed:

  • java.text.SimpleDateFormat constructor
  • java.util.Locale.getDefault method
  • java.lang.String.join
  • java.util.Objects class
  • Guava com.google.common.base.Function parent interface

I did have to re-write some of the code in the Destination Sol codebase as well as it's Android facade, however the changes were mostly minimal. They primarily consisted of removing .foreach calls (which given enough time I would have worked-out how to re-write them using the pre-build bytecode-manipulator). It appears that .foreach calls were automatially ported by the ProGuard backporter and so did not need to be modified. The changes to those calls were reverted.

Most of the manual bytecode changes should be able to be removed when the Android Gradle Plugin 4 is released (see https://developer.android.com/studio/preview/features/#j8-desugar for more information), as it provides a more comprehensive back-porting functionality than ProGuard does.

The primary issue with this method is that I am not sure of what newer APIs could be used in the future. Also, my implementation has a rather long initial loading time, due to I think the addition of multi-dex support.

I have opened this issue to discuss any other possible alternatives, as well as the viability of the bytecode manipulation as a solution to lower the minimum API level supported. I've included the Java 7 example here to describe a possible, yet unlikely solution, due to the reduction in developer productivity.


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions