Xamarin Android强引用Bitmap被GC回收致ObjectDisposedException问题
问题原因
Xamarin JNI桥接的双重生命周期管理
你的Bitmap是Android原生对象的.NET托管包装器,生命周期同时受.NET GC和JNI引用规则约束。当ML Kit的Process方法执行完毕后,内部会自动释放关联的JNI局部引用;此时如果.NET GC判定托管包装器(即代码中的bitmap变量)没有被有效根引用,就会回收该包装器,进而触发原生Bitmap的recycle()方法释放底层资源,后续访问就会抛出ObjectDisposedException。异步上下文的JIT优化导致引用丢失
在async/await的挂起-恢复流程中,JIT编译器可能对闭包中的变量(如bitmap、dumObj)进行优化。当AddOnCompleteListener的回调执行GC.Collect()时,await之后的代码尚未执行,JIT可能判定这些变量在当前执行阶段没有被引用,将其标记为可回收对象。自定义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

