BitmapFactory.decodeStream是否安全?有时我的异步任务在完成之前完成

时间:2012-01-21 11:25:22

标签: android multithreading android-asynctask android-2.1-eclair

我有以下异步任务,它应该只从给定的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应该在后台线程上运行,因此我应该总是获取图像,或者获得异常?

1 个答案:

答案 0 :(得分:2)

N.B。如果您的应用程序仅仅为其位图耗尽了本机后备内存,那么这种方法将无济于事。如果你有难以解释的位图问题,特别是在Honeycomb之前,我不能夸大理解Dalvik堆与本机后备内存之间关系的重要性。杜布罗先生对此的讨论对我非常有帮助 - 值得一直聆听Dubroy's Heap Presentation

然后,我试图回答你上面的问题。 。 。我无法证明这一点,但我非常怀疑它不是线程安全的。我在抓取后进行图像处理时就点击了这个。与上面的示例一样,当我请求多个图像文件并在它们到达时处理它们时,我得到OutOfMemory错误,我发现它只是发现堆和可用的本机后备内存都很好(分别>&gt; 100k和> 100M)。并且,有时获取工作(如您所述),但有时不工作。在某些设备上,它比其他设备更强大。当被要求发明故事的原因时,我想,在某些设备上可能存在图像处理硬件(例如jpg编码器)而不是其他设备,OS的本地库可能会或可能不会利用它们。然后,我立即着手将这些硬件瓶颈归咎于不是线程安全的 - 所有这些都没有任何类似证据的最小碎片。无论如何,我发现的唯一方法是在我的测试稳定(大约十几个)中的所有设备 - 可靠 - 是隔离位图操作部分和单线程。

在上面的示例中,您仍然可以使用AsyncTask实际从网络中获取文件,并将它们写入某个位置的存储(原始字节流)。当AsyncTask完成时(即调用onPostExecution的代理人),那么您可以执行类似我的海报课程。

在我的活动中(我发出了多个下载请求),我在类中创建了一个全局执行器,它最初在UI-Thread中实例化:

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-Thread中)具有'busy'drawable。当委托被调用时(在文件下载并被该线程转换为Drawable之后,它就可以加载到你指定的任何Drawable接收器中。可以并行下载任意数量的图像,这保证了后台线程只会一次处理一个图像。很幸运的是,它占用UI线程来进行图像处理

(注意,你的调用类中仍然需要一个Handler(将自己设置为委托的那个)让UI线程实际将Drawable 放入接收{{1} }} / View /其它)。对于尝试完整性,可能如下所示:

Layout

也许这一切都有帮助,也许不是。但我会听到你提出的优秀问题的确切答案。