Skip to content

Commit 64701dc

Browse files
committed
Merge branch '12-relinker' into 'dev'
Android: use ReLinker to load native library See merge request objectbox/objectbox-java!11
2 parents 5840728 + d11c4a4 commit 64701dc

File tree

4 files changed

+79
-5
lines changed

4 files changed

+79
-5
lines changed

build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ version = ob_version
33

44
buildscript {
55
ext {
6-
ob_version = '2.4.0-SNAPSHOT'
7-
ob_native_version = ob_version // Be careful to diverge here; easy to forget and hard to find JNI problems
6+
ob_version = '2.4.0-relinker-SNAPSHOT'
7+
// ob_native_version = ob_version // Be careful to diverge here; easy to forget and hard to find JNI problems
8+
ob_native_version = '2.4.0-SNAPSHOT' // Be careful to diverge here; easy to forget and hard to find JNI problems
89
ob_expected_version = project.hasProperty('expectedVersion') ? project.property('expectedVersion') : 'UNDEFINED'
910

1011
isLinux = System.getProperty("os.name").contains("Linux")

objectbox-java/src/main/java/io/objectbox/BoxStore.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
@ThreadSafe
6060
public class BoxStore implements Closeable {
6161

62+
/** On Android used for native library loading. */
63+
@Nullable public static Object context;
64+
@Nullable public static Object relinker;
65+
/** Change so ReLinker will update native library when using workaround loading. */
66+
public static final String JNI_VERSION = "2.4.0";
67+
6268
private static final String VERSION = "2.4.0-2019-01-08";
6369
private static BoxStore defaultStore;
6470

@@ -186,6 +192,8 @@ public static boolean isObjectBrowserAvailable() {
186192
private final TxCallback failedReadTxAttemptCallback;
187193

188194
BoxStore(BoxStoreBuilder builder) {
195+
context = builder.context;
196+
relinker = builder.relinker;
189197
NativeLibraryLoader.ensureLoaded();
190198

191199
directory = builder.directory;

objectbox-java/src/main/java/io/objectbox/BoxStoreBuilder.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public class BoxStoreBuilder {
6666
/** BoxStore uses this */
6767
File directory;
6868

69+
/** On Android used for native library loading. */
70+
@Nullable Object context;
71+
@Nullable Object relinker;
72+
6973
/** Ignored by BoxStore */
7074
private File baseDirectory;
7175

@@ -174,6 +178,8 @@ public BoxStoreBuilder androidContext(Object context) {
174178
if (context == null) {
175179
throw new NullPointerException("Context may not be null");
176180
}
181+
this.context = getApplicationContext(context);
182+
177183
File baseDir = getAndroidBaseDir(context);
178184
if (!baseDir.exists()) {
179185
baseDir.mkdir();
@@ -189,6 +195,29 @@ public BoxStoreBuilder androidContext(Object context) {
189195
return this;
190196
}
191197

198+
private Object getApplicationContext(Object context) {
199+
try {
200+
return context.getClass().getMethod("getApplicationContext").invoke(context);
201+
} catch (ReflectiveOperationException e) {
202+
throw new RuntimeException("context must be a valid Android Context", e);
203+
}
204+
}
205+
206+
/**
207+
* Pass a custom ReLinkerInstance, for example {@code ReLinker.log(logger)} to use for loading the native library
208+
* on Android devices. Note that setting {@link #androidContext(Object)} is required for ReLinker to work.
209+
*/
210+
public BoxStoreBuilder androidReLinker(Object reLinkerInstance) {
211+
if (context == null) {
212+
throw new IllegalArgumentException("Set a Context using androidContext(context) first");
213+
}
214+
if (reLinkerInstance == null) {
215+
throw new NullPointerException("ReLinkerInstance may not be null");
216+
}
217+
this.relinker = reLinkerInstance;
218+
return this;
219+
}
220+
192221
static File getAndroidDbDir(Object context, @Nullable String dbName) {
193222
File baseDir = getAndroidBaseDir(context);
194223
return new File(baseDir, dbName(dbName));

objectbox-java/src/main/java/io/objectbox/internal/NativeLibraryLoader.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,21 @@
2525
import java.io.IOException;
2626
import java.io.InputStream;
2727
import java.io.OutputStream;
28+
import java.lang.reflect.Method;
2829
import java.net.URL;
2930
import java.net.URLConnection;
3031

32+
import io.objectbox.BoxStore;
33+
3134
/**
3235
* Separate class, so we can mock BoxStore.
3336
*/
3437
public class NativeLibraryLoader {
38+
39+
private static final String OBJECTBOX_JNI = "objectbox-jni";
40+
3541
static {
36-
String libname = "objectbox-jni";
42+
String libname = OBJECTBOX_JNI;
3743
String filename = libname + ".so";
3844
boolean isLinux = false;
3945
// For Android, os.name is also "Linux", so we need an extra check
@@ -68,11 +74,15 @@ public class NativeLibraryLoader {
6874
System.err.println("File not available: " + file.getAbsolutePath());
6975
}
7076
try {
71-
System.loadLibrary(libname);
77+
if (!android || !loadLibraryAndroid(libname)) {
78+
System.loadLibrary(libname);
79+
}
7280
} catch (UnsatisfiedLinkError e) {
7381
if (!android && isLinux) {
7482
// maybe is Android, but check failed: try loading Android lib
75-
System.loadLibrary("objectbox-jni");
83+
if (!loadLibraryAndroid(OBJECTBOX_JNI)) {
84+
System.loadLibrary(OBJECTBOX_JNI);
85+
}
7686
} else {
7787
throw e;
7888
}
@@ -113,6 +123,32 @@ private static void checkUnpackLib(String filename) {
113123
}
114124
}
115125

126+
@SuppressWarnings("BooleanMethodIsAlwaysInverted") // more readable
127+
private static boolean loadLibraryAndroid(String libname) {
128+
if (BoxStore.context == null) {
129+
return false;
130+
}
131+
132+
try {
133+
Class<?> context = Class.forName("android.content.Context");
134+
if (BoxStore.relinker == null) {
135+
// use default ReLinker
136+
Class<?> relinker = Class.forName("com.getkeepsafe.relinker.ReLinker");
137+
Method loadLibrary = relinker.getMethod("loadLibrary", context, String.class, String.class);
138+
loadLibrary.invoke(null, BoxStore.context, libname, BoxStore.JNI_VERSION);
139+
} else {
140+
// use custom ReLinkerInstance
141+
Method loadLibrary = BoxStore.relinker.getClass().getMethod("loadLibrary", context, String.class, String.class);
142+
loadLibrary.invoke(BoxStore.relinker, BoxStore.context, libname, BoxStore.JNI_VERSION);
143+
}
144+
} catch (ReflectiveOperationException e) {
145+
// note: do not catch Exception as it will swallow ReLinker exceptions useful for debugging
146+
return false;
147+
}
148+
149+
return true;
150+
}
151+
116152
public static void ensureLoaded() {
117153
}
118154
}

0 commit comments

Comments
 (0)