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

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

相关推荐
顾林海1 小时前
揭开Android ContentProvider的神秘面纱:从原理到实战
android
_一条咸鱼_3 小时前
Android Retrofit 请求执行模块执行原理深入源码分析(三)
android
tangweiguo030519873 小时前
Android自定义View全解析:从基础绘制到复杂交互,实战多种自定义View实现
android·交互
LuXi_foryou3 小时前
Cannot resolve symbol ‘view‘ Androidstudio报错解决办法
android·java·android studio
QING6183 小时前
Android中Binder通信的优势以及与传统IPC的差异
android·kotlin·app
前行的小黑炭3 小时前
Android Compose是如何使用什么架构,多个Activity?还是Fragment?compose的ui又是如何卸载和挂载的呢?
android·kotlin
QING6183 小时前
一文带你吃透Android View绘制流程与原理详解
android·kotlin·app
pengyu3 小时前
系统化掌握Flutter组件之Dismissible
android·flutter·dart
_一条咸鱼_3 小时前
Android Retrofit 框架的接口代理与调用模块源码深度剖析(二)
android
奋斗的小鹰5 小时前
Android中使用Glide加载图片闪烁问题
android·glide