本文共 4171 字,大约阅读时间需要 13 分钟。
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不允许在代码中主动强杀前台进程。
底层原理请看:
在当前案例中,共有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{ val list: MutableList = 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 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.ktclass 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)}
转载地址:https://linqiarui.blog.csdn.net/article/details/112728426 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!