6.7.0 - Alpha12 - 修复 Shizuku 用户服务进程未能正常结束导致进程堆积的问题 (issue #474)
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$data": {
|
"$data": {
|
||||||
"v6.7.0": {
|
"v6.7.0": {
|
||||||
"released_date": "2025/12/23",
|
"released_date": "2025/12/25",
|
||||||
"feature": [
|
"feature": [
|
||||||
"插件中心功能, 支持插件的安装/卸载/更新等操作 (入口: 主页抽屉按钮)",
|
"插件中心功能, 支持插件的安装/卸载/更新等操作 (入口: 主页抽屉按钮)",
|
||||||
"cvt 模块, 用于数据单位转换 (参阅 项目文档 > [单位转换](https://docs.autojs6.com/#/cvt))",
|
"cvt 模块, 用于数据单位转换 (参阅 项目文档 > [单位转换](https://docs.autojs6.com/#/cvt))",
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
"项目配置文件中构建版本号或构建时间出现较大数字时可能导致应用崩溃的问题",
|
"项目配置文件中构建版本号或构建时间出现较大数字时可能导致应用崩溃的问题",
|
||||||
"频繁获取或重建 ImageReader 时可能因缓冲区暂无可用帧导致应用崩溃的问题",
|
"频繁获取或重建 ImageReader 时可能因缓冲区暂无可用帧导致应用崩溃的问题",
|
||||||
"输入事件观察器 InputEventObserver 可能导致应用启动时明显卡顿的问题",
|
"输入事件观察器 InputEventObserver 可能导致应用启动时明显卡顿的问题",
|
||||||
|
"Shizuku 用户服务进程未能正常结束导致进程堆积的问题 _[`issue #474`](http://issues.autojs6.com/474)_",
|
||||||
"打包应用无法正常使用 Paddle OCR 与 Rapid OCR 功能的问题",
|
"打包应用无法正常使用 Paddle OCR 与 Rapid OCR 功能的问题",
|
||||||
"版本历史页面部分系统因字体差别导致统计数据显示不完整的问题",
|
"版本历史页面部分系统因字体差别导致统计数据显示不完整的问题",
|
||||||
"部分设备无法正常初始化 MLKit Google OCR 的问题 (试修) _[`issue #8`](http://issues.autojs6.com/8#issuecomment-3117061768)_",
|
"部分设备无法正常初始化 MLKit Google OCR 的问题 (试修) _[`issue #8`](http://issues.autojs6.com/8#issuecomment-3117061768)_",
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ package org.autojs.autojs.core.shizuku
|
|||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Process
|
||||||
import android.os.RemoteException
|
import android.os.RemoteException
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
import org.autojs.autojs.runtime.api.AbstractShell
|
import org.autojs.autojs.runtime.api.AbstractShell
|
||||||
import org.autojs.autojs.runtime.api.ProcessShell
|
import org.autojs.autojs.runtime.api.ProcessShell
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class UserService : IUserService.Stub {
|
class UserService : IUserService.Stub {
|
||||||
|
|
||||||
@@ -49,8 +51,22 @@ class UserService : IUserService.Stub {
|
|||||||
*/
|
*/
|
||||||
override fun destroy() {
|
override fun destroy() {
|
||||||
Log.i("UserService", "destroy")
|
Log.i("UserService", "destroy")
|
||||||
|
|
||||||
|
// Ensure the user service process terminates when Shizuku server requests destroy.
|
||||||
|
// zh-CN: 确保 Shizuku server 请求 destroy 时, user service 进程能够真正退出.
|
||||||
|
runCatching {
|
||||||
|
Process.killProcess(Process.myPid())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallback to exit the process if killProcess doesn't stop it immediately.
|
||||||
|
// zh-CN: 如果 killProcess 未能立刻终止, 则使用 exitProcess 作为兜底退出.
|
||||||
|
runCatching {
|
||||||
|
exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit method defined by user.
|
||||||
|
// zh-CN: 用户定义的退出方法.
|
||||||
override fun exit() {
|
override fun exit() {
|
||||||
destroy()
|
destroy()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.DeadObjectException
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import org.autojs.autojs.AbstractAutoJs.Companion.isInrt
|
import org.autojs.autojs.AbstractAutoJs.Companion.isInrt
|
||||||
@@ -17,6 +18,7 @@ import org.autojs.autojs.util.App.SHIZUKU
|
|||||||
import org.autojs.autojs.util.ViewUtils
|
import org.autojs.autojs.util.ViewUtils
|
||||||
import org.autojs.autojs6.R
|
import org.autojs.autojs6.R
|
||||||
import rikka.shizuku.Shizuku
|
import rikka.shizuku.Shizuku
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import org.autojs.autojs.runtime.api.AbstractShell.Result as ShellResult
|
import org.autojs.autojs.runtime.api.AbstractShell.Result as ShellResult
|
||||||
@@ -37,18 +39,27 @@ object WrappedShizuku {
|
|||||||
}
|
}
|
||||||
private var mHasBinder = false
|
private var mHasBinder = false
|
||||||
|
|
||||||
|
private val mServiceWaiters = CopyOnWriteArrayList<CountDownLatch>()
|
||||||
|
|
||||||
private val mUserServiceConnection: ServiceConnection = object : ServiceConnection {
|
private val mUserServiceConnection: ServiceConnection = object : ServiceConnection {
|
||||||
override fun onServiceConnected(componentName: ComponentName, binder: IBinder?) {
|
override fun onServiceConnected(componentName: ComponentName, binder: IBinder?) {
|
||||||
Log.d(TAG, "onServiceConnected: ${componentName.className}")
|
Log.d(TAG, "onServiceConnected: ${componentName.className}")
|
||||||
if (binder?.pingBinder() == true) {
|
if (binder?.pingBinder() == true) {
|
||||||
service = IUserService.Stub.asInterface(binder)
|
service = IUserService.Stub.asInterface(binder)
|
||||||
|
|
||||||
|
// Wake all waiters when binder is ready.
|
||||||
|
// zh-CN: 当 binder 就绪时, 唤醒所有等待者.
|
||||||
|
mServiceWaiters.forEach { it.countDown() }
|
||||||
|
mServiceWaiters.clear()
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "invalid binder for $componentName received")
|
Log.w(TAG, "invalid binder for $componentName received")
|
||||||
|
service = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onServiceDisconnected(componentName: ComponentName) {
|
override fun onServiceDisconnected(componentName: ComponentName) {
|
||||||
Log.d(TAG, "onServiceDisconnected: ${componentName.className}")
|
Log.d(TAG, "onServiceDisconnected: ${componentName.className}")
|
||||||
|
service = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +98,9 @@ object WrappedShizuku {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun bindUserServiceIfNeeded() {
|
internal fun bindUserServiceIfNeeded() {
|
||||||
|
if (service?.asBinder()?.pingBinder() == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (hasPermission()) {
|
if (hasPermission()) {
|
||||||
bindUserService()
|
bindUserService()
|
||||||
}
|
}
|
||||||
@@ -148,17 +162,41 @@ object WrappedShizuku {
|
|||||||
|
|
||||||
@ScriptInterface
|
@ScriptInterface
|
||||||
fun execCommand(context: Context, cmd: String): ShellResult {
|
fun execCommand(context: Context, cmd: String): ShellResult {
|
||||||
|
return execCommandWithAutoReconnect(context, cmd, allowRetry = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun execCommandWithAutoReconnect(context: Context, cmd: String, allowRetry: Boolean): ShellResult {
|
||||||
if (service == null && hasPermission()) {
|
if (service == null && hasPermission()) {
|
||||||
|
onCreate()
|
||||||
|
bindUserServiceIfNeeded()
|
||||||
initializeShizukuServiceAndWait(5000L)
|
initializeShizukuServiceAndWait(5000L)
|
||||||
}
|
}
|
||||||
|
|
||||||
val service = service ?: when {
|
val service = service ?: when {
|
||||||
!hasPermission() -> R.string.error_no_permission_to_access_shizuku
|
!hasPermission() -> R.string.error_no_permission_to_access_shizuku
|
||||||
!isRunning() -> R.string.error_shizuku_service_may_be_not_running
|
!isRunning() -> R.string.error_shizuku_service_may_be_not_running
|
||||||
else -> R.string.error_unable_to_use_shizuku_service
|
else -> R.string.error_unable_to_use_shizuku_service
|
||||||
}.let { throw IllegalStateException(context.getString(it)) }
|
}.let { throw IllegalStateException(context.getString(it)) }
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
ShellResult.fromJson(service.execCommand(cmd.replace(Regex("^\\s*adb\\s+shell\\s+", RegexOption.IGNORE_CASE), "")))
|
ShellResult.fromJson(
|
||||||
|
service.execCommand(
|
||||||
|
cmd.replace(Regex("^\\s*adb\\s+shell\\s+", RegexOption.IGNORE_CASE), "")
|
||||||
|
)
|
||||||
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
|
// Reconnect and retry once when binder is dead.
|
||||||
|
// zh-CN: 当 binder 已死亡时, 自动重连并重试一次.
|
||||||
|
if (allowRetry && e is DeadObjectException) {
|
||||||
|
this.service = null
|
||||||
|
runCatching {
|
||||||
|
onCreate()
|
||||||
|
bindUserServiceIfNeeded()
|
||||||
|
initializeShizukuServiceAndWait(5000L)
|
||||||
|
}
|
||||||
|
return execCommandWithAutoReconnect(context, cmd, allowRetry = false)
|
||||||
|
}
|
||||||
|
|
||||||
ShellResult().apply {
|
ShellResult().apply {
|
||||||
code = 1
|
code = 1
|
||||||
error = e.message ?: when {
|
error = e.message ?: when {
|
||||||
@@ -191,17 +229,25 @@ object WrappedShizuku {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun initializeShizukuServiceAndWait(@Suppress("SameParameterValue") timeout: Long) {
|
private fun initializeShizukuServiceAndWait(@Suppress("SameParameterValue") timeout: Long) {
|
||||||
onCreate()
|
if (service?.asBinder()?.pingBinder() == true) {
|
||||||
bindUserServiceIfNeeded()
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val latch = CountDownLatch(1)
|
val latch = CountDownLatch(1)
|
||||||
val tmpConnection = object : ServiceConnection {
|
mServiceWaiters.add(latch)
|
||||||
override fun onServiceConnected(name: ComponentName, binder: IBinder) = latch.countDown()
|
|
||||||
override fun onServiceDisconnected(name: ComponentName) = Unit
|
// Re-check after registering waiter to avoid missing a fast onServiceConnected().
|
||||||
|
// zh-CN: 注册等待者后再次检查, 避免 onServiceConnected() 很快到来导致错过唤醒.
|
||||||
|
if (service?.asBinder()?.pingBinder() == true) {
|
||||||
|
mServiceWaiters.remove(latch)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
Shizuku.bindUserService(mUserServiceArgs, tmpConnection)
|
|
||||||
|
runCatching {
|
||||||
latch.await(timeout, TimeUnit.MILLISECONDS)
|
latch.await(timeout, TimeUnit.MILLISECONDS)
|
||||||
Shizuku.unbindUserService(mUserServiceArgs, tmpConnection, true)
|
}.also {
|
||||||
|
mServiceWaiters.remove(latch)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -155,7 +155,13 @@ class MainActivity : BaseActivity(), DelegateHost, HostActivity {
|
|||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
WrappedShizuku.bindUserServiceIfNeeded()
|
// @Hint by SuperMonster003 on Dec 24, 2025.
|
||||||
|
// ! Avoid binding Shizuku user service on app start.
|
||||||
|
// ! It may spawn root user-service processes repeatedly during IDE "Run" (force-stop + relaunch).
|
||||||
|
// ! zh-CN:
|
||||||
|
// ! 避免在应用启动时绑定 Shizuku user service.
|
||||||
|
// ! IDE "Run" (force-stop + relaunch) 期间可能反复拉起 root user-service 进程.
|
||||||
|
// # WrappedShizuku.bindUserServiceIfNeeded()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recreateIfNeeded() {
|
private fun recreateIfNeeded() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#Tue Dec 23 06:37:24 CST 2025
|
#Wed Dec 24 16:10:20 CST 2025
|
||||||
BUILD_TIME=1766443044399
|
BUILD_TIME=1766563820979
|
||||||
COMPILE_SDK_VERSION=36
|
COMPILE_SDK_VERSION=36
|
||||||
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
||||||
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
||||||
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
|
|||||||
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
|
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
|
||||||
TARGET_SDK_VERSION=36
|
TARGET_SDK_VERSION=36
|
||||||
TARGET_SDK_VERSION_INRT=29
|
TARGET_SDK_VERSION_INRT=29
|
||||||
VERSION_BUILD=3544
|
VERSION_BUILD=3546
|
||||||
VERSION_NAME=6.7.0 Alpha12
|
VERSION_NAME=6.7.0 Alpha12
|
||||||
VSCODE_EXT_REQUIRED_VERSION=1.0.8
|
VSCODE_EXT_REQUIRED_VERSION=1.0.8
|
||||||
|
|||||||
Reference in New Issue
Block a user