Android - Bitmap.CreateBitmap - 空指针异常

5
有时候在尝试创建模糊位图时,我会遇到“空指针异常”。
出现在以下代码块中(最近开始捕获异常,至少不会使应用程序崩溃):
try
{
    using (Bitmap.Config config = Bitmap.Config.Rgb565) {
        return Bitmap.CreateBitmap (blurredBitmap, width, height, config);
    }
}
catch (Java.Lang.Exception exception)
{
    Util.Log(exception.ToString());
}

请参考这些图片,了解我传递到“CreateBitmap”方法中的参数的更多细节:

enter image description here

这里是扩展后的参数:

enter image description here

完整异常:
异常 {Java.Lang.NullPointerException: 抛出类型为“Java.Lang.NullPointerException”的异常。在 /Users/builder/data/lanes/2058/58099c53/source/mono/mcs/class/corlib/System.Runtime.ExceptionServices/ExceptionDispatchInfo.cs 中,System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() [0x0000b]。在 /Users/builder/data/lanes/2058/58099c53/source/monodroid/src/Mono.Android/src/Runtime/JNIEnv.g.cs 中,Android.Runtime.JNIEnv.CallStaticObjectMethod(IntPtr jclass, IntPtr jmethod, Android.Runtime.JValue* parms) [0x00064]。在 /Users/builder/data/lanes/2058/58099c53/source/monodroid/src/Mono.Android/platforms/android-22/src/generated/Android.Graphics.Bitmap.cs 中,Android.Graphics.Bitmap.CreateBitmap(System.Int32[] colors, Int32 width, Int32 height, Android.Graphics.Config config) [0x00088]。在 d:\Dev\psonar\Source\Psonar.Apps\Psonar.Apps.Droid\Psonar.Apps.Droid.PayPerPlay\Utilities\StackBlur.cs 中,Psonar.Apps.Droid.PayPerPlay.StackBlur.GetBlurredBitmap(Android.Graphics.Bitmap original, Int32 radius) [0x00375]。--- 管理的异常堆栈跟踪结束 --- java.lang.NullPointerException at android.graphics.Bitmap.createBitmap(Bitmap.java:687) at android.graphics.Bitmap.createBitmap(Bitmap.java:707) at dalvik.system.NativeStart.run(Native Method) } Java.Lang.NullPointerException
不确定这是否是Xamarin中的错误还是传递的参数有误。
1个回答

2
我收到了Xamarin团队成员Jonathan Pryor的回复:

The NullPointerException is coming from Java code:

at android.graphics.Bitmap.createBitmap(Bitmap.java:687) at android.graphics.Bitmap.createBitmap(Bitmap.java:707) at dalvik.system.NativeStart.run(Native Method)

Looking quickly at a variety of releases, Jelly Bean might fit:

https://github.com/android/platform_frameworks_base/blob/jb-release/graphics/java/android/graphics/Bitmap.java#L687

    return nativeCreate(colors, offset, stride, width, height,
                        config.nativeInt, false);

A quick glance at the surrounding method body shows that config isn't checked for null-ness, so if null is passed this would result in a NullPointerException.

The problem, though, is that you're not passing null:

using (Bitmap.Config config = Bitmap.Config.Rgb565) {
    return Bitmap.CreateBitmap (blurredBitmap, width, height, config);
}

...or are you?

I would suggest that you remove the using block:

return Bitmap.CreateBitmap (blurredBitmap, width, height,
        Bitmap.Config.Rgb565);

Here's what I think may be happening, but first, a digression:

Deep in the core of Xamarin.Android there is a mapping between Java objects and their corresponding C# wrapper objects. Constructor invocation, Java.Lang.Object.GetObject(), etc. will create mappings; Java.lang.Object.Dispose() will remove mappings.

A core part of this is Object Identity: when a Java instance is exposed to C# code and a C# wrapper is created, the same C# wrapper instance should continue to be reused for that Java instance.

An implicit consequence of this is that any instance is effectively global, because if multiple code paths/threads/etc. obtain a JNI handle to the same Java instance, they'll get the same C# wrapper.

Which brings us back to my hypothesis, and your code block: Bitmap.Config is a Java enum, meaning each member is a Java object. Furthermore, they're global values, so every thread has access to those members, meaning the C# Bitmap.Config.Rgb565 instance is effectively a global variable.

A global variable that you're Dispose()ing.

Which is "fine", in that the next time Bitmap.Config.Rgb565 is access, a new wrapper will be created.

The problem, though, is if you have multiple threads accessing Bitmap.Config.Rgb565 at the ~same time, each of which is trying to Dispose() of an instance. At which point it's entirely plausible that the two threads may reference the same wrapper instance, and the Dispose() from one thread will thus INVALIDATE the instance used by the other thread.

Which would result in null being passed to the Bitmap.createBitmap() call, which is precisely what you're observing.

Please try removing the using block, and see if that helps matters.

整个讨论线程可以在此处访问。

然后我问:

Jonathan Pryor - 感谢您的建议。我的问题是,如果我删除using语句,是否会引入内存泄漏?也就是说,如果我停止处理新的config实例会发生什么?

他回答道:

这就是GC的作用!

(插入咳嗽和笑声。)

我们可以争论很多。我会认为这不是内存泄漏,因为内存是有根的且被充分知晓的;不断访问Bitmap.Config.Rgb565将返回先前创建的实例,而不是不断创建新实例。没有所谓的“泄漏”。

我会认为这个实例和底层GREF是一种“税收”,是做生意成本的一部分。虽然最好尽量减少这些成本,但删除所有成本并不切实际(例如,我们通过.class_ref每个类失去一个GREF,.class_ref用于查找方法ID...),至少在当前架构下是如此。

(我也想不出会导致不同成本/“税收”的替代架构。虽然我对允许某些领域改进的想法有一些想法,但它们并不是很大。)

我建议不要过分担心Bitmap.Config.Rgb565和类似成员,除非/直到分析器或GREF计数显示其他情况。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接