Android MainActivity 的 onCreate() 在安装后首次启动时被调用两次

问题描述 投票:0回答:2

问题:

我遇到了一个不寻常的问题,即我的应用程序启动器活动 (MainActivity) 的 onCreate() 方法被调用两次,但仅在我安装应用程序后首次启动该应用程序时调用(来自 Google Play 内部应用程序共享链接)或在设备上安装签名的 apk 文件)。此行为会导致同时创建 MainActivity 的多个实例。

描述:

我已经彻底检查了我的代码,并且可以确认没有意外调用 startActivity 或任何会导致 onCreate() 被多次调用的显式代码。 我检查了this,但这些错误似乎已经过时了

观察结果:

  • 此问题仅在安装后首次启动应用程序时出现。
  • 应用程序的后续启动不会出现此行为; onCreate() 按预期仅被调用一次。
  • 问题似乎与应用程序的首次启动有关 安装后。
  • 我正在 Android 13 版本设备中进行测试

代码:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="xyz">

    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

    <!-- Required to maintain app compatibility. -->
    <uses-permission
        android:name="android.permission.READ_EXTERNAL_STORAGE"
        android:maxSdkVersion="32" />

    <application
        android:name=".framework.presentation.BaseApplication"
        android:allowBackup="false"
        android:fullBackupOnly="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <meta-data
            android:name="firebase_crashlytics_collection_enabled"
            android:value="${enableCrashReporting}" />

        <activity
            android:name="xyz.framework.presentation.MainActivity"
            android:windowSoftInputMode="adjustResize"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name="xyz.framework.presentation.main.RecipeActivity"
            android:windowSoftInputMode="adjustResize"
            android:exported="true" />

        <activity
            android:name="com.facebook.FacebookActivity"
            android:configChanges="keyboard|keyboardHidden|screenLayout|screenSize|orientation"
            android:theme="@style/com_facebook_activity_theme"
            android:exported="false" />
        <activity
            android:name="com.facebook.CustomTabMainActivity"
            android:exported="false" />
        <activity
            android:name="androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity"
            android:theme="@android:style/Theme"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>
        <activity
            android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
            android:theme="@android:style/Theme"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>
        <activity
            android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyFloatingActivity"
            android:theme="@android:style/Theme.Dialog"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
            </intent-filter>
        </activity>


    </application>

</manifest>

基础应用程序

@FlowPreview
@ExperimentalCoroutinesApi
@HiltAndroidApp
open class BaseApplication : Application()

主要活动

@AndroidEntryPoint
class MainActivity : AppCompatActivity(), UIController {

@Inject
lateinit var editor: SharedPreferences.Editor

@Inject
lateinit var sharedPreferences: SharedPreferences

@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory

private val viewModel: SplashViewModel by viewModels {
    viewModelFactory
}

private val signInLauncher = registerForActivityResult(
    FirebaseAuthUIActivityResultContract()
) { res ->
    this.onSignInResult(res)
}

@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    Toast.makeText(this, "oncreate called", Toast.LENGTH_SHORT).show()
    setContent {
        SplashProvider()
    }
    viewModel.hasSyncBeenExecuted()
        .observe(this) { hasSyncBeenExecuted ->

            if (hasSyncBeenExecuted) {
                startActivity(Intent(this, RecipeActivity::class.java))
                // Use lifecycleScope for the coroutine
                lifecycleScope.launch {
                    delay(2000) // Adjust the delay time as needed
                    finish()
                }
            }
        }
    if (sharedPreferences?.getString(
            PreferenceKeys.USER_UID,
            null
        ) != null && FirebaseAuth.getInstance().currentUser != null
    ) {
        viewModel.syncCacheWithNetwork()
    } else {
        createSignInIntent()
    }
}

private fun createSignInIntent() {
    // [START auth_fui_create_intent]
    // Choose authentication providers
    val providers = arrayListOf(
        AuthUI.IdpConfig.EmailBuilder().build(),
        AuthUI.IdpConfig.GoogleBuilder().build()
    )
    Toast.makeText(this, "createSignInIntent called", Toast.LENGTH_SHORT).show()
    // Create and launch sign-in intent
    val signInIntent = AuthUI.getInstance()
        .createSignInIntentBuilder()
        .setAvailableProviders(providers)
        .setTheme(R.style.LoginTheme)
        .setLogo(R.drawable.handshake) // Set logo drawable           
        .build()
    signInLauncher.launch(signInIntent)
    // [END auth_fui_create_intent]
}

override fun hideSoftKeyboard() {
    if (currentFocus != null) {
        val inputMethodManager = getSystemService(
            Context.INPUT_METHOD_SERVICE
        ) as InputMethodManager
        inputMethodManager
            .hideSoftInputFromWindow(currentFocus!!.windowToken, 0)
    }
}

private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) {
    val response = result.idpResponse
    if (result.resultCode == AppCompatActivity.RESULT_OK) {
        // Successfully signed in
        val user = FirebaseAuth.getInstance().currentUser
        // ...
        editor?.putString(PreferenceKeys.USER_UID, user?.uid)
        editor?.apply()
        viewModel.syncCacheWithNetwork()
    } else if (response != null) {
        Toast.makeText(
            this,
            response.error?.errorCode.toString().plus(response.error?.message),
            Toast.LENGTH_LONG
        ).show()
    } else {
        finish()
    }
}

}

SplashProvider

@Composable
fun SplashProvider(
) {
    val images = listOf(R.drawable.splash1, R.drawable.splash2, R.drawable.splash3)
    val imageIndex = Random.nextInt(images.size)

    Splash(images[imageIndex])
}

@Composable
fun Splash(img: Int) {

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Image(
            contentDescription = "Recipe",
            painter = painterResource(img),
            modifier = Modifier.fillMaxSize(),
            contentScale = ContentScale.Crop
        )
        Column(
            modifier = Modifier
                .wrapContentSize()
                .clip(CircleShape)
                .background(Color.Black.copy(alpha = 0.6f))
                .padding(16.dp),
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            CircularProgressIndicator(
                modifier = Modifier.size(50.dp),
                strokeWidth = 4.dp,
                color = Color.White
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(
                text = "Loading...",
                color = Color.White,
                fontSize = 16.sp
            )
        }

    }
}
android android-activity google-play android-manifest internal-app-sharing
2个回答
0
投票

您的MainActivity

启动模式
未在您的
AndroidManifest.xml
中指定。默认启动模式是“
standard
”,允许创建任意数量的实例。您可能想尝试将其设置为“
singleTop
”,以确保如果新实例已位于堆栈顶部,则不会创建新实例:

用于测试:

<activity
    android:name="xyz.framework.presentation.MainActivity"
    android:launchMode="singleTop"
    android:windowSoftInputMode="adjustResize"
    android:exported="true">

此外,在您的

MainActivity
中,启动
RecipeActivity
,然后在 2000 毫秒的延迟后,在
finish()
上调用
MainActivity
。如果系统或用户在该时间范围内触发重新创建(例如旋转或向后导航),则可能会允许重新创建
MainActivity

尝试在启动

finish()
后立即调用
RecipeActivity
,或者更好的是,使用
FLAG_ACTIVITY_NO_HISTORY
标志
确保
MainActivity
不会保留在活动堆栈中:

Intent intent = new Intent(this, RecipeActivity.class.java);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
startActivity(intent);
finish();

为了帮助您调试此问题,请使用时间戳和其他相关数据记录对

onCreate()
的每次调用,并尝试追溯为什么它被调用两次:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Log.d("MainActivity", "onCreate called at " + System.currentTimeMillis());
    // Rest of the code
}

0
投票

我也遇到了同样的问题,这似乎是 Android 中的一个错误。虽然这个解决方案确实在一定程度上有所帮助,但它会导致意外的行为,有时 onCreate() 内的代码根本没有执行。所以,我想出了一个替代解决方法:

class SingleOnCreateExecution(context: Context, savedInstanceState: Bundle?, executeCode: () -> Unit) {

init {
    val preferences = context.getSharedPreferences("executeOnce", Context.MODE_PRIVATE)
    val launchCount = preferences.getInt("launchCount", 0)

    when (launchCount) {
        1 -> if (savedInstanceState == null) executeCode() // Second time run only if no saved state
        else -> executeCode() // Runs when launchCount is 0 or after the second time, regardless of saved state
    }

    preferences.edit().putInt("launchCount", launchCount + 1).apply()
}
}

然后像这样运行它:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    SingleExecution(this, savedInstanceState) {
        Log.i("myTag", "onCreate")
        // ... your onCreate() code here
    }
}

这确保仅在第二次启动时检查“savedInstanceState == null”。因此,onCreate() 大部分时间都正常运行,最大限度地减少中断。

如果您已经在应用程序中跟踪应用程序的 launchCount,则可以使用现有的 SharedPreferences。

© www.soinside.com 2019 - 2024. All rights reserved.