有时位图解码返回 SkImageDecoder::Factory 返回 null 并显示损坏的图像

Posted

技术标签:

【中文标题】有时位图解码返回 SkImageDecoder::Factory 返回 null 并显示损坏的图像【英文标题】:Sometimes Bitmap decoding return SkImageDecoder::Factory returned null and it displays corrupted image 【发布时间】:2016-09-27 11:22:39 【问题描述】:

我开发了以下类来接收带有标题(22 字节)发送的图像,首先我解码标题以检查图像正确性,然后我解码图像,但并非所有图像都被解码,有时它返回 SkImageDecoder::Factory returned null。我正在使用this android 教程到inSampleImage

public class SocketServerStreamDataThread extends Thread 

    private Socket streamSocket;
    MainActivity mainActivity ;
    ImageView  displayedImage ;

    Bitmap bitmapImageToDisplay = null;
    byte[] dataBuffer = new byte[1048576];

    boolean result ;

    protected static boolean mReceivingStop; // Flag used to start and stop transmitting data

    SocketServerStreamDataThread(Socket socket, MainActivity mainActivityReceived, ImageView displayedImageView) 
        streamSocket = socket;
        // received activity to work on it in our java file
        mainActivity = mainActivityReceived ;
        // received image UI component to display
        displayedImage = displayedImageView;
    

    @Override
    public void run() 
        /* Receiving the image */
        try 
            DecodeData decodeData = new DecodeData();
            // call DecodeData to get Image data from stream
            while (mReceivingStop == false) 
                if (bitmapImageToDisplay == null) 
                    InputStream inputStream = streamSocket.getInputStream();
                    if (inputStream.read(dataBuffer) > -1) 
                        // Call the class to decode received stream and return integer with the state of the decoding of the received data
                        // result = 0  ; means that decoding header is successful.
                        // result = -1 ; means that there is a problem in decoding header.
                        byte [] dataHeaderReceived = Arrays.copyOf(dataBuffer, 23) ;
                        result = decodeData.iDecodeReceiveData(dataHeaderReceived);
                        // Evalute the received data
                        if (result == false) 
                            /* Fault on application */
                            /* Close socket */
                            streamSocket.close();
                            mReceivingStop = true;
                        

                        if (result == true) 
                            /* Data have been received */
                            /* Show image */
                            BitmapFactory.Options imageOption = new BitmapFactory.Options();
                            imageOption.inJustDecodeBounds = true;
                            BitmapFactory.decodeResource(mainActivity.getResources(), R.id.display_image, imageOption);
                            imageOption.inSampleSize = calculateInSampleSize (imageOption, 550, 435) ;
                            imageOption.inJustDecodeBounds = false;
                            bitmapImageToDisplay = BitmapFactory.decodeByteArray(dataBuffer, 23, decodeData.imageSize, imageOption);

                            if (bitmapImageToDisplay != null) 
                                mainActivity.runOnUiThread(new Runnable() 
                                    @Override
                                    public void run() 
                                        //try to display to image
                                        displayedImage.setImageBitmap(bitmapImageToDisplay);
                                    
                                );
                            
                        
                    
                    SystemClock.sleep(300);
                    bitmapImageToDisplay = null ;
                
            
            streamSocket.close();
            mReceivingStop = true;

            mainActivity.runOnUiThread(new Runnable() 
                @Override
                public void run() 
                    Toast.makeText(mainActivity, "Finished", Toast.LENGTH_LONG).show();
                
            );
         catch (IOException e) 
            e.printStackTrace();
            StringBuilder eMsg = new StringBuilder();
            eMsg.append("Something wrong: " + e.getMessage());
            final String eMessage=eMsg.toString();
            // final String eMsg = "Something wrong: " + e.getMessage();

            mainActivity.runOnUiThread(new Runnable() 
                @Override
                public void run() 
                    Toast.makeText(mainActivity, eMessage, Toast.LENGTH_LONG).show();
                
            );
         finally 
            if(streamSocket != null)
                try 
                    streamSocket.close();
                    mReceivingStop = true ;
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    

Logcat 输出:

GC_FOR_ALLOC freed 1307K, 27% free 13676K/18596K, paused 34ms, total 34ms
D/dalvikvm: GC_FOR_ALLOC freed 1306K, 27% free 13676K/18596K, paused 8ms, total 8ms
D/skia: --- SkImageDecoder::Factory returned null
D/skia: --- SkImageDecoder::Factory returned null
D/dalvikvm: GC_FOR_ALLOC freed 1607K, 29% free 13376K/18596K, paused 24ms, total 24ms
D/skia: --- SkImageDecoder::Factory returned null
D/dalvikvm: GC_FOR_ALLOC freed 1255K, 27% free 13728K/18596K, paused 9ms, total 9ms

【问题讨论】:

我使用 SystemClock.sleep(300);为解码过程留出时间,因为它没有被解码,并且出现了 decode return false 的消息 什么是DecodeData?你从哪里得到resId DecodeData 是对接收到的图像的头部进行解码以获取图像属性并了解图像大小的类 【参考方案1】:

在 Android 上加载位图是一个庞大且相当复杂的主题。这就是为什么我建议使用经过验证的库之一来为您处理它;例如Glide、Picasso 或Fresco。这些库将为您处理加载位图带来的许多陷阱;包括内存限制、缓存和线程。


如果你真的想一个人去,你将不得不自己面对这些问题。 SkImageDecoder::Factory returned nullBitmapFactory.decode... 方法在位图无法正确解码时吐出的内容,这可能是由许多问题引起的。我不知道具体是什么原因造成的,但我确实在您的代码中看到了一些问题。

首先,您将MainActivity 传递给Thread,这只是自找麻烦。相反,您只需要将ImageView 传递给Thread。并确保您使用WeakReference,否则您可能会泄露您的Context

WeakReference<ImageView> mDisplayedImageView;

SocketServerStreamDataThread(Socket socket, ImageView imageView) 
    mDisplayedImageView = new WeakReference<>(imageView);
    ...

接下来,您正在使用可绘制资源来解码Bitmap 的边界,但随后您尝试从Socket 连接中获取Bitmap。相反,您应该使用InputStream 解码边界:

BufferedInputStream inBounds = new BufferedInputStream(streamSocket.getInputStream());

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(inBounds, null, options);

options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

inBounds.close();

您不需要单独的DecodeData 类,因为BitmapFactory 也有解码流的方法:

options.inJustDecodeBounds = false;

BufferedInputStream in = new BufferedInputStream(streamSocket.getInputStream());

Bitmap bitmap = BitmapFactory.decodeStream(in, null, options);

in.close();

最后,如果您使用Thread.sleep()SystemClock.sleep() 来克服一些障碍,您可能应该重新考虑您的解决方案。这些只是我快速浏览时注意到的问题。如果您真的想在没有库帮助的情况下继续,我建议您对如何有效地加载位图进行更多研究;一个好的起点是the documentation。

【讨论】:

感谢您的帮助,但首先接收到的图像添加了需要解码的 22 个特殊字节标头,因此我使用的是 DecodeData 类,它无法使用 BitmapFactory 解码,因此我需要先阅读前 22 个字节用于读取特殊数据。其次,我正在使用“SystemClock.sleep()”来克服这个我找不到它的来源的问题。另外我正在传递“MainActivity”来使用“runOnUiThread”我不知道“WeakReference”我会搜索它如果这是一个解决方案。 我有一个问题,Picasso 或 Glide 是否在内部包含图像解码器而不是 'BitmapFactory.decode'? 据我所知 Picasso、Glide 和 Fresco 都使用BitmapFactory.decode... 方法来解码图像,它们不包含单独的解码器。 您还应该使用Handler 和回调,而不是传递MainActivity

以上是关于有时位图解码返回 SkImageDecoder::Factory 返回 null 并显示损坏的图像的主要内容,如果未能解决你的问题,请参考以下文章

SkImageDecoder:工厂返回 null

Android SkImageDecoder :: Factory返回null错误

Xamarin 安卓。将字节数组转换为位图。 Skia 解码器返回 false

Android SKIA 图像解码

Android位图解码A

如何获取位图信息,然后从 internet-inputStream 解码位图?