I am developing an Android Launcher application that needs to enumerate and display installed applications (I am using MAUI because I am more familiar with XAML layouts, and I am using the MVVM Community Toolkit). I have published a minimal repro on GitHub here - https://github.com/PaloMraz/MauiLauncherApp. This is a simple, one-page app:
The „Refresh Apps!“ button retrieves a list of installed applications using Adroid APIs encapsulated in the LauncherService class. It then populates an observable collection-typed property AppsForListView when the „Use Collection View (instead of ListView)?“ checkbox is not checked, or AppsForCollectionView otherwise.
The problem I am facing is that when the CollectionView is used, pressing the „Refresh Apps!“ second time, it generates „Java.Lang.RuntimeException: 'Canvas: trying to use a recycled bitmap android.graphics.Bitmap@91dbd84'“ and the app crashes. This also happens when pressing „Refresh Apps!“ once and the trying to scroll through the displayed app list.
The Debug output with the exception stack trace is here.
This problem does not occur when using the ListView control.
I have tested on Android emulator and also on two physical devices (Samsung Galaxy A70, Zebra TC78).
As I am not proficient with Android development, can you please provide me with some tips / workarounds?
There is a bug caching Images
on MAUI
Collections
Solved by @mohachouch Intermittent crash when navigating to a page containing images #9011
MauiProgram.cs (Tested)
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
...
#if ANDROID
ImageHandler.Mapper.PrependToMapping(nameof(Microsoft.Maui.IImage.Source), (handler, view) => PrependToMappingImageSource(handler, view));
#endif
return builder.Build();
}
#if ANDROID
public static void PrependToMappingImageSource(IImageHandler handler, Microsoft.Maui.IImage image)
{
handler.PlatformView?.Clear();
}
#endif
}
Improved version by @Palo Mraz
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
...
#if ANDROID
ImageHandler.Mapper.PrependToMapping(nameof(Microsoft.Maui.IImage.Source), (handler, view) => handler.PlatformView?.Clear());
#endif
return builder.Build();
}
}
Failed attemps to get rid of the bug:
internal static List<AppInfoRecord> Cache_AppInfoRecordList = new List<AppInfoRecord>();
internal static List<AppInfoRecord_MemoryStream> Cache_AppInfoRecord_MemoryStreamList = new List<AppInfoRecord_MemoryStream>();
internal static List<AppInfoRecord_Image> Cache_AppInfoRecord_Image_List = new List<AppInfoRecord_Image>();
internal static List<AppInfoRecord_Bitmap> Cache_AppInfoRecord_Bitmap_List = new List<AppInfoRecord_Bitmap>();
internal static List<AppInfoRecord_Drawable> Cache_AppInfoRecord_Drawable_List = new List<AppInfoRecord_Drawable>();
public partial class LauncherService
{
...
public partial IEnumerable<AppInfoRecord> GetInstalledApps()
{
...
foreach (UserHandle userHandle in userManager.UserProfiles!)
{
...
foreach (var resolveActivityInfo in launcherApps.GetActivityList(null, userHandle)!)
{
var TryGetFrom_Cache_AppInfoRecord_Image_List = Cache.Cache_AppInfoRecord_Image_List.Where(x => x.PackageName == packageName).FirstOrDefault();
if (TryGetFrom_Cache_AppInfoRecord_Image_List is null)
{
var newAppInfoRecord_Image = new AppInfoRecord_Image(packageName, label, DrawableToImage(icon));
ImageSource imageSource = newAppInfoRecord_Image.Image.Source;
//ImageSource imageSource = DrawableToImageSource(icon);
//Bitmap bitmap = DrawableToBitmap(icon);
var new_AppInfoRecord = new AppInfoRecord(packageName, label, imageSource);
Cache.Cache_AppInfoRecord_Image_List.Add(newAppInfoRecord_Image);
yield return new_AppInfoRecord;
}
else
{
yield return new AppInfoRecord(packageName, label, TryGetFrom_Cache_AppInfoRecord_Image_List.Image.Source);
//yield return new AppInfoRecord(packageName, label, DrawableToImageSource(TryGetFrom_Cache_AppInfoRecord_Bitmap_List.icon_Drawable));
//yield return new AppInfoRecord(packageName, label, ImageSource.FromStream(() => TryGetFrom_Cache_AppInfoRecordList.MemoryStream_Icon), icon);
//yield return TryGetFrom_Cache_AppInfoRecordList;
}
}
}
}
}