aws-amplify / aws-sdk-android

AWS SDK for Android. For more information, see our web site:
https://docs.amplify.aws
Other
1.02k stars 548 forks source link

Pinpoint Analytics causes crash - SQLiteReadOnlyDatabaseException: attempt to write a readonly database #3565

Open frogoscar opened 2 months ago

frogoscar commented 2 months ago

Describe the bug After integrating AWS Pinpoint Analytics into my Android app, I notice many crashes reported on my Google Play Console, as below :

Exception android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database - SQLITE_READONLY_DBMOVED (Sqlite code 1032 SQLITE_READONLY_DBMOVED), (OS error - 2:No such file or directory)
  at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount
  at android.database.sqlite.SQLiteConnection.executeForChangedRowCount (SQLiteConnection.java:875)
  at android.database.sqlite.SQLiteSession.executeForChangedRowCount (SQLiteSession.java:776)
  at android.database.sqlite.SQLiteStatement.executeUpdateDelete (SQLiteStatement.java:66)
  at android.database.sqlite.SQLiteDatabase.delete (SQLiteDatabase.java:1694)
  at com.amazonaws.mobileconnectors.pinpoint.internal.event.PinpointDBBase.delete (PinpointDBBase.java:206)
  at com.amazonaws.mobileconnectors.pinpoint.internal.event.PinpointDBUtil.deleteEvent (PinpointDBUtil.java:111)
  at com.amazonaws.mobileconnectors.pinpoint.internal.event.EventRecorder.processEvents (EventRecorder.java:330)
  at com.amazonaws.mobileconnectors.pinpoint.internal.event.EventRecorder$1.run (EventRecorder.java:256)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
  at java.lang.Thread.run (Thread.java:929)

This crash is encountered mostly when opening the Android app.

The log of this crash is likely the same to https://github.com/aws-amplify/aws-sdk-android/issues/2520 .

From the log, I can say the crash occurs when AWS Pinpoint calls method submitEvents() . Please look at my usage as below.

I firstly initialize AWS (in an Kotlin object class named AnalyticsUtils 's init() method) Pinpoint Analytics Client as below

object AnalyticsUtils {
    lateinit var pinpointAnalyticsClient: AnalyticsClient

    fun init(context: Context) {
        val appId = "xxxxxxx"
        val identityPoolId = "xxxxxxx"

        val configMap = mapOf(
            "PinpointAnalytics" to mapOf(
            "AppId" to appId,
            "Region" to "us-west-2"
            )
        )
        val pinpointConfig = PinpointConfiguration(
            context,
            CognitoCredentialsProvider(identityPoolId, Regions.US_WEST_2),
            AWSConfiguration(JSONObject(configMap))
        )
        pinpointAnalyticsClient = PinpointManager(pinpointConfig).analyticsClient
    }
}

Record an event as below

fun logEvent(eventString: String, params: HashMap<String, String>?) {
    if (eventString.isEmpty()) {
        Log.w(TAG, "Event name is empty, skip")
        return
    }

    if (this::firebaseAnalytics.isInitialized) {
        val event = pinpointAnalyticsClient.createEvent(eventString)
        params?.map { entry -> event.addAttribute(entry.key, entry.value) }
        pinpointAnalyticsClient.recordEvent(event)
    }
}

Submit all events recorded in local SQLite database when app is launched (in the onCreate() method of the inherited Application class) as below

override fun onCreate() {
    super.onCreate()

    // init Pinpoint Analytics Client
    AnalyticsUtils.init(this)

    // submit all Pinpoint events saved in local Sqlite database
    AnalyticsUtils.pinpointSubmitEvents()
}

pinpointSubmitEvents() method is defined in AnalyticsUtils.kt

fun pinpointSubmitEvents() {
    if (this::pinpointAnalyticsClient.isInitialized) {
        pinpointAnalyticsClient.submitEvents()
    }
}

Could you tell me if I use the Pinpoint SDK incorrectly, how can I fix this crash ? Or should I use the new Amplify SDK for Pinpoint Analytics instead ?

Thank you very much!

To Reproduce

The problem is that I can not reproduce this crash on my own physical Android device (which is OPPO K10x), it works fine. But when the Prod release is on Google Play, my users encounter crash, some users say that they encounter crash when opening my app.

Which AWS service(s) are affected? AWS Pinpoint

Expected behavior No crash.

Screenshots

AWS Pinpoint crash

Environment Information (please complete the following information):

Additional context Add any other context about the problem here.

frogoscar commented 2 months ago

Any kind help ? Thanks a lot~

joon-won commented 2 months ago

Hi @frogoscar , thank you for your patience, our team will look into the issue

vincetran commented 2 months ago

Hi @frogoscar, thanks for your patient. I started to look at this and have a question about how you're calling SubmitEvents. From the snippits you provided it looks like you're submitting Pinpoint events immediately after you initialize the Pinpoint client. So how I read your flow is:

App launch > Initialize Pinpoint > Submit local events > App has finished starting > Events are logged.

I don't know the full workings of the Pinpoint client yet but I'm wondering if you're attempting to submit the events too early and running into a race condition where on certain user's devices, the database client isn't fully initialized and thus ready for writes. The issue that you linked was explicitly found during unit testing so the scenario for encountering that issue is pretty different than the real-life scenario you're encountering.

Is there a reason you're trying to submit events so early/in the Application class? From documentation it looks like general best practice is to submit events at the end of the user's session. For cases of fatal recoveries, you can always try to submit events in some primary activity like your login, home, or similar activity.

frogoscar commented 2 months ago

Hi @frogoscar, thanks for your patient. I started to look at this and have a question about how you're calling SubmitEvents. From the snippits you provided it looks like you're submitting Pinpoint events immediately after you initialize the Pinpoint client. So how I read your flow is:

App launch > Initialize Pinpoint > Submit local events > App has finished starting > Events are logged.

I don't know the full workings of the Pinpoint client yet but I'm wondering if you're attempting to submit the events too early and running into a race condition where on certain user's devices, the database client isn't fully initialized and thus ready for writes. The issue that you linked was explicitly found during unit testing so the scenario for encountering that issue is pretty different than the real-life scenario you're encountering.

Is there a reason you're trying to submit events so early/in the Application class? From documentation it looks like general best practice is to submit events at the end of the user's session. For cases of fatal recoveries, you can always try to submit events in some primary activity like your login, home, or similar activity.

@vincetran @joon-won

Hi, thank you very much for your kind reply.

So maybe the reason is that I call submitEvents() too early (like vincetran says) ?

So if I remove the code that calls submitEvents() at Application 's onCreate(), and put submitEvents() in the code location in an Activity when the app switches from Foreground to Background (At that moment I think the database client is fully initialized and thus ready for writes), will it be ok ?

Thanks a lot!

vincetran commented 2 months ago

That sounds more reasonable! If you take a look at the docs, there's an example that demonstrates that exact usecase (albeit written in Java): https://docs.amplify.aws/android/sdk/analytics/events/#reporting-session-events

Basically, when the app enters foreground, you call startSession() and when it enters background, it calls both stopSession() and submitEvents().

Hopefully that fixes your problem but at a higher level suggestion, we do recommend you migrate over to use the Amplify SDK at your earliest convenience.

frogoscar commented 2 months ago

That sounds more reasonable! If you take a look at the docs, there's an example that demonstrates that exact usecase (albeit written in Java): https://docs.amplify.aws/android/sdk/analytics/events/#reporting-session-events

Basically, when the app enters foreground, you call startSession() and when it enters background, it calls both stopSession() and submitEvents().

Hopefully that fixes your problem but at a higher level suggestion, we do recommend you migrate over to use the Amplify SDK at your earliest convenience.

@vincetran Thanks a lot.

The calls of startSession() and stopSession() are not mandatory, right ?

I would like to use the new Amplify SDK for Android, of course.

But my Android app's minSdk is 21, while the latest Amplify SDK for Android (from version 2.0.0) requires Android minSdk >= 24).

I have some customs whose Android devices still use Android 5 (Android SDK 21) ~ Android 6 (Android SDK 23).

The latest version of Amplify SDK for Android that satisfies the condition of minSdk 21 is 1.38.8 (1.38.8 uses underlying version 2.73.0 of AWS SDK for Android ).

Currently I use version 2.75.0 of AWS SDK for Android.

In the future when I set my Android minSdk >= 24 , I will migrate to Amplify SDK for Android .