You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android安装后首次启动MainActivity的onCreate()为何被调用两次?

问题

应用的启动页Activity(MainActivity)的onCreate()方法会被调用两次,但仅在安装后首次启动应用时出现(从Google Play内部应用分享链接安装或安装签名APK文件到设备)。此行为会导致同时创建多个MainActivity实例。

描述

已全面检查代码,确认不存在意外调用startActivity或任何会导致onCreate()被多次调用的显式代码。查看过相关旧bug报告,但该bug似乎已过时。

观察结果

  • 仅在安装后首次启动应用时出现此问题
  • 后续启动应用时不会出现该行为,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>

BaseApplication

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

MainActivity

@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
            )
        }

    }
}

解决方案

1. 限制Activity启动模式

在AndroidManifest.xml中给MainActivity添加launchMode="singleTop",确保同类型实例不会重复创建:

<activity
    android:name="xyz.framework.presentation.MainActivity"
    android:windowSoftInputMode="adjustResize"
    android:exported="true"
    android:launchMode="singleTop">
    <!-- intent-filter 保持不变 -->
</activity>

singleTop模式下,当Activity处于任务栈顶部时,系统会复用现有实例而非创建新的,适配启动页的场景需求。

2. 拦截重复启动的Intent

在MainActivity的onCreate()方法开头添加判断,若当前实例不是任务栈根且是从桌面启动的,直接销毁避免重复:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // 拦截重复启动的实例
    if (!isTaskRoot) {
        val intent = intent
        if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN == intent.action) {
            finish()
            return
        }
    }
    // 原有业务代码
}

安装后首次启动时,系统可能因安装完成的广播和用户点击图标发送两个启动Intent,这段代码可以过滤掉重复的实例。

3. 优化页面跳转逻辑

移除startActivity后的延迟finish()逻辑,直接跳转后销毁当前Activity,避免实例堆积:

if (hasSyncBeenExecuted) {
    startActivity(Intent(this, RecipeActivity::class.java))
    finish()
}

延迟销毁没有实际意义,反而会导致原Activity未销毁时新实例被创建。

4. 调整Auth启动时机

将Firebase Auth的启动逻辑移到onResume()中,确保Activity完全初始化后再执行,避免干扰系统启动流程:

private var isFirstResume = true

override fun onResume() {
    super.onResume()
    if (isFirstResume) {
        isFirstResume = false
        if (sharedPreferences?.getString(PreferenceKeys.USER_UID, null) != null && FirebaseAuth.getInstance().currentUser != null) {
            viewModel.syncCacheWithNetwork()
        } else {
            createSignInIntent()
        }
    }
}

同时删除onCreate()中对应的判断代码,减少Activity创建阶段的异步操作。

5. 移除测试组件

正式包中移除androidx.test.core.app相关的Activity,测试组件可能干扰系统的启动逻辑,建议通过构建变体(productFlavors)或构建类型(buildType)在正式包中排除这些代码。


内容的提问来源于stack exchange,提问作者Android Developer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 09:55:54