"面对系统多任务机制带来的多实例问题,本文提供从标准API到系统级hack的渐进式解决方案,帮你构建坚如磐石的单实例应用!"
针对 Android 应用的单实例需求,这里提供分层解决方案:
一、基础方案(推荐优先实现)
1. 在入口 Activity 声明单任务栈
xml
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:taskAffinity="你的包名" <!-- 关键:强制绑定到应用专属任务栈 -->
android:clearTaskOnLaunch="true"/> <!-- 可选:每次启动清除非根Activity -->
2. 全局实例检测(Application 级)
kotlin
class MyApp : Application() {
private var isRunning = false
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (activity is MainActivity) {
if (isRunning) {
activity.finish()
return
}
isRunning = true
}
}
override fun onActivityDestroyed(activity: Activity) {
if (activity is MainActivity) {
isRunning = false
}
}
// 其他生命周期方法留空实现
})
}
}
二、增强方案(应对特殊启动方式)
1. 防止多实例穿透
kotlin
abstract class SingleInstanceActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
if (!isTaskRoot) { // 检测是否处于任务栈底部
if (intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
// 处理从桌面图标重复点击启动
finish()
startActivity(Intent.makeMainActivity(componentName))
return
}
finish()
return
}
super.onCreate(savedInstanceState)
}
}
2. 强制前台唤醒
kotlin
fun bringToFrontIfRunning(context: Context) {
val activityManager = context.getSystemService(ACTIVITY_SERVICE) as ActivityManager
val tasks = activityManager.getRunningTasks(100)
tasks.firstOrNull { it.baseActivity?.packageName == context.packageName }?.let {
val intent = Intent(context, MainActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
}
三、终极方案(跨进程锁)
适用于需要绝对单实例的场景(需注意性能损耗)
1. 文件锁实现
kotlin
private const val LOCK_FILE = "single_instance.lock"
fun acquireAppLock(context: Context): Boolean {
return try {
val lockFile = File(context.filesDir, LOCK_FILE)
val channel = RandomAccessFile(lockFile, "rw").channel
val lock = channel.tryLock()
lock != null && lock.isValid
} catch (e: Exception) {
false
}
}
2. 结合 IPC 通信
kotlin
// 在 Application 中
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
// 通过 binder 判断是否已有实例
}
override fun onServiceDisconnected(name: ComponentName?) {}
}
fun checkExistingInstance() {
val bindResult = bindService(
Intent(this, InstanceCheckService::class.java),
serviceConnection,
Context.BIND_ABOVE_CLIENT
)
if (!bindResult) {
// 无已有实例
}
}
四、特别场景处理
1. 多窗口模式适配
xml
<activity android:name=".MainActivity"
android:resizeableActivity="false" <!-- 禁用分屏 -->
android:supportsPictureInPicture="false"/> <!-- 禁用画中画 -->
2. 通知栏点击处理
ini
val pendingIntent = PendingIntent.getActivity(
context,
0,
Intent(context, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
},
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
五、验证方法
- 通过 ADB 模拟多启动:
sql
adb shell am start -n your.package.name/.MainActivity
adb shell am start -a android.intent.action.VIEW -d "your://deep/link"
- 使用开发者选项中的「不保留活动」进行压力测试
- 监控 Activity 生命周期:
go
adb shell dumpsys activity activities | grep your.package.name
根据实际业务复杂度选择方案层级,建议优先采用基础方案 + 增强方案组合实现,既能覆盖大部分场景,又不会引入过度设计。