You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin Android强引用Bitmap被GC回收致ObjectDisposedException问题

问题原因分析及修复方案

问题原因

  1. Xamarin JNI桥接的双重生命周期管理
    你的Bitmap是Android原生对象的.NET托管包装器,生命周期同时受.NET GC和JNI引用规则约束。当ML Kit的Process方法执行完毕后,内部会自动释放关联的JNI局部引用;此时如果.NET GC判定托管包装器(即代码中的bitmap变量)没有被有效根引用,就会回收该包装器,进而触发原生Bitmap的recycle()方法释放底层资源,后续访问就会抛出ObjectDisposedException。

  2. 异步上下文的JIT优化导致引用丢失
    在async/await的挂起-恢复流程中,JIT编译器可能对闭包中的变量(如bitmap、dumObj)进行优化。当AddOnCompleteListener的回调执行GC.Collect()时,await之后的代码尚未执行,JIT可能判定这些变量在当前执行阶段没有被引用,将其标记为可回收对象。

  3. 自定义JNI监听器的引用持有问题
    GmsTaskCompleteListener作为原生Java监听器的实现,如果没有正确持有对.NET回调逻辑的强引用,会导致.NET侧的闭包(包含bitmap的引用)失去根引用,被GC提前回收。

正确修复方案

方案1:强制保留异步流程中的变量引用

通过将关键变量标记为volatile,或在回调中显式引用变量,阻止JIT优化掉这些引用,确保它们在await之后的代码执行前不被GC回收。

修改后的代码:

View view = (View)sender;
var bitmap = Bitmap.CreateBitmap(480, 640, Bitmap.Config.Argb8888);
view.Post(async () =>
{
    var recognizer = TextRecognition.GetClient(Xamarin.Google.MLKit.Vision.Text.Latin.TextRecognizerOptions.DefaultOptions);
    // 用volatile标记避免JIT优化
    volatile DummyObj dumObj = new DummyObj();
    dumObj.bitmap = bitmap;
    volatile Bitmap localBitmap = bitmap;

    await recognizer.Process(InputImage.FromBitmap(localBitmap, 0)).AddOnCompleteListener(new GmsTaskCompleteListener<Text>(visionText =>
    {
        // 显式引用变量,确保不被GC回收
        _ = localBitmap.Handle;
        GC.Collect();
    },
    errorMessage =>
    {
        Console.WriteLine("Error occurred");
    }));

    Console.WriteLine(dumObj.bitmap.ByteCount);
    Console.WriteLine(localBitmap.ByteCount);
    Console.WriteLine("Experiment Completed!");
});

方案2:使用.NET原生异步API替代JNI监听器

ML Kit的Xamarin绑定通常提供了.NET风格的异步方法(直接返回Task<Text>),无需手动添加JNI监听器。这种方式下,.NET的异步上下文会自动管理变量引用,避免JNI侧的生命周期冲突。

修改后的代码:

View view = (View)sender;
var bitmap = Bitmap.CreateBitmap(480, 640, Bitmap.Config.Argb8888);
view.Post(async () =>
{
    var recognizer = TextRecognition.GetClient(Xamarin.Google.MLKit.Vision.Text.Latin.TextRecognizerOptions.DefaultOptions);
    DummyObj dumObj = new DummyObj();
    dumObj.bitmap = bitmap;

    try
    {
        var visionText = await recognizer.Process(InputImage.FromBitmap(bitmap, 0));
        GC.Collect();
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error occurred: {ex.Message}");
    }

    Console.WriteLine(dumObj.bitmap.ByteCount);
    Console.WriteLine(bitmap.ByteCount);
    Console.WriteLine("Experiment Completed!");
});

如果绑定未提供直接返回Task的Process方法,可以自行用TaskCompletionSource封装JNI监听器逻辑,确保.NET异步上下文正确保留引用。

方案3:手动管理JNI全局引用

通过创建JNI全局引用,强制保留原生Bitmap的资源,直到你手动释放,避免.NET GC提前回收托管包装器。

修改后的代码:

View view = (View)sender;
var bitmap = Bitmap.CreateBitmap(480, 640, Bitmap.Config.Argb8888);
// 创建JNI全局引用,锁定原生Bitmap资源
var jniBitmapRef = Android.Runtime.JNIEnv.NewGlobalRef(bitmap.Handle);
view.Post(async () =>
{
    var recognizer = TextRecognition.GetClient(Xamarin.Google.MLKit.Vision.Text.Latin.TextRecognizerOptions.DefaultOptions);
    DummyObj dumObj = new DummyObj();
    dumObj.bitmap = bitmap;

    await recognizer.Process(InputImage.FromBitmap(bitmap, 0)).AddOnCompleteListener(new GmsTaskCompleteListener<Text>(visionText =>
    {
        GC.Collect();
    },
    errorMessage =>
    {
        Console.WriteLine("Error occurred");
    }));

    Console.WriteLine(dumObj.bitmap.ByteCount);
    Console.WriteLine(bitmap.ByteCount);
    Console.WriteLine("Experiment Completed!");

    // 手动释放JNI全局引用,避免内存泄漏
    Android.Runtime.JNIEnv.DeleteGlobalRef(jniBitmapRef);
});

临时方案的不足

你当前使用的GCHandle.Alloc虽然能临时锁定对象,但需要手动调用Free()释放,一旦遗漏会导致内存泄漏;上述方案更贴合Xamarin的生命周期管理规则,安全性和可维护性更高。

内容的提问来源于stack exchange,提问作者Shubham Agarwal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 07:25:28