diff --git a/CHANGELOG.md b/CHANGELOG.md index 15579ea551..6c8182922b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Mapbox welcomes participation and contributions from everyone. # 10.16.5 ## Features ✨ and improvements 🏁 +* Introduce better way (compared to v10.16.3) of dealing with `java.lang.UnsatisfiedLinkError` exception happening on the startup. ## Bug fixes 🐞 * Address crashes on certain Android devices by disabling the texture pool. diff --git a/buildSrc/src/main/kotlin/Project.kt b/buildSrc/src/main/kotlin/Project.kt index 8568252f74..e728b4da01 100644 --- a/buildSrc/src/main/kotlin/Project.kt +++ b/buildSrc/src/main/kotlin/Project.kt @@ -52,7 +52,6 @@ object Dependencies { const val androidxRecyclerView = "androidx.recyclerview:recyclerview:${Versions.androidxRecyclerView}" const val androidxCoreKtx = "androidx.core:core-ktx:${Versions.androidxCore}" const val androidxAnnotations = "androidx.annotation:annotation:${Versions.androidxAnnotation}" - const val androidxStartup = "androidx.startup:startup-runtime:${Versions.androidxStartup}" const val androidxInterpolators = "androidx.interpolator:interpolator:${Versions.androidxInterpolator}" const val androidxConstraintLayout = "androidx.constraintlayout:constraintlayout:${Versions.androidxConstraintLayout}" const val androidxEspresso = "androidx.test.espresso:espresso-core:${Versions.androidxEspresso}" @@ -120,7 +119,6 @@ object Versions { const val androidxCore = "1.6.0" // Latest version that supports compile SDK 30 const val androidxFragmentTesting = "1.3.6" // Latest version that supports compile SDK 30 const val androidxAnnotation = "1.1.0" - const val androidxStartup = "1.1.0" const val androidxAppcompat = "1.3.0" const val androidxTest = "1.4.0" const val androidxArchCoreTest = "2.1.0" diff --git a/sdk-base/api/PublicRelease/metalava.txt b/sdk-base/api/PublicRelease/metalava.txt index a6b4a44656..04f7b22116 100644 --- a/sdk-base/api/PublicRelease/metalava.txt +++ b/sdk-base/api/PublicRelease/metalava.txt @@ -22,21 +22,6 @@ package com.mapbox.maps { @kotlin.RequiresOptIn(level=kotlin.RequiresOptIn.Level, message="This API is experimental. It may be changed in the future without notice.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget, kotlin.annotation.AnnotationTarget}) public @interface MapboxExperimental { } - @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class MapboxInitializer implements androidx.startup.Initializer { - ctor public MapboxInitializer(); - method public Boolean create(android.content.Context context); - method public java.util.List>> dependencies(); - method @MainThread @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=MapboxInitializerException::class) public static void init(android.content.Context context) throws java.lang.Throwable; - field public static final com.mapbox.maps.MapboxInitializer.Companion Companion; - } - - public static final class MapboxInitializer.Companion { - method @MainThread @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=MapboxInitializerException::class) public void init(android.content.Context context) throws java.lang.Throwable; - } - - public final class MapboxInitializerKt { - } - public interface MapboxLifecycleObserver { method public void onDestroy(); method public void onLowMemory(); diff --git a/sdk-base/build.gradle.kts b/sdk-base/build.gradle.kts index a5e6d6f26e..e8419f308f 100644 --- a/sdk-base/build.gradle.kts +++ b/sdk-base/build.gradle.kts @@ -50,7 +50,6 @@ dependencies { api(Dependencies.mapboxGlNative) api(Dependencies.mapboxCoreCommon) } - implementation(Dependencies.androidxStartup) testImplementation(Dependencies.junit) testImplementation(Dependencies.mockk) diff --git a/sdk-base/src/main/AndroidManifest.xml b/sdk-base/src/main/AndroidManifest.xml index 7b21bf9318..87b0f1f494 100644 --- a/sdk-base/src/main/AndroidManifest.xml +++ b/sdk-base/src/main/AndroidManifest.xml @@ -1,25 +1 @@ - - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/sdk-base/src/main/java/com/mapbox/maps/MapboxInitializer.kt b/sdk-base/src/main/java/com/mapbox/maps/MapboxInitializer.kt deleted file mode 100644 index e64fa72fcb..0000000000 --- a/sdk-base/src/main/java/com/mapbox/maps/MapboxInitializer.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.mapbox.maps - -import android.content.Context -import android.os.Build -import android.os.Looper -import android.os.SystemClock -import android.util.Log -import androidx.annotation.MainThread -import androidx.annotation.RestrictTo -import androidx.startup.AppInitializer -import androidx.startup.Initializer -import com.mapbox.maps.loader.MapboxMapsInitializer -import java.io.File - -/** - * Unified Mapbox SDKs initializer class that catches exceptions to avoid crashing during app - * process start. - * - * Most of the crashes reported are related to [UnsatisfiedLinkError] - * (https://github.com/mapbox/mapbox-maps-android/issues/1109). - * - * This solution is valid only when using Mapbox SDK for Android and no other Mapbox SDK (e.g. - * Navigation, Search,...). - * - * In order to use this solution no other Mapbox SDK initializer should run (i.e. - * [MapboxMapsInitializer] or [com.mapbox.common.MapboxSDKCommonInitializer]) during process start. - * See the `sdk/src/main/AndroidManifest.xml` file. - */ -@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) -class MapboxInitializer : Initializer { - - /** - * This code is run exactly one time on process startup. - */ - override fun create(context: Context): Boolean { - initializerCalledElapsedTime = SystemClock.elapsedRealtime() - // try-catch to avoid terminating the whole process - try { - init(context) - } catch (e: Throwable) { - // Catch the exception, store it and log instead of propagating it. The app process will be - // able to continue its start. The Mapbox SDK is not loaded and can't be used until `init` - // function in the companion object is called. `MapView`, `MapSurface` and `Snapshotter` will - // call the init by themselves. - initializerFailure = e - Log.w(TAG, "Exception occurred when initializing Mapbox: ${e.message}") - } - return true - } - - /** - * We do not need any dependencies here. - */ - override fun dependencies(): MutableList>> { - return mutableListOf() - } - - /** - * Companion object for [MapboxInitializer] that holds some static state to keep track of - * initialization state and also provides [init] that does the SDK native stack initialization. - */ - companion object { - private const val TAG = "MapboxInitializer" - private var successfulInit = false - private var currentAttempt = 0 - - /** - * Elapsed time since boot when [MapboxInitializer.create] was called or null if it was not - * called. - */ - internal var initializerCalledElapsedTime: Long? = null - private set - internal var initializerFailure: Throwable? = null - private set - - /** - * This function initializes Maps SDK native stack if it has not yet been done successfully. - * - * It can be called multiple times. If the native stack was already initialized successfully - * then this is a no-op. - * - * If initialization process throws an exception we catch it and enhanced it with system - * information (see [MapboxInitializerException]). - */ - @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) - @MainThread - @JvmStatic - @Throws(MapboxInitializerException::class) - public fun init(context: Context) { - if (successfulInit) { - return - } - if (Looper.myLooper() != Looper.getMainLooper()) { - throw RuntimeException("Mapbox must be called from main thread only!") - } - // we operate with application context to avoid memory leaks with Activity / View context - val applicationContext = context.applicationContext - Log.i(TAG, "MapboxInitializer started initialization, attempt ${++currentAttempt}") - runCatchingEnhanced(applicationContext) { - // it is enough to call only MapboxMapsInitializer as it has dependency on MapboxSDKCommonInitializer - AppInitializer.getInstance(applicationContext) - .initializeComponent(MapboxMapsInitializer::class.java) - Log.i(TAG, "MapboxInitializer initialized Maps successfully") - } - successfulInit = true - } - - /** - * Runs the given [function]. If [function] throws a [Throwable] then a - * [MapboxInitializerException] is thrown which contains extra information in it. - */ - @Throws(MapboxInitializerException::class) - private inline fun runCatchingEnhanced(context: Context, function: () -> Unit) { - try { - function() - } catch (t: Throwable) { - // if we got to this point there we are most likely hitting UnsatisfiedLinkError, re-throw an exception - throw MapboxInitializerException(currentAttempt, context, t) - } - } - } -} - -internal class MapboxInitializerException(attempt: Int, context: Context, t: Throwable) : - Throwable(gatherSystemInfo(attempt, context, t), t) - -private fun gatherSystemInfo(attempt: Int, context: Context, t: Throwable): String { - val isInstantApp = runCatching { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - context.packageManager?.isInstantApp - } else { - null - } - } - val nativeLibs = runCatching { - context.packageManager?.getApplicationInfo(context.packageName, 0)?.let { ai -> - File(ai.nativeLibraryDir).list()?.joinToString() ?: "" - } - } - val initializerCalledMsg = MapboxInitializer.initializerCalledElapsedTime?.let { - // Log how long since the first time we tried to initialize during app process start. Or "null" if initializer was never called - "initializer called ${SystemClock.elapsedRealtime() - it }ms ago" - } ?: "initializer not called" - - return "Failed to initialize: Attempt=$attempt," + - " exception=[${t.javaClass.simpleName}]," + - " $initializerCalledMsg," + - " initializerFailure=[${MapboxInitializer.initializerFailure?.javaClass?.simpleName}]," + - // Most likely initializerFailure is MapboxInitializerException so try to find its cause - " initializerFailure.cause=[${MapboxInitializer.initializerFailure?.cause?.javaClass?.simpleName}]," + - " extractedNativeLibs=[${nativeLibs.getOrNull()}]," + - " isInstantApp=[${isInstantApp.getOrNull()}]," -} \ No newline at end of file diff --git a/sdk/src/main/java/com/mapbox/maps/MapSurface.kt b/sdk/src/main/java/com/mapbox/maps/MapSurface.kt index a5f2b01163..de920b21b4 100644 --- a/sdk/src/main/java/com/mapbox/maps/MapSurface.kt +++ b/sdk/src/main/java/com/mapbox/maps/MapSurface.kt @@ -46,7 +46,6 @@ class MapSurface : MapPluginProviderDelegate, MapControllable { surface: Surface, mapInitOptions: MapInitOptions = MapInitOptions(context) // could use strong ref here as MapInitOptions have strong ref in any case ) { - MapboxInitializer.init(context) this.context = context this.surface = surface this.mapInitOptions = mapInitOptions diff --git a/sdk/src/main/java/com/mapbox/maps/MapView.kt b/sdk/src/main/java/com/mapbox/maps/MapView.kt index e686b1f3f5..fd447f63ef 100644 --- a/sdk/src/main/java/com/mapbox/maps/MapView.kt +++ b/sdk/src/main/java/com/mapbox/maps/MapView.kt @@ -92,7 +92,6 @@ open class MapView : FrameLayout, MapPluginProviderDelegate, MapControllable { defStyleRes: Int, initOptions: MapInitOptions?, ) : super(context, attrs, defStyleAttr, defStyleRes) { - MapboxInitializer.init(context) val resolvedMapInitOptions = if (attrs != null) { parseTypedArray(context, attrs) } else { diff --git a/sdk/src/main/java/com/mapbox/maps/Snapshotter.kt b/sdk/src/main/java/com/mapbox/maps/Snapshotter.kt index 41d79c7228..c9b9257ffc 100644 --- a/sdk/src/main/java/com/mapbox/maps/Snapshotter.kt +++ b/sdk/src/main/java/com/mapbox/maps/Snapshotter.kt @@ -46,7 +46,6 @@ open class Snapshotter { options: MapSnapshotOptions, overlayOptions: SnapshotOverlayOptions = SnapshotOverlayOptions() ) { - MapboxInitializer.init(context) this.context = WeakReference(context) mapSnapshotOptions = options snapshotOverlayOptions = overlayOptions