BitmapFactory.decodeStream是线程安全的吗?有时我的Async任务在完成之前就结束了。

3

我有以下的异步任务,它应该只是从给定的URL加载一张图片。这些图片确实存在,而且我也可以访问它们。

private class FetchVehicleImage extends AsyncTask<String, Integer, Bitmap>
    {

        private ProgressBar mSpinner;
        private ImageView mImage;
        private String imagesBaseUrl = "http://mywebsite.net/images/";
        private URL url = null;

        @Override
        protected void onPreExecute()
        {
            mImage = (ImageView) findViewById(R.id.vehicle_image);
            mSpinner = (ProgressBar) findViewById(R.id.vehicle_image_progress_bar);
            mSpinner.setIndeterminate(true);
            mSpinner.setVisibility(View.VISIBLE);
            mImage.setVisibility(View.GONE);
        }

        @Override
        protected Bitmap doInBackground(String... strings)
        {
            Bitmap bm = null;

            try
            {
                url = new URL(imagesBaseUrl + strings[0]);

                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setDoInput(true);
                conn.connect();
                InputStream is = conn.getInputStream();
                bm = BitmapFactory.decodeStream(is);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
            return bm;
        }

        protected void onPostExecute(final Bitmap result)
        {
            if (result != null)
            {
                mImage.setImageBitmap(result);
            }
            mImage.setVisibility(View.VISIBLE);
            mSpinner.setVisibility(View.GONE);
        }
    }

我从未看到doInBackground中的异常,但有时bm返回为null,但这非常间歇性。我有4张图片,其中3张每次都可以完美加载,但有一张只有在我在bm赋值处打断点时才会加载,这样似乎给了它足够的时间来完成工作?
我认为doInBackground应该在后台线程上运行,因此我应该始终要么获取图像,要么得到异常?

也许这篇文章可以帮你理解一些 http://foo.jasonhudgins.com/2010/05/limitations-of-asynctask.html 但是在你只有4张图片的情况下,它几乎不适用。我建议你使用一个普通的线程(覆盖run()方法),并在此情况下使用runOnUiThread。 - Sergey Benner
1个回答

2
请注意,如果您的应用程序仅仅是因为位图的本地支持内存不足而运行缓慢,那么这种方法将无济于事。如果您在处理位图时遇到难以解释的问题,特别是在Honeycomb之前的版本中,我无法过分强调了解Dalvik堆和本地支持内存之间关系的重要性。Dubroy先生对此的讨论对我非常有帮助——值得全程倾听。Dubroy堆演示 然后,针对您上面提出的问题,我的尝试性答案是......我无法证明它是线程安全的,但我非常怀疑它不是线程安全的。当我在获取图像后进行图像处理时,我会遇到这个问题。与您上面的示例一样,当我请求多个图像文件并在它们到达时进行处理时,我会遇到OutOfMemory错误,我捕获这些错误,只发现堆和可用的本地支持内存都很好(>100k和>100M)。有时候获取工作正常(如您所描述的那样),但有时候不行。在某些设备上,它比其他设备更稳定。当被要求为此编造一个故事时,我想象自己可能有图像处理硬件(例如jpg编码器)在某些设备上而在其他设备上没有,操作系统的本地库可能或可能不会利用它们。然后,我立即责怪这些硬件瓶颈不安全——所有这些都没有任何像证据一样的东西。无论如何,我发现在我的测试环境中(约十个),唯一可靠的方法是隔离位图操作部分并单线程化。
在您上面的示例中,您仍将使用AsyncTask从网络获取文件,并将它们写入某个位置(原始字节流)。当AsyncTask完成(即调用其委托以进行onPostExecution)时,您可以执行下面我的海报类之类的操作。
在我的活动中(我在其中发出多个下载请求),我在类中创建了一个全局执行程序,最初在UI线程中实例化:
public ExecutorService mImagePipelineTask = null;  // Thread to use for pipelining images (overlays, etc.)

然后进行初始化:

        mImagePipelineTask = Executors.newSingleThreadExecutor();

然后,我放弃使用 AsyncTask,以控制 Thread 池中的线程数量。我的异步代码看起来像这样:

   public class PosterImage extends HashMap<String, Object> {

        private final String TAG = "DEBUG -- " + ClassUtils.getShortClassName(this.getClass());
        private PosterImageDelegate mPosterDelegate = null;
        private Drawable mBusyDrawable = null;
        private Drawable mErrorDrawable = null;
        private ExecutorService mImagePipelineTask = null;

        /*
         * Globals
         */
        Context mContext = null;

        /*
         * Constructors
         */
        public PosterImage() {
        }

        public PosterImage(PlaygroundActivity aContext) {
            mContext = aContext;
            mImagePipelineTask = aContext.mImagePipelineTask; 
            mBusyDrawable = mContext.getResources().getDrawable(R.drawable.loading);
            mErrorDrawable = mContext.getResources().getDrawable(R.drawable.load_error);
        }

然后,一些你可能不关心的细节...接着是一些初始化的内容,例如如何设置我们的委托(当然你会想要一个PosterImageDelegate接口):

    public void setPosterDelegate(PosterImageDelegate aPosterDelegate) {
        mPosterDelegate = aPosterDelegate;
    }

然后,对于执行图像处理的部分,会使用BitmapFactory(和Drawable)类。为了使用这些类,您需要实例化PosterImage对象,并将自己设置为代理,然后调用以下函数:

    public Drawable getPreformattedFileAsync() {
        if(mFetchFileTask == null) {
            Log.e(TAG, " -- Task is Null!!, Need to start an executor");
            return(mErrorDrawable);
        }
        Runnable job = new Runnable() {
             public void run() {
                 Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
                 Thread.currentThread().yield();
                 if(mPosterDelegate != null) {
                     Drawable retDrawable = getPreformattedFile();
                     if(retDrawable != null) {
                            mPosterDelegate.onDrawableRequest(retDrawable);
                     }  else  {
                         mPosterDelegate.onDrawableRequest( mErrorDrawable);
                     }
                 }
             }
         };
         mImagePipelineTask.execute(job);
         return(mBusyDrawable);
    }

    public Drawable getPreformattedFile() {
        Drawable ret = null;
        try {
            FileInputStream in = new FileInputStream(preformattedFileName());
            ret = Drawable.createFromStream(in, null);
                    // do something interesting with the Drawable
        } catch( OutOfMemoryError e ) {
            System.gc();
            e.printStackTrace();
                        // Will return null on its own
        } catch( Exception e) {
            Log.e(TAG, "Trouble reading PNG file ["+e+"]");
        }
        return(ret);
    }

当这个方法返回时,调用对象(在UI线程中)有一个“忙碌”的可绘制对象。当委托被调用时(在文件下载并由此线程转换为Drawable后),它已经准备好加载到您指定的任何Drawable接收器中。可以同时下载任意数量的图像,并保证后台线程一次只会处理一个图像。令人高兴的是,它不会占用UI线程来进行图像处理。
(注意:您仍然需要在调用类(将自己设置为委托的类)中使用Handler,以便UI线程实际将Drawable放入接收View/Layout/whatever中)。为了尽可能完整,可能看起来像:
mHandler.post(new Runnable() {
    @Override
    public void run() {
        aItem.getButton().setBackgroundDrawable(aDrawable);
        aItem.getButton().postInvalidate();
}
});

也许这些都有帮助,也许没有。但是我非常想听到你提出的优秀问题的明确答案。

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