拒绝重复进程!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

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

相关推荐
用户091 小时前
Flutter构建速度深度优化指南
android·flutter·ios
PenguinLetsGo1 小时前
关于「幽灵调用」一事第三弹:完结?
android
雨白5 小时前
Android 多线程:理解 Handler 与 Looper 机制
android
sweetying8 小时前
30了,人生按部就班
android·程序员
用户2018792831678 小时前
Binder驱动缓冲区的工作机制答疑
android
真夜8 小时前
关于rngh手势与Slider组件手势与事件冲突解决问题记录
android·javascript·app
用户2018792831678 小时前
浅析Binder通信的三种调用方式
android
用户099 小时前
深入了解 Android 16KB内存页面
android·kotlin
火车叼位10 小时前
Android Studio与命令行Gradle表现不一致问题分析
android
前行的小黑炭12 小时前
【Android】 Context使用不当,存在内存泄漏,语言不生效等等
android·kotlin·app