BitmapFactory.decodeStream поток безопасно?Иногда моя асинхронная задача заканчивается до того, как она завершится - PullRequest
3 голосов
/ 21 января 2012

У меня есть следующая асинхронная задача, которая должна просто загрузить изображение с заданного 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 возвращается как ноль, но это очень прерывисто.У меня есть 4 изображения, 3 из которых загружаются идеально каждый раз, но одно загружается только в том случае, если я достиг точки останова в назначении bm, якобы давая ему достаточно времени для выполнения своей работы?

Я думал, что doInBackground должен работать в фоновом потоке, поэтому я всегда должен либо получать изображение, либо получать исключение?

1 Ответ

2 голосов
/ 15 ноября 2012

N.B. Если вашему приложению просто не хватает встроенной резервной памяти для растровых изображений, такой подход не поможет. Если у вас проблемы с растровыми изображениями, особенно с предсотовыми картами, которые трудно объяснить, я не могу преувеличить важность понимания связи кучи Dalvik с собственной резервной памятью. Мистер Дуброй обсудил это исключительно полезно для меня - стоило слушать все время до Куча Дуброя

И тогда моя попытка ответить на ваш вопрос выше. , , Я не могу доказать это, но у меня есть очень сильное подозрение, что это не потокобезопасно. Я ударил это, когда я делаю изображение после извлечения. Как и в приведенном выше примере, когда я запрашиваю более одного файла изображения и обрабатываю их по мере их поступления, я получаю OutOfMemory ошибок, которые я улавливаю, только чтобы обнаружить, что и куча, и доступная собственная резервная память работают нормально (> 100k и> 100M соответственно). И иногда выборка работает (как вы описываете), но иногда нет. На некоторых устройствах это более надежно, чем на других. Когда мне предложат придумать историю, почему это так, я представляю себе, что на некоторых устройствах может быть аппаратное обеспечение для обработки изображений (например, jpg-кодировщики), а не на других, что могут или не могут использоваться нативные библиотеки ОС. Затем я сразу же обвиняю эти аппаратные узкие места в том, что они не безопасны для потоков - и все это без малейшего клочка чего-либо, напоминающего доказательства. В любом случае, единственный подход, который я нашел, работает на всех устройствах в моей тестовой стабильной версии (около десятка) - надежно - это изолировать части манипуляции с растровыми изображениями и однопоточную.

В приведенном выше примере вы все равно используете AsyncTask для фактического извлечения файлов из сети и их записи в хранилище (поток необработанных байтов). Когда AsyncTask завершается (то есть вызывает его делегата для onPostExecution), тогда вы можете сделать что-то вроде моего класса Poster ниже.

В своей деятельности (где я делаю несколько запросов на загрузку) я создаю глобального исполнителя в классе, который первоначально создается в потоке пользовательского интерфейса:

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;
    }

И затем, биты, которые выполняют манипуляции с изображениями и, как побочный эффект, используют классы BitmapFactoryDrawable). Чтобы использовать это, вы создаете экземпляр объекта 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);
    }

Когда это возвращается, вызывающий объект (в потоке пользовательского интерфейса) имеет «занятый» объект. Когда делегат вызывается (после того, как файл загружен и преобразован в Drawable этим потоком, он готов для загрузки в любой назначаемый Drawable приемник. Любое количество изображений может быть загружено параллельно, и это гарантирует, что фоновый поток обрабатывает только одно изображение за раз. К счастью, не связывает поток пользовательского интерфейса для обработки изображений

(Примечание: вам все еще нужно Handler в вашем вызывающем классе (тот, который устанавливает себя в качестве делегата), чтобы поток пользовательского интерфейса фактически помещал Drawable в принимающего View / Layout/без разницы). Для попытки завершенности это может выглядеть так:

mHandler.post(new Runnable() {
    @Override
    public void run() {
        aItem.getButton().setBackgroundDrawable(aDrawable);
        aItem.getButton().postInvalidate();
}
});

Возможно, все это помогает, а может и нет. Но я бы хотел бы услышать окончательный ответ на отличный вопрос, который вы задаете.

...