Skip to content

[Performance] Refactor getKeyPair() to run off the main thread #24

@nbd-boss

Description

@nbd-boss

Description

The getKeyPair() method in MainActivity.kt currently performs potentially long-running operations on the UI thread, including:

  • Disk I/O
    PreferenceManager.getDefaultSharedPreferences(this) involves reading from an XML file, which is a synchronous disk operation.

  • CPU-intensive work
    When no keys are found in SharedPreferences, KeyPairGenerator.generateKeyPair() is invoked to generate a 2048-bit RSA key pair, which is computationally expensive.

Executing these operations on the UI thread—especially during cold start or first launch—can lead to dropped frames (jank) and may even trigger Application Not Responding (ANR) errors on lower-end devices or under heavy system load, negatively impacting user experience.


Problem with the Current Implementation

The comment // crashes on non-main thread correctly highlights a limitation in the current design.

The root cause is that getKeyPair() implicitly depends on this (the Activity context), tightly coupling it to the Activity lifecycle. This makes the method unsafe to call from a background thread and effectively forces it to run on the UI thread, even though it performs blocking work.


Proposed Solution

A two-step refactoring is recommended:

1. Decouple the Context Dependency

Modify the method signature to explicitly accept a Context parameter instead of implicitly using this.

Before:

private fun getKeyPair(): KeyPair

After:

private fun getKeyPair(context: Context): KeyPair

Inside the method, replace all usages of this with the provided context.
This makes the function self-contained, easier to test, and safe to call from background threads.


2. Move the Call to a Background Thread

At the call site (e.g., in onCreate), execute the method on a background thread using coroutines and switch back to the main thread with the result.

Example Implementation:

import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

// ...

lifecycleScope.launch {
    try {
        val keyPairResult = withContext(Dispatchers.IO) {
            // Use applicationContext for thread safety
            getKeyPair(applicationContext)
        }
        // Back on the main thread
        this@YourActivity.keyPair = keyPairResult
    } catch (e: Exception) {
        Log.e(TAG, "Failed to get key pair", e)
    }
}

Benefits of This Change

  • Improved User Experience
    Prevents UI thread blocking, ensuring smoother rendering and responsiveness.

  • Increased App Stability
    Eliminates potential ANR risks caused by disk I/O and cryptographic operations on the main thread.

  • Better Code Architecture
    Decouples business logic from the Activity, resulting in cleaner, more maintainable, and more robust code.


Hope this suggestion is helpful for improving the performance and stability of the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions