Skip to content

manager: fix crashes when loading ultra-high resolution app icons#3227

Merged
YuKongA merged 3 commits intotiann:mainfrom
u9521:appicon-fix
Feb 28, 2026
Merged

manager: fix crashes when loading ultra-high resolution app icons#3227
YuKongA merged 3 commits intotiann:mainfrom
u9521:appicon-fix

Conversation

@u9521
Copy link
Contributor

@u9521 u9521 commented Feb 24, 2026

  • Automatically scales down oversized bitmaps based on the target display size before rendering.
  • Added a crossfade animation for smoother icon loading and implemented async label loading.

@u9521
Copy link
Contributor Author

u9521 commented Feb 24, 2026

fix #3226

@aviraxp
Copy link
Collaborator

aviraxp commented Feb 26, 2026

或许可以使用coil和appiconloader

@YuKongA

@u9521 u9521 closed this Feb 26, 2026
@u9521 u9521 deleted the appicon-fix branch February 26, 2026 16:16
@u9521 u9521 restored the appicon-fix branch February 26, 2026 18:41
@u9521 u9521 reopened this Feb 26, 2026
@u9521
Copy link
Contributor Author

u9521 commented Feb 26, 2026

改了一下,利用采用率缩放bitmap,现在那个2500x2500的图标(不知道为什么,在我的foldable avd上面原始bitmap分辨率单边能到6k+)平均40ms加载
顺便修了角标显示的问题

- Refactor `AppIconImage` to use `ApplicationInfo` and support asynchronous icon loading with a crossfade transition.
- Implement icon scaling for oversized bitmaps to improve performance and memory usage.
- Add a placeholder while the icon is loading.
- Introduce `DrawablePainter` to handle native `Drawable` rendering within Compose.
- Update `AppIconImage` to handle cases where `applicationInfo` might be null.

Signed-off-by: u9521 <63995396+u9521@users.noreply.github.com>
- Use `BitmapFactory.Options` with `inSampleSize` to decode oversized icons more efficiently.
- Replace `pm.getApplicationIcon` with `loadUnbadgedIcon` and manually apply user badges using `getUserBadgedIcon` to support multi-user profiles.
- Improve icon scaling logic by using `Canvas` to draw into a fixed-size bitmap when icons exceed twice the target size.
- Add `calculateInSampleSize` helper for memory-efficient image downsampling.

Signed-off-by: u9521 <63995396+u9521@users.noreply.github.com>
@YuKongA
Copy link
Collaborator

YuKongA commented Feb 27, 2026

现在哪个性能好点

…ROMs optimize loadUnbadgedIcon, such as adding clipping or automatic scaling, these optimizations are acceptable when the icon is not particularly large.

- Increase the icon size threshold for downsampling from 2x to 6x.
- Ensure `finalDrawable` is explicitly nulled when the icon size is within bounds or an exception occurs during decoding.

Signed-off-by: u9521 <63995396+u9521@users.noreply.github.com>
@u9521
Copy link
Contributor Author

u9521 commented Feb 27, 2026

现在哪个性能好点

大部分情况差不多,但是在一些定制rom上会优化 loadUnbadgedIcon 会添加clip,自动缩放之类的,如果遇上超大图的时候decodeResource更快,appiconloader 会读优化后的图标就会慢一些
下面写了两个perf

AppIconImage

Subject: [PATCH] manager: add icon loading performance logging
---
Index: manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt
--- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt	(revision b28108dc497cb4948efaca4994968dc42765a934)
+++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt	(revision 154ef77a3f1335696052dabc07550905b55c9f27)
@@ -7,6 +7,7 @@
 import android.graphics.drawable.BitmapDrawable
 import android.graphics.drawable.Drawable
 import android.os.UserHandle
+import android.util.Log
 import androidx.compose.animation.Crossfade
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Image
@@ -63,6 +64,7 @@
         }
 
         var appIcon by remember { mutableStateOf<Drawable?>(null) }
+        val start = System.nanoTime()
 
         LaunchedEffect(applicationInfo, targetSizePx) {
             val loadedIcon = withContext(Dispatchers.IO) {
@@ -117,6 +119,8 @@
                 }
             }
             appIcon = loadedIcon
+            val end = System.nanoTime()
+            Log.d("IconPerf", "${applicationInfo.packageName}: load icon resource cost ${(end - start) / 1_000_000}ms")
         }
 
         val appLabel by produceState(initialValue = label, key1 = applicationInfo) {

coil

Subject: [PATCH] manager: add Coil performance listener for icon loading
---
Index: manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt b/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt
--- a/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt	(revision bc805ecd75c608cfb3f1a7b25271314fb4292f5e)
+++ b/manager/app/src/main/java/me/weishu/kernelsu/KernelSUApplication.kt	(revision 8d26f47f5901af08899427738da8f42f1e5ef2d6)
@@ -11,6 +11,7 @@
 import coil3.ImageLoader
 import coil3.PlatformContext
 import coil3.SingletonImageLoader
+import me.weishu.kernelsu.ui.component.CoilPerformanceListener
 import me.weishu.kernelsu.ui.util.AppIconFetcher
 import me.weishu.kernelsu.ui.util.AppIconKeyer
 import me.weishu.kernelsu.ui.viewmodel.SuperUserViewModel
@@ -74,6 +75,7 @@
     override fun newImageLoader(context: PlatformContext): ImageLoader {
         val iconSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48f, context.resources.displayMetrics).toInt()
         return ImageLoader.Builder(context)
+            .eventListenerFactory(CoilPerformanceListener.Factory())
             .components {
                 add(AppIconKeyer())
                 add(AppIconFetcher.Factory(iconSize = iconSize))
Index: manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt
--- a/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt	(revision bc805ecd75c608cfb3f1a7b25271314fb4292f5e)
+++ b/manager/app/src/main/java/me/weishu/kernelsu/ui/component/AppIconImage.kt	(revision 8d26f47f5901af08899427738da8f42f1e5ef2d6)
@@ -1,6 +1,7 @@
 package me.weishu.kernelsu.ui.component
 
 import android.content.pm.PackageInfo
+import android.util.Log
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
@@ -12,7 +13,11 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import coil3.EventListener
 import coil3.compose.AsyncImage
+import coil3.request.ErrorResult
+import coil3.request.ImageRequest
+import coil3.request.SuccessResult
 import com.kyant.capsule.ContinuousRoundedRectangle
 import top.yukonga.miuix.kmp.theme.MiuixTheme.colorScheme
 
@@ -44,3 +49,26 @@
         )
     }
 }
+
+
+class CoilPerformanceListener : EventListener() {
+    private var startTime = 0L
+
+    override fun onStart(request: ImageRequest) {
+        startTime = System.nanoTime()
+    }
+
+    override fun onSuccess(request: ImageRequest, result: SuccessResult) {
+        val duration = (System.nanoTime() - startTime) / 1_000_000
+        Log.d("IconPerf", "Success [${duration}ms] Source:[${result.dataSource}] Data:[${(request.data as? PackageInfo)?.packageName}]")
+    }
+
+    override fun onError(request: ImageRequest, result: ErrorResult) {
+        val duration = (System.nanoTime() - startTime) / 1_000_000
+        Log.e("IconPerf", "Error [${duration}ms] Reason:[${result.throwable.message}] Data:[${(request.data as? PackageInfo)?.packageName}]")
+    }
+
+    class Factory : EventListener.Factory {
+        override fun create(request: ImageRequest) = CoilPerformanceListener()
+    }
+}
\ No newline at end of file

@YuKongA YuKongA merged commit 78c2b76 into tiann:main Feb 28, 2026
@u9521 u9521 deleted the appicon-fix branch February 28, 2026 05:05
u9521 added a commit to u9521/KernelSU that referenced this pull request Feb 28, 2026
…ann#3227)

- Automatically scales down oversized bitmaps based on the target
display size before rendering.
- Added a crossfade animation for smoother icon loading and implemented
async label loading.

---------

Signed-off-by: u9521 <63995396+u9521@users.noreply.github.com>
u9521 added a commit to u9521/KernelSU that referenced this pull request Feb 28, 2026
…ann#3227)

- Automatically scales down oversized bitmaps based on the target
display size before rendering.
- Added a crossfade animation for smoother icon loading and implemented
async label loading.

---------

Signed-off-by: u9521 <63995396+u9521@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants