啟動頁是 App 給用戶的第一印象,核心需求:展示品牌圖、預載關鍵資源、固定停留時間後跳轉主頁。本文對比 Handler.postDelayed 與 CountDownTimer 兩種主流實現,分析生命週期坑點、記憶體洩漏防範與最佳實踐。
public class SplashActivity extends AppCompatActivity {
private static final long DELAY_MS = 3000;
private Handler handler = new Handler(Looper.getMainLooper());
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 全屏無標題
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_splash);
handler.postDelayed(() -> {
// 檢查 Activity 是否仍有效(防止已銷毀後回調)
if (!isFinishing() && !isDestroyed()) {
startActivity(new Intent(this, MainActivity.class));
finish(); // 關鍵:防止返回鍵回到啟動頁
}
}, DELAY_MS);
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null); // 防洩漏
}
}
優點:代碼極簡、單次延遲執行語義清晰。
缺點:無倒計時顯示、無法中途取消邏輯複雜。
public class SplashActivity extends AppCompatActivity {
private TextView tvCountdown;
private MyCountDownTimer timer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
tvCountdown = findViewById(R.id.tv_countdown);
// 總時長 3000ms,每 1000ms 回調一次
timer = new MyCountDownTimer(3000, 1000);
timer.start();
}
private class MyCountDownTimer extends CountDownTimer {
public MyCountDownTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onTick(long millisUntilFinished) {
// UI 線程安全更新
tvCountdown.setText("倒計時 " + (millisUntilFinished / 1000) + " 秒");
}
@Override
public void onFinish() {
if (!isFinishing() && !isDestroyed()) {
tvCountdown.setText("正在進入...");
startActivity(new Intent(SplashActivity.this, MainActivity.class));
finish();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (timer != null) timer.cancel(); // 必須取消,否則洩漏
}
}
優點:內建倒計時邏輯、自動處理間隔回調、cancel() 一鍵清理。
缺點:內部仍用 Handler,本質相同。
| 問題 | 後果 | 解決 |
|---|---|---|
不檢查 isFinishing() | 用戶快速返回/切換,Activity 已銷毀卻仍跳轉 | 回調前必加 if (!isFinishing() && !isDestroyed()) |
不調用 finish() | 主頁按返回鍵會回到啟動頁,體驗極差 | 跳轉後立即 finish() |
| Handler/Timer 未清理 | 記憶體洩漏,延遲任務持有 Activity 引用 | onDestroy 中 removeCallbacksAndMessages / cancel() |
| 主題閃爍 | 啟動瞬間顯示默認主題再切換 | Manifest 設置 android:theme="@style/SplashTheme" |
| 初始化耗時過長阻塞主線程 | ANR、啟動頁白屏 | 耗時操作放入 WorkManager/協程/線程池異步執行 |
<!-- res/values/styles.xml -->
<style name="SplashTheme" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/splash_bg</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<layer-list xmlns:android="https://schemas.android.com/apk/res/android">
<item android:drawable="@color/brand_primary"/>
<item>
<bitmap android:gravity="center" android:src="@drawable/ic_logo"/>
</item>
</layer-list>
<activity
android:name=".SplashActivity"
android:theme="@style/SplashTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
效果:系統啟動 Activity 前即顯示圖片,零代碼、零延遲渲染,完美解決白屏。
@AndroidEntryPoint
class SplashActivity : AppCompatActivity() {
private lateinit var binding: ActivitySplashBinding
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySplashBinding.inflate(layoutInflater)
setContentView(binding.root)
scope.launch {
// 並行預載:數據庫、配置、Token 刷新
val initDeferred = async(Dispatchers.IO) { Repository.initialize() }
val delayDeferred = async { delay(2000) } // 最小停留 2s
delayDeferred.await() // 等待最小時間
initDeferred.await() // 等待初始化完成
if (!isFinishing && !isDestroyed) {
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
}
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
}
優勢:結構化並發、自動生命週期綁定、異常傳播、易測試。
Handler.postDelayed + finish() + 生命週期檢查CountDownTimer + cancel()windowBackground(最關鍵優化)finish(),銷毀必清理回調,回調必檢查存活