问题:
我遇到了一个不寻常的问题,即我的应用程序启动器活动 (MainActivity) 的 onCreate() 方法被调用两次,但仅在我安装应用程序后首次启动该应用程序时调用(来自 Google Play 内部应用程序共享链接)或在设备上安装签名的 apk 文件)。此行为会导致同时创建 MainActivity 的多个实例。
描述:
我已经彻底检查了我的代码,并且可以确认没有意外调用 startActivity 或任何会导致 onCreate() 被多次调用的显式代码。 我检查了this,但这些错误似乎已经过时了
观察结果:
代码:
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
)
}
}
}
您的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
}
我也遇到了同样的问题,这似乎是 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。