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;
}
И затем, биты, которые выполняют манипуляции с изображениями и, как побочный эффект, используют классы 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);
}
Когда это возвращается, вызывающий объект (в потоке пользовательского интерфейса) имеет «занятый» объект. Когда делегат вызывается (после того, как файл загружен и преобразован в Drawable этим потоком, он готов для загрузки в любой назначаемый Drawable приемник. Любое количество изображений может быть загружено параллельно, и это гарантирует, что фоновый поток обрабатывает только одно изображение за раз. К счастью, не связывает поток пользовательского интерфейса для обработки изображений
(Примечание: вам все еще нужно Handler
в вашем вызывающем классе (тот, который устанавливает себя в качестве делегата), чтобы поток пользовательского интерфейса фактически помещал Drawable в принимающего View
/ Layout
/без разницы). Для попытки завершенности это может выглядеть так:
mHandler.post(new Runnable() {
@Override
public void run() {
aItem.getButton().setBackgroundDrawable(aDrawable);
aItem.getButton().postInvalidate();
}
});
Возможно, все это помогает, а может и нет. Но я бы хотел бы услышать окончательный ответ на отличный вопрос, который вы задаете.