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应该在后台线程上运行,因此我应该总是获取图像,或者获取异常?

4

1 回答 1

2

注意 如果您的应用程序只是用完其位图的本机后备内存,那么这种方法将无济于事。如果您在位图方面遇到难以解释的问题,尤其是在蜂窝之前,我不能夸大理解 Dalvik 堆与本机后备内存的关系的重要性。Dubroy 先生对此的讨论对我非常有帮助——值得通过Dubroy 的 Heap Presentation一路聆听

然后,我试图回答你上面的问题。. . 我无法证明这一点,但我非常怀疑它不是线程安全的。我在获取后进行图像处理时遇到了这个问题。与上面的示例一样,当我请求多个图像文件并在它们到达时对其进行处理时,我得到OutOfMemory我捕获的错误只是发现堆和可用的本机后备内存都很好(分别> 100k和> 100M)。而且,有时 fetch 有效(如您所述),但有时无效。在某些设备上,它比其他设备更强大。当被要求编造一个故事来解释为什么会这样时,我自己想象在某些设备上可能有图像处理硬件(例如 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(and 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();
}
});

也许这一切都有帮助,也许没有。但我很想听听你提出的优秀问题的明确答案。

于 2012-11-15T19:42:30.800 回答