Xposed 强制停止应用

  • 基于 Xposed 强制终止应用进程

  • 资料来源:

    https://github.com/M66B/XPrivacyLua
    https://blog.csdn.net/qidabing/article/details/77895304

  • 更新

    1
    2022.06.24 初始

导语

基于 xposed 强制终止应用进程.

需求来源是修改应用配置后,直接终止应用不必要再手动重启.

思路 & 代码实现来自 XprivacyLua

方案

强制关闭应用,google 后发现有几种方法

  • killBackgroundProcesses
  • forceStopPackage

似乎都是隐藏 api ,在 Android 9 以后限制私有 api 调用后,目前无法使用.

xprivacylua 提供了另一个思路, hook 系统进程,通过 ContentProvider call 方法调用,在系统进程里调用 forceStopPackageAsUser 干掉应用.

实施

终止程序最终执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// force stop app
@Throws(Throwable::class)
private fun forceStop(context: Context, packageName: String, userid: Int) {
// Access activity manager as system user
val id = Binder.clearCallingIdentity()
try {
// public void forceStopPackageAsUser(String packageName, int userId)
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val mForceStop: Method = am.javaClass.getMethod(
"forceStopPackageAsUser",
String::class.java,
Int::class.javaPrimitiveType
)
mForceStop.invoke(am, packageName, userid)
} finally {
Binder.restoreCallingIdentity(id)
}
}

这里需要的是 packageNameuserid

  • packageName 是通过 PackageManager 获取
  • userid 要麻烦一点要通过应用的 uid 转换.
    • android 4.2 以前不支持多用户,4.2 以后支持多用户.
    • 应用在内部虽然有多用户,但只有一个 uid,在不同的用户下,通过 uid 和用户 id 合成一个新的 uid,以保证在每个用户下能够区分.
    • 反推是除以 100000 得到多用户下该用户的 userid
1
2
3
4
5
6
7
8
9
10
11
private const val PER_USER_RANGE = 100000
private fun getUserId(uid: Int): Int {
return try {
// public static final int getUserId(int uid)
val method =
UserHandle::class.java.getDeclaredMethod("getUserId", Int::class.javaPrimitiveType)
method.invoke(null, uid) as Int
} catch (ex: Throwable) {
return uid / PER_USER_RANGE
}
}

hook 的入口是

1
"com.android.providers.settings"

最终具体的类是 com.android.providers.settings.SettingsProvider 的 call 方法,随后调用我们注入的 makeCall 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fun hook(lpparam: XC_LoadPackage.LoadPackageParam) {
// https://android.googlesource.com/platform/frameworks/base/+/master/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
val clsSet = Class.forName(
"com.android.providers.settings.SettingsProvider",
false,
lpparam.classLoader
)
// Bundle call(String method, String arg, Bundle extras)
val mCall: Method = clsSet.getMethod(
"call",
String::class.java,
String::class.java,
Bundle::class.java
)

XposedBridge.hookMethod(mCall, object : XC_MethodHook() {
@Throws(Throwable::class)
override fun beforeHookedMethod(param: MethodHookParam) {
makeCall(param)
}
})
}

makeCall 获取 method 方法名 arg 参数 extras 额外的 Bundle

  • 总之是不能破坏原有的系统调用,否则相当危险.
  • xprivacy 是使用 method 区别调用方,这里也相同.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private fun makeCall(param: XC_MethodHook.MethodHookParam) {
try {
val method = param.args[0] as String?
val arg = param.args[1] as String?
val extras = param.args[2] as Bundle?

if ("DeepSleep" == method) { // if call form DeepSleep
try {
val mGetContext = param.thisObject.javaClass.getMethod("getContext")
val context: Context =
mGetContext.invoke(param.thisObject) as Context

XpUtil.log("$method,$arg,$extras")

param.result = XProvider.call(context, arg, extras) // call XProvider
} catch (ex: IllegalArgumentException) {
XpUtil.log("Error: " + ex.message)
param.throwable = ex
} catch (ex: Throwable) {
XpUtil.log(Log.getStackTraceString(ex))
param.result = null
}
}
} catch (ex: Throwable) {
XpUtil.log(Log.getStackTraceString(ex))
}
}

模块真正调用

  • 严重警告,因为是系统进程内运行,执行是无条件的,必须对终止的程序加以判断.
  • 绝对不能出现干掉系统程序.
1
2
3
4
5
6
7
8
9
10
11
12
13
fun getURI(): Uri {  
return Settings.System.CONTENT_URI
}

fun killapp(packageName:String,uid:Int){
val args = Bundle()
args.putString("packageName", packageName)
args.putInt("uid", uid)

val uri = getURI()
val contentResolver = BasicApp.context.contentResolver
contentResolver.call(uri, "DeepSleep", XProviderMethods.forceStop, args)
}