拒绝重复进程!Android开发者必须掌握的单实例设计全攻略

"面对系统多任务机制带来的多实例问题,本文提供从标准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
)

五、验证方法

  1. 通过 ADB 模拟多启动:
sql 复制代码
adb shell am start -n your.package.name/.MainActivity
adb shell am start -a android.intent.action.VIEW -d "your://deep/link"
  1. 使用开发者选项中的「不保留活动」进行压力测试
  2. 监控 Activity 生命周期:
go 复制代码
adb shell dumpsys activity activities | grep your.package.name

根据实际业务复杂度选择方案层级,建议优先采用基础方案 + 增强方案组合实现,既能覆盖大部分场景,又不会引入过度设计。

相关推荐
鸿蒙布道师2 小时前
鸿蒙NEXT开发动画案例2
android·ios·华为·harmonyos·鸿蒙系统·arkui·huawei
androidwork2 小时前
Kotlin Android工程Mock数据方法总结
android·开发语言·kotlin
xiangxiongfly9154 小时前
Android setContentView()源码分析
android·setcontentview
人间有清欢6 小时前
Android开发补充内容
android·okhttp·rxjava·retrofit·hilt·jetpack compose
人间有清欢6 小时前
Android开发报错解决
android
每次的天空8 小时前
Android学习总结之kotlin协程面试篇
android·学习·kotlin
每次的天空10 小时前
Android学习总结之Binder篇
android·学习·binder
峥嵘life10 小时前
Android 有线网开发调试总结
android
是店小二呀11 小时前
【算法-链表】链表操作技巧:常见算法
android·c++·算法·链表
zhifanxu12 小时前
Kotlin 遍历
android·开发语言·kotlin