如何卸载 Image 对象上显示的图像以释放内存?

问题描述

我正在开发一个 Xamarin.Forms Android 项目,只需按一下按钮即可显示一个一个的图像。我当前的测试设备是 HTC Nexus 9,显然,由于规格有限,我在尝试加载第 17 张图片时出现以下错误

java.lang.OutOfMemoryError: Failed to allocate a 11059212 byte allocation with 8286168 free bytes and 7MB until OOM
    at dalvik.system.vmruntime.newNonMovableArray(Native Method)
    at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method)
    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:620)
    at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:455)
    at android.graphics.drawable.Drawable.createFromresourceStream(Drawable.java:1155)
    at android.content.res.ResourcesImpl.loadDrawableForCookie(ResourcesImpl.java:720)
    at android.content.res.ResourcesImpl.loadDrawable(ResourcesImpl.java:571)
    at android.content.res.Resources.getDrawable(Resources.java:771)
    at android.content.Context.getDrawable(Context.java:525)
    at androidx.core.content.ContextCompat.getDrawable(ContextCompat.java:455)
    at androidx.appcompat.widget.ResourceManagerInternal.getDrawable(ResourceManagerInternal.java:144)
    at androidx.appcompat.widget.ResourceManagerInternal.getDrawable(ResourceManagerInternal.java:132)
    at androidx.appcompat.content.res.AppCompatResources.getDrawable(AppCompatResources.java:104)
    at crc64ee486da937c010f4.ButtonRenderer.n_onClick(Native Method)
    at crc64ee486da937c010f4.ButtonRenderer.onClick(ButtonRenderer.java:104)
    at android.view.View.performClick(View.java:5637)
    at android.view.View$PerformClick.run(View.java:22433)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.app.ActivityThread.main(ActivityThread.java:6229)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:891)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:781)

我尝试在 Java Max Heap Size 下将 1G 设置为 .Android > Properties > Android Options > Advanced,但没有任何改变。

enter image description here

我找到了一个 potential solution here,但显然,它适用于基于 Java 的 Android 应用程序,我不知道在 Xamarin.Forms 的 Android 项目上哪里可以找到类似的清单文件

以下是我使用最简单的 MVVM 实现的代码片段:

.xaml

<Image Source="{Binding ImgSource}"
       WidthRequest="960"
       HeightRequest="720" />
       

<!--#region Page1 Controls-->
<Button Command="{Binding Page1DhcpIconCommand}"
        IsVisible="{Binding Page1ControlVisible}"
        BackgroundColor="{Binding Page1ControlColor}"
        Opacity="0.25"
        RelativeLayout.XConstraint="57"
        RelativeLayout.YConstraint="679"
        RelativeLayout.WidthConstraint="46"
        RelativeLayout.HeightConstraint="37" />
<!--#endregion-->

<!--#region Page2 Controls-->
<Button Command="{Binding Page2RolesCommand}"
        IsVisible="{Binding Page2ControlVisible}"
        BackgroundColor="{Binding Page2ControlColor}"
        Opacity="0.25"
        RelativeLayout.XConstraint="24"
        RelativeLayout.YConstraint="85"
        RelativeLayout.WidthConstraint="46"
        RelativeLayout.HeightConstraint="15" />
<!--#endregion-->

<!--#region Page3 Controls-->
<Button Command="{Binding Page3AddRolesCommand}"
        IsVisible="{Binding Page3ControlVisible}"
        BackgroundColor="{Binding Page3ControlColor}"
        Opacity="0.25"
        RelativeLayout.XConstraint="720"
        RelativeLayout.YConstraint="199"
        RelativeLayout.WidthConstraint="71"
        RelativeLayout.HeightConstraint="21" />
<!--#endregion-->

viewmodel.cs

#region backing Fields
string imgSource;

bool page1ControlVisible;
string page1ControlColor;

bool page2ControlVisible;
string page2ControlColor;

bool page3ControlVisible;
string page3ControlColor;
#endregion
#region Private Variables
private readonly bool _transparentOverride;
#endregion
#region Public Properties
public string ImgSource
{
    get => imgSource;
    set => SetProperty(ref imgSource,value);
}

#region Page1 Properties & Commands
public bool Page1ControlVisible
{
    get => page1ControlVisible;
    set => SetProperty(ref page1ControlVisible,value);
}
public string Page1ControlColor
{
    get => page1ControlColor;
    set => SetProperty(ref page1ControlColor,value);
}

public Command Page1DhcpIconCommand { get; }
#endregion

#region Page2 Properties & Commands
public bool Page2ControlVisible
{
    get => page2ControlVisible;
    set => SetProperty(ref page2ControlVisible,value);
}
public string Page2ControlColor
{
    get => page2ControlColor;
    set => SetProperty(ref page2ControlColor,value);
}

public Command Page2RolesCommand { get; }
#endregion

#region Page3 Properties & Commands
public bool Page3ControlVisible
{
    get => page3ControlVisible;
    set => SetProperty(ref page3ControlVisible,value);
}
public string Page3ControlColor
{
    get => page3ControlColor;
    set => SetProperty(ref page3ControlColor,value);
}

public Command Page3AddRolesCommand { get; }
#endregion
#endregion


public DHCPConfigurationviewmodel()
{
    _transparentOverride = true;

    ImgSource = "img_dhcp_config_01.jpg";

    #region Page1 Properties & Commands
    Page1ControlVisible = true;
    Page1ControlColor = _transparentOverride ? "Transparent" : "Red";

    Page1DhcpIconCommand = new Command(Page1DhcpIcon);
    #endregion

    #region Page2 Properties & Commands
    Page2ControlVisible = false;
    Page2ControlColor = _transparentOverride ? "Transparent" : "Orange";

    Page2RolesCommand = new Command(Page2Roles);
    #endregion

    #region Page3 Properties & Commands
    Page3ControlVisible = false;
    Page3ControlColor = _transparentOverride ? "Transparent" : "Yellow";

    Page3AddRolesCommand = new Command(Page3AddRoles);
    #endregion
}

private void Page1DhcpIcon()
{
    ImgSource = "img_dhcp_config_02.jpg";

    Page1ControlVisible = false;
    Page2ControlVisible = true;
}

private void Page2Roles()
{
    ImgSource = "img_dhcp_config_03.jpg";

    Page2ControlVisible = false;
    Page3ControlVisible = true;
}

private void Page3AddRoles()
{
    ImgSource = "img_dhcp_config_04.jpg";

    Page3ControlVisible = false;
    Page4ControlVisible = true;
}

解决方法

为此,您可以检查文档 Improve Xamarin.Forms App Performance

特别部分:Optimize image resources

显示图像资源可以大大增加应用程序的 内存占用。因此,它们应该只在需要时创建 并且应该在应用程序不再需要时立即发布 他们。例如,如果应用程序通过阅读来显示图像 它的数据来自流,确保仅在流时创建流 需要,并确保流不再被释放 必需的。这可以通过在页面打开时创建流来实现 创建,或者当 Page.Appearing 事件触发时,然后处理 Page.Disappearing 事件触发时的流。

使用 ImageSource.FromUri 方法下载要显示的图像时,通过确保 UriImageSource.CachingEnabled 属性设置为 [] 来缓存下载的图像。

您还可以尝试 nuget Xamarin.FFImageLoading.Forms,这是一个 xamarin 库,可在 Xamarin.Forms 上快速轻松地加载图像。 更多详情,请查看:https://github.com/luberda-molinet/FFImageLoading .