manager: Small increase in app acquisition speed
This commit is contained in:
@@ -7,26 +7,35 @@ import android.content.pm.PackageInfo
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.supervisorScope
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import com.sukisu.ultra.Natives
|
import com.sukisu.ultra.Natives
|
||||||
import com.sukisu.ultra.ksuApp
|
import com.sukisu.ultra.ksuApp
|
||||||
import com.sukisu.ultra.ui.util.HanziToPinyin
|
import com.sukisu.ultra.ui.util.HanziToPinyin
|
||||||
import java.text.Collator
|
import java.text.Collator
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
import com.dergoogler.mmrl.platform.Platform
|
import com.dergoogler.mmrl.platform.Platform
|
||||||
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
|
import com.dergoogler.mmrl.platform.TIMEOUT_MILLIS
|
||||||
import com.sukisu.ultra.ui.webui.getInstalledPackagesAll
|
import com.sukisu.ultra.ui.webui.getInstalledPackagesAll
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import androidx.core.content.edit
|
import androidx.core.content.edit
|
||||||
|
import kotlinx.coroutines.asCoroutineDispatcher
|
||||||
|
|
||||||
// 应用分类
|
// 应用分类
|
||||||
enum class AppCategory(val displayNameRes: Int, val persistKey: String) {
|
enum class AppCategory(val displayNameRes: Int, val persistKey: String) {
|
||||||
@@ -65,6 +74,7 @@ enum class SortType(val displayNameRes: Int, val persistKey: String) {
|
|||||||
*/
|
*/
|
||||||
class SuperUserViewModel : ViewModel() {
|
class SuperUserViewModel : ViewModel() {
|
||||||
val isPlatformAlive get() = Platform.isAlive
|
val isPlatformAlive get() = Platform.isAlive
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "SuperUserViewModel"
|
private const val TAG = "SuperUserViewModel"
|
||||||
var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
var apps by mutableStateOf<List<AppInfo>>(emptyList())
|
||||||
@@ -72,6 +82,10 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
private const val KEY_SHOW_SYSTEM_APPS = "show_system_apps"
|
private const val KEY_SHOW_SYSTEM_APPS = "show_system_apps"
|
||||||
private const val KEY_SELECTED_CATEGORY = "selected_category"
|
private const val KEY_SELECTED_CATEGORY = "selected_category"
|
||||||
private const val KEY_CURRENT_SORT_TYPE = "current_sort_type"
|
private const val KEY_CURRENT_SORT_TYPE = "current_sort_type"
|
||||||
|
private const val CORE_POOL_SIZE = 4
|
||||||
|
private const val MAX_POOL_SIZE = 8
|
||||||
|
private const val KEEP_ALIVE_TIME = 60L
|
||||||
|
private const val BATCH_SIZE = 20
|
||||||
}
|
}
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -100,6 +114,23 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val appProcessingThreadPool = ThreadPoolExecutor(
|
||||||
|
CORE_POOL_SIZE,
|
||||||
|
MAX_POOL_SIZE,
|
||||||
|
KEEP_ALIVE_TIME,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
LinkedBlockingQueue()
|
||||||
|
) { runnable ->
|
||||||
|
Thread(runnable, "AppProcessing-${System.currentTimeMillis()}").apply {
|
||||||
|
isDaemon = true
|
||||||
|
priority = Thread.NORM_PRIORITY
|
||||||
|
}
|
||||||
|
}.asCoroutineDispatcher()
|
||||||
|
|
||||||
|
private val appListMutex = Mutex()
|
||||||
|
|
||||||
|
private val configChangeListeners = mutableSetOf<(String) -> Unit>()
|
||||||
|
|
||||||
private val prefs: SharedPreferences = ksuApp.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
private val prefs: SharedPreferences = ksuApp.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
var search by mutableStateOf("")
|
var search by mutableStateOf("")
|
||||||
@@ -121,6 +152,12 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
var selectedApps by mutableStateOf<Set<String>>(emptySet())
|
var selectedApps by mutableStateOf<Set<String>>(emptySet())
|
||||||
internal set
|
internal set
|
||||||
|
|
||||||
|
// 加载进度状态
|
||||||
|
var loadingProgress by mutableFloatStateOf(0f)
|
||||||
|
private set
|
||||||
|
var loadingMessage by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从SharedPreferences加载显示系统应用设置
|
* 从SharedPreferences加载显示系统应用设置
|
||||||
*/
|
*/
|
||||||
@@ -253,19 +290,14 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
val profile = Natives.getAppProfile(packageName, it.uid)
|
val profile = Natives.getAppProfile(packageName, it.uid)
|
||||||
val updatedProfile = profile.copy(allowSu = allowSu)
|
val updatedProfile = profile.copy(allowSu = allowSu)
|
||||||
if (Natives.setAppProfile(updatedProfile)) {
|
if (Natives.setAppProfile(updatedProfile)) {
|
||||||
apps = apps.map { app ->
|
updateAppProfileLocally(packageName, updatedProfile)
|
||||||
if (app.packageName == packageName) {
|
notifyConfigChange(packageName)
|
||||||
app.copy(profile = updatedProfile)
|
|
||||||
} else {
|
|
||||||
app
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clearSelection()
|
clearSelection()
|
||||||
showBatchActions = false // 批量操作完成后退出批量模式
|
showBatchActions = false
|
||||||
fetchAppList() // 刷新列表以显示最新状态
|
refreshAppConfigurations()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量更新权限和umount模块设置
|
// 批量更新权限和umount模块设置
|
||||||
@@ -280,6 +312,21 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
nonRootUseDefault = false
|
nonRootUseDefault = false
|
||||||
)
|
)
|
||||||
if (Natives.setAppProfile(updatedProfile)) {
|
if (Natives.setAppProfile(updatedProfile)) {
|
||||||
|
updateAppProfileLocally(packageName, updatedProfile)
|
||||||
|
notifyConfigChange(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clearSelection()
|
||||||
|
showBatchActions = false
|
||||||
|
refreshAppConfigurations()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新本地应用配置
|
||||||
|
fun updateAppProfileLocally(packageName: String, updatedProfile: Natives.Profile) {
|
||||||
|
appListMutex.tryLock().let { locked ->
|
||||||
|
if (locked) {
|
||||||
|
try {
|
||||||
apps = apps.map { app ->
|
apps = apps.map { app ->
|
||||||
if (app.packageName == packageName) {
|
if (app.packageName == packageName) {
|
||||||
app.copy(profile = updatedProfile)
|
app.copy(profile = updatedProfile)
|
||||||
@@ -287,27 +334,67 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
app
|
app
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
appListMutex.unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clearSelection()
|
|
||||||
showBatchActions = false // 批量操作完成后退出批量模式
|
|
||||||
fetchAppList() // 刷新列表以显示最新状态
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新本地应用配置
|
private fun notifyConfigChange(packageName: String) {
|
||||||
fun updateAppProfileLocally(packageName: String, updatedProfile: Natives.Profile) {
|
configChangeListeners.forEach { listener ->
|
||||||
apps = apps.map { app ->
|
try {
|
||||||
if (app.packageName == packageName) {
|
listener(packageName)
|
||||||
app.copy(profile = updatedProfile)
|
} catch (e: Exception) {
|
||||||
} else {
|
Log.e(TAG, "Error notifying config change for $packageName", e)
|
||||||
app
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新应用配置状态
|
||||||
|
*/
|
||||||
|
suspend fun refreshAppConfigurations() {
|
||||||
|
withContext(appProcessingThreadPool) {
|
||||||
|
supervisorScope {
|
||||||
|
val currentApps = apps.toList()
|
||||||
|
val batches = currentApps.chunked(BATCH_SIZE)
|
||||||
|
|
||||||
|
loadingProgress = 0f
|
||||||
|
|
||||||
|
val updatedApps = batches.mapIndexed { batchIndex, batch ->
|
||||||
|
async {
|
||||||
|
val batchResult = batch.map { app ->
|
||||||
|
try {
|
||||||
|
val updatedProfile = Natives.getAppProfile(app.packageName, app.uid)
|
||||||
|
app.copy(profile = updatedProfile)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error refreshing profile for ${app.packageName}", e)
|
||||||
|
app
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val progress = (batchIndex + 1).toFloat() / batches.size
|
||||||
|
loadingProgress = progress
|
||||||
|
|
||||||
|
batchResult
|
||||||
|
}
|
||||||
|
}.awaitAll().flatten()
|
||||||
|
|
||||||
|
appListMutex.withLock {
|
||||||
|
apps = updatedApps
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingProgress = 1f
|
||||||
|
|
||||||
|
Log.i(TAG, "Refreshed configurations for ${updatedApps.size} apps")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchAppList() {
|
suspend fun fetchAppList() {
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
|
loadingProgress = 0f
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
withTimeoutOrNull(TIMEOUT_MILLIS) {
|
withTimeoutOrNull(TIMEOUT_MILLIS) {
|
||||||
@@ -318,21 +405,87 @@ class SuperUserViewModel : ViewModel() {
|
|||||||
val pm = ksuApp.packageManager
|
val pm = ksuApp.packageManager
|
||||||
val start = SystemClock.elapsedRealtime()
|
val start = SystemClock.elapsedRealtime()
|
||||||
|
|
||||||
val packages = Platform.getInstalledPackagesAll {
|
try {
|
||||||
Log.e(TAG, "getInstalledPackagesAll:", it)
|
val packages = Platform.getInstalledPackagesAll {
|
||||||
Toast.makeText(ksuApp, "Something went wrong, check logs", Toast.LENGTH_SHORT).show()
|
Log.e(TAG, "getInstalledPackagesAll:", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingProgress = 0.3f
|
||||||
|
|
||||||
|
val filteredPackages = packages.filter { it.packageName != ksuApp.packageName }
|
||||||
|
|
||||||
|
withContext(appProcessingThreadPool) {
|
||||||
|
supervisorScope {
|
||||||
|
val batches = filteredPackages.chunked(BATCH_SIZE)
|
||||||
|
|
||||||
|
val processedApps = batches.mapIndexed { batchIndex, batch ->
|
||||||
|
async {
|
||||||
|
val batchResult = batch.mapNotNull { packageInfo ->
|
||||||
|
try {
|
||||||
|
val appInfo = packageInfo.applicationInfo!!
|
||||||
|
val uid = appInfo.uid
|
||||||
|
|
||||||
|
val labelDeferred = async {
|
||||||
|
appInfo.loadLabel(pm).toString()
|
||||||
|
}
|
||||||
|
val profileDeferred = async {
|
||||||
|
Natives.getAppProfile(packageInfo.packageName, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
val label = labelDeferred.await()
|
||||||
|
val profile = profileDeferred.await()
|
||||||
|
|
||||||
|
AppInfo(
|
||||||
|
label = label,
|
||||||
|
packageInfo = packageInfo,
|
||||||
|
profile = profile,
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(
|
||||||
|
TAG,
|
||||||
|
"Error processing app ${packageInfo.packageName}",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val progress = 0.3f + (batchIndex + 1).toFloat() / batches.size * 0.6f
|
||||||
|
loadingProgress = progress
|
||||||
|
|
||||||
|
batchResult
|
||||||
|
}
|
||||||
|
}.awaitAll().flatten()
|
||||||
|
|
||||||
|
appListMutex.withLock {
|
||||||
|
apps = processedApps
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingProgress = 1f
|
||||||
|
|
||||||
|
val elapsed = SystemClock.elapsedRealtime() - start
|
||||||
|
Log.i(TAG, "Loaded ${processedApps.size} apps in ${elapsed}ms using concurrent processing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error fetching app list", e)
|
||||||
|
} finally {
|
||||||
|
isRefreshing = false
|
||||||
|
loadingProgress = 0f
|
||||||
|
loadingMessage = ""
|
||||||
}
|
}
|
||||||
apps = packages.map {
|
}
|
||||||
val appInfo = it.applicationInfo
|
}
|
||||||
val uid = appInfo!!.uid
|
/**
|
||||||
val profile = Natives.getAppProfile(it.packageName, uid)
|
* 清理资源
|
||||||
AppInfo(
|
*/
|
||||||
label = appInfo.loadLabel(pm).toString(),
|
override fun onCleared() {
|
||||||
packageInfo = it,
|
super.onCleared()
|
||||||
profile = profile,
|
try {
|
||||||
)
|
appProcessingThreadPool.close()
|
||||||
}.filter { it.packageName != ksuApp.packageName }
|
configChangeListeners.clear()
|
||||||
Log.i(TAG, "load cost: ${SystemClock.elapsedRealtime() - start}")
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error cleaning up resources", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -481,7 +481,7 @@
|
|||||||
<string name="susfs_reset_umounts_title">Reset Try Umount</string>
|
<string name="susfs_reset_umounts_title">Reset Try Umount</string>
|
||||||
<string name="susfs_reset_umounts_message">This will clear all try umount configurations. Are you sure you want to continue?</string>
|
<string name="susfs_reset_umounts_message">This will clear all try umount configurations. Are you sure you want to continue?</string>
|
||||||
<!-- SuSFS Path Settings -->
|
<!-- SuSFS Path Settings -->
|
||||||
<string name="susfs_path_settings">Path Settings</string>
|
<string name="susfs_path_settings">Reset Path Settingss</string>
|
||||||
<string name="susfs_android_data_path_label">Android Data Path</string>
|
<string name="susfs_android_data_path_label">Android Data Path</string>
|
||||||
<string name="susfs_sdcard_path_label">SD Card Path</string>
|
<string name="susfs_sdcard_path_label">SD Card Path</string>
|
||||||
<string name="susfs_set_android_data_path">Set Android Data Path</string>
|
<string name="susfs_set_android_data_path">Set Android Data Path</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user