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

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

相关推荐
雨白3 小时前
Jetpack系列(三):Room数据库——从增删改查到数据库平滑升级
android·android jetpack
花王江不语7 小时前
android studio 配置硬件加速 haxm
android·ide·android studio
江太翁8 小时前
mediapipe流水线分析 三
android·mediapipe
与火星的孩子对话9 小时前
Unity进阶课程【六】Android、ios、Pad 终端设备打包局域网IP调试、USB调试、性能检测、控制台打印日志等、C#
android·unity·ios·c#·ip
tmacfrank10 小时前
Android 网络全栈攻略(四)—— TCPIP 协议族与 HTTPS 协议
android·网络·https
fundroid11 小时前
Kotlin 协程:Channel 与 Flow 深度对比及 Channel 使用指南
android·kotlin·协程
草字11 小时前
cocos 打包安卓
android
DeBuggggggg12 小时前
centos 7.6安装mysql8
android
浩浩测试一下13 小时前
渗透信息收集- Web应用漏洞与指纹信息收集以及情报收集
android·前端·安全·web安全·网络安全·安全架构
移动开发者1号14 小时前
深入理解原子类与CAS无锁编程:原理、实战与优化
android·kotlin