Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[android] Make Expo Android use a custom SoLoader that implements 0.1…
….0 and 0.5.1 RN 0.57 and some of its dependencies (Fresco) updated to use SoLoader 0.5.1, which introduced breaking changes compared to 0.1.0. Namely, `SoLoader.loadLibrary` now returns a boolean instead of nothing. This causes old Java bytecode -- including older Expo ABIs -- that was looking for `void SoLoader.loadLibrary` to fail because the method is not defined. The behavior of SoLoader is seemingly otherwise compatible, so we may be able to just add the old method back. This commit adds a custom SoLoader AAR and overrides the version from Maven so that we use it when building Expo, but unimodules can depend on Maven's SoLoader 0.5.1 in the build.gradle files and work with the latest RN (0.57) and address issues like https://github.com/expo/expo-gl/issues/2. Test Plan: Built the Android client and loaded the published NavigationPlayground project, which uses SDK 30. Without this patch, the app crashes and adb logcat shows an error with SoLoader missing the void `loadLibrary` method. With this patch, NavigationPlayground (SDK 30) loads. Also unpacked the APK and examined the DEX files (classes3.dex specifically) and verified that both the old and new SoLoader methods are defined: ``` #13 : (in Lcom/facebook/soloader/SoLoader;) name : 'loadLibrary' type : '(Ljava/lang/String;)V' access : 0x0009 (PUBLIC STATIC) code - registers : 1 ins : 1 outs : 1 insns size : 4 16-bit code units catches : (none) positions : 0x0000 line=840 locals : 0x0000 - 0x0004 reg=0 shortName Ljava/lang/String; #14 : (in Lcom/facebook/soloader/SoLoader;) name : 'loadLibrary' type : '(Ljava/lang/String;)Z' access : 0x0009 (PUBLIC STATIC) code - registers : 2 ins : 1 outs : 2 insns size : 6 16-bit code units catches : (none) positions : 0x0000 line=455 locals : 0x0000 - 0x0006 reg=1 shortName Ljava/lang/String; ``` --- There is some sorcery to the AAR so these are some notes. Implementing the new and old SoLoader methods is complicated by the fact that we can't add the old method to the SoLoader.java source and recompile SoLoader. Java does not support return-type dispatch, so `void loadLibrary(String)` and `boolean loadLibrary(String)` fail to compile when together. However, JVM bytecode does allow for two methods with the same name and parameter types but different return values to co-exist. (In fact, some bytecode obfuscators use this behavior as they make take two differently-named Java methods with different return types and produce JVM bytecode with the same method name and different return types.) By editing the bytecode directly we can support the old and new SoLoader interfaces simultaneously. To do this, I found the SoLoader AAR from the Gradle cache, extracted it, then extracted classes.jar and found SoLoader.class. In a JVM bytecode editor I added a new method: ``` public static void loadLibrary(java.lang.String); descriptor: (Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokestatic #635 // Method loadLibrary:(Ljava/lang/String;)Z 4: pop 5: return LineNumberTable: line 840: 0 LocalVariableTable: Start Length Slot Name Signature 0 6 0 shortName Ljava/lang/String; ``` The implementation of this method was written by hand, which seems error-prone so I verified it three ways: - Wrote a small test class with a similar method and inspected its bytecode and ensured it matched the new SoLoader method's bytecode. - Ran the modified .class file through a decompiler and verified the Java code looked right. - Wrote a small test class that called into the actual modified .class file and verified I could invoke the underlying `SoLoader.loadLibrary` method i.e. the thin patched method was working.
- Loading branch information