FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

    • DroidPlugin

    • RePlugin

      • RePlugin集成Fresco
      • RePlugin强制退出App
        • 一、前提
        • 二、问题
        • 三、分析
        • 四、解析方案
          • 方式一:插件通知宿主"自杀"
          • 方式二:插件控制宿主"自杀"
      • RePlugin集成AndroidAutoSize
      • RePlugin集成ARouter
      • RePluginX - 兼容AndroidX并加入新特性开发纪要
  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

  • iOS逆向

  • Flutter开发

  • 移动端
  • Android插件化
  • RePlugin
GitLqr
2021-01-05
目录

RePlugin强制退出App

欢迎关注微信公众号:[FSA全栈行动 👋]

# RePlugin强制退出

需求:插件中按“退出”按钮,就完全退出整个app,包括宿主。

# 一、前提

以下所有的理论,都是基于宿主跟插件使用同个进程这个大前提下,即不需要常驻进程。

apply plugin: 'replugin-host-gradle'
repluginHostConfig {
    useAppCompat = true
    persistentEnable = false // 设置为“不需要常驻进程”
}

"不需要常驻进程" 的目的是为了减少整个app的内存开销。

# 二、问题

在插件中使用System.exit(0)或android.os.Process.killProcess(android.os.Process.myPid())强杀app后,会重启宿主app。

流程:宿主MainActivity --安装启动--> 插件MainActivity --点击"退出"--> 触发主动强杀进程

# 三、分析

其实这跟Replugin是没有任何关系,从Android5.0开始(也可能是4.x),Android不允许在代码中主动强杀前台进程。

底层原理请看:《Android5.1.1源码 - App服务进程被杀后自动重启的原因》 (opens new window)

在当前案例中,共有2个Activity,插件MainActivity中触发了finish()与System.exit(0),这里要特别注意,仅仅只是关闭了插件MainActivity,而宿主MainActivity什么都没做,此时的app还是处于前台进程,紧接着System.exit(0)把整个app进程杀死,这时便会触发Android守卫前台进程的规则,将刚刚"go died"的app重新拉起来。

# 四、解析方案

知道原理之后,这个问题也就迎刃而解了。解决方案就是让app不再是前台进程后,再执行System.exit(0)即可。换句话说就是把app中所有的【Activity】和【Service】全部关闭,再执行System.exit(0)。

注意:Service跟Activity一样,也能让app处于前台进程,但如果是bindService()启动的话(与app同生共死),则不会有这种问题,所以要特别小心那些使用startService()启动的服务,下面只写Activity的解决办法,Service请自行处理。

当然了,如果能确保宿主在启动插件后,app进程不再有除插件以外的Activity存在的话,则不需要如下操作,以当前例子来说明,即宿主MainActivity通过 RePlugin.startActivity() 启动了插件MainActivity,同时通过 finish() 将宿主MainActivity自己关闭。

流程:宿主MainActivity --安装启动插件,并finish()--> 插件MainActivity --点击"退出"--> 触发主动强杀进程

如果无法确保上述条件,或是业务逻辑需要保留宿主的Activity一直存活,则需要继续往下看。收集所有Activity并逐个finish()的做法有多种,比如:使用Application#registerActivityLifecycleCallbacks ,或者是反射Application ,为了方便这里使用的是反射Application,AppUtil.kt代码如下 :

// AppUtil.kt
/**
 * 杀死进程
 */
fun killApp(application: Application) {
    getActivitiesByApplication(application)?.let {
        for (activity in it) {
            activity.finish()
        }
        exitProcess(0)
    }
}

/**
 * 获取当前App中所有的Activity
 */
fun getActivitiesByApplication(application: Application): List<Activity> {
    val list: MutableList<Activity> = ArrayList()
    try {
        val applicationClass = Application::class.java
        val mLoadedApkField: Field = applicationClass.getDeclaredField("mLoadedApk")
        mLoadedApkField.isAccessible = true
        val mLoadedApk: Any = mLoadedApkField.get(application)
        val mLoadedApkClass: Class<*> = mLoadedApk.javaClass
        val mActivityThreadField: Field = mLoadedApkClass.getDeclaredField("mActivityThread")
        mActivityThreadField.isAccessible = true
        val mActivityThread: Any = mActivityThreadField.get(mLoadedApk)
        val mActivityThreadClass: Class<*> = mActivityThread.javaClass
        val mActivitiesField: Field = mActivityThreadClass.getDeclaredField("mActivities")
        mActivitiesField.isAccessible = true
        val mActivities: Any = mActivitiesField.get(mActivityThread)
        // 注意这里一定写成Map,低版本这里用的是HashMap,高版本用的是ArrayMap
        if (mActivities is Map<*, *>) {
            val arrayMap = mActivities as Map<Any, Any>
            for ((_, value) in arrayMap) {
                val activityClientRecordClass: Class<*> = value.javaClass
                val activityField: Field =
                    activityClientRecordClass.getDeclaredField("activity")
                activityField.isAccessible = true
                val o: Any = activityField.get(value)
                list!!.add(o as Activity)
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
    return list
}

# 方式一:插件通知宿主"自杀"

好处:宿主才是真正的app,能直接管理所有真实的四大组件,代码可扩展性强。

坏处:插件工程中可能需要判断是否是单品的情况。

# 1)宿主代码

在宿主中接收来自插件的"自杀"消息,关闭所有的acitivty后"自杀":

// MyApplication.kt
class MyApplication : RePluginApplication() {

    override fun onCreate() {
        super.onCreate()
        registerKillProcessReceiver()
    }
    
    private fun registerKillProcessReceiver() {
        LocalBroadcastManager.getInstance(this).registerReceiver(object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                killApp(this@MyApplication)
            }

        }, IntentFilter("killprocess"))
    }
}

# 2)插件代码

在插件中把"自杀"的消息通知给宿主,然后静静等待死亡:

btn_exit_system.setOnClickListener {
    LocalBroadcastManager.getInstance(this).sendBroadcast(Intent("killprocess"))
}

# 方式二:插件控制宿主"自杀"

既然宿主和插件是同个进程,也就意味着插件能获取到宿主Application,直接在插件中干掉宿主就好了。

好处:单品中不需要修改任何代码,也一样能用。

坏处:反射Api的方式可能存在风险;作为插件可能无法掌控整个app。

# 1)插件代码

btn_exit_process.setOnClickListener {
    killApp(application)
}
#插件化#RePlugin
RePlugin集成Fresco
RePlugin集成AndroidAutoSize

← RePlugin集成Fresco RePlugin集成AndroidAutoSize→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×