Android: java.util.concurrent.ThreadPoolExecutor
En mi aplicación hay RecyclerView
con toneladas de imágenes en it.Images se cargan como el usuario se desplaza RecyclerView con este código:
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) loader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,url); else loader.execute(url);
Desafortunadamente, cuando el usuario se desplaza rápidamente este error se produce:
- Android Thread.sleep a veces espera demasiado tiempo
- No error "Sólo el subproceso original que creó una jerarquía de vista puede tocar sus vistas" cuando la vista se actualiza sin demora
- Java.lang.IllegalMonitorStateException: objeto no bloqueado por hilo antes de wait ()?
- ¿Es esta una forma perfecta de detener handlerthread?
- Cuando está fuera del hilo principal, ¿cómo puedo obtener algún código para ejecutarse en el hilo principal lo más rápido posible?
Task android.os.AsyncTask$3@73f1d84 rejected from java.util.concurrent.ThreadPoolExecutor@8f5f96d[Running, pool size = 9, active threads = 9, queued tasks = 128, completed tasks = 279]
¿Hay manera de detectar si poolExecutor está lleno y omitir la carga de la imagen?
Clase de imagen completa:
public class Image extends ImageView { private AsyncTask<String,Integer,Bitmap> loader; public Image(Context context) { super(context); this.setScaleType(ScaleType.FIT_XY); } public Image(Context context, AttributeSet attrs) { super(context, attrs); this.setScaleType(ScaleType.FIT_XY); } public void loadURL(String url) { if(loader!=null) loader.cancel(true); loader=new AsyncTask<String, Integer, Bitmap>() { @Override protected Bitmap doInBackground(String... params) { URL url = null; byte[] bytes = null; HttpURLConnection connection=null; try { url = new URL(params[0]); connection=(HttpURLConnection) url.openConnection(); connection.setRequestProperty("Connection", "close"); connection.setRequestMethod("GET"); connection.setUseCaches(true); InputStream is = null; is=connection.getInputStream(); bytes = IOUtils.toByteArray(is); } catch (MalformedURLException e) { e.printStackTrace(); } catch (ProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } if (connection!=null) connection.disconnect(); Bitmap res=null; if(!isCancelled() && bytes!=null) res=BitmapFactory.decodeByteArray(bytes,0,bytes.length); return res; } @Override protected void onPostExecute(Bitmap res) { if(res!=null) { setImageBitmap(res); _animate(); } } }; if (this.getDrawable()!=null) { Bitmap bmp=((BitmapDrawable) this.getDrawable()).getBitmap(); this.setAnimation(null); if (bmp!=null) { bmp.recycle(); //Log.d("image","recycled"); } this.setImageBitmap(null); } /* ThreadPoolExecutor e =(ThreadPoolExecutor) Executors.newFixedThreadPool(9); Log.d("pool size",e.getActiveCount()+"/"+e.getMaximumPoolSize()); if (e.getActiveCount() == e.getMaximumPoolSize()) { } */ //start loading if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) loader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url); else loader.execute(url); } private void _animate() { ValueAnimator bgAnim= ValueAnimator.ofObject(new IntEvaluator(),0,255); bgAnim.setDuration(500); bgAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { Image.this.getDrawable().setAlpha((int) (animation.getAnimatedValue())); } }); bgAnim.start(); }
}
- Java - Android: Hilo que se llama (ejecutar) dos veces
- Simple Thread Management - Java - Android
- Cómo ejecutar un hilo después de completar otro hilo
- ¿Cuál es la diferencia entre Handler, Runnable y Threads?
- Android: "Actualizar automáticamente" después de un tiempo determinado
- Cómo optimizar las aplicaciones de Android para varios núcleos
- Hilo android sigue funcionando después de presionar el botón Atrás
- ¿Puede alguien ayudarme a entender el enhebrado de mi programa?
Respondo eso antes ( aquí , aquí , aquí y aquí, y probablemente otros) y le respondo de nuevo por usted: ¡No trate de reinventar la rueda!
Imagen de carga / caché es una tarea muy compleja en Android y un montón de buenos desarrolladores ya lo hizo. Threading es sólo uno de los problemas, pero puedo ver de su código que tiene una pérdida de memoria, no hay almacenamiento en caché por lo que volverá a descargar imágenes de nuevo si desplazarse de nuevo a ella, HttpURLConnection
es una capa de red de mierda.
Por lo tanto, la forma de resolver esto (IMHO) es sólo para reutilizar el trabajo realizado por otros desarrolladores. Los buenos ejemplos de las bibliotecas que usted debe considerar para eso son:
- Picasso – https://github.com/square/picasso
- Glide – https://github.com/bumptech/glide
- Fresco – https://github.com/facebook/fresco
Picasso es mi favorito, por lo que para usarlo es necesario simplemente llamar:
Picasso.with(context).load(url).into(imgView);
Y todo se maneja para usted.
Puede comprobar si el recuento de hilos activos es igual al tamaño máximo del grupo de hilos, entonces su grupo de hilos está lleno usando este
ThreadPoolExecutor e =(ThreadPoolExecutor)Executors.newFixedThreadPool(totalnofthreads); if (e.getActiveCount() == e.getMaximumPoolSize()) { }
Para detectar si el usuario se desplaza rápidamente, puede usar onFlingListener()
recyclerView.setOnFlingListener(new RecyclerView.OnFlingListener() { @Override public boolean onFling(int velocityX, int velocityY) { isFlinging = checkFlinging(velocityY); if (isFlinging) { //Stop image loading here } return false; } }); private boolean checkFlinging(int velocityY) { return (velocityY < 0 && velocityY < -RECYCLER_VIEW_FLING_VELOCITY) || (velocityY > 0 && velocityY > RECYCLER_VIEW_FLING_VELOCITY); }
Permítanme explicar un poco, velocityY
porque uso recyclerView con desplazamiento vertical (para desplazamiento horizontal sólo cambie este parámetro a velocityX
), RECYCLER_VIEW_FLING_VELOCITY – su velocidad de fling, para mí RECYCLER_VIEW_FLING_VELOCITY = 7000
.
Acabo relized puedo envolver el código de carga con try / catch:
try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) loader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url); else loader.execute(url); } catch (RejectedExecutionException e){ e.printStackTrace(); }
Parece que esto sería una solución opcional.
En lugar de utilizar AsyncTask.THREAD_POOL_EXECUTOR
puede utilizar su propia instancia de Executor con un RejectedExecutionHandler
, por ejemplo:
private final Executor mExecutor = new ThreadPoolExecutor(0, 8, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(16), new ThreadPoolExecutor.DiscardPolicy());
Esto creará un ejecutor que ejecute un máximo de 8 subprocesos, que mantenga 16 tareas en la cola y DiscardPolicy
cualquier tarea por encima de esos límites ( DiscardPolicy
es un RejectedExecutionHandler
predefinido que hace exactamente eso). A continuación, puede pasarlo a executeOnExecutor()
.
Alternativamente, es posible que desee que las imágenes se carguen. En ese caso, puede utilizar un Executor con una cola ilimitada (es decir ilimitada). Nunca lanzará una excepción RejectedExecutionException. La clase de utilidad de ejecutores tiene algún método de fábrica agradable para crear esos:
private final Executor mExecutor = Executors.newFixedThreadPool(8);
Cuando ImageView desvinculado de la ventana, cancelar la tarea correspondiente de carga url:
public class Image extends ImageView { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if(loader!=null) loader.cancel(true); } }
En primer lugar, ¿por qué sigue utilizando AsyncTask? La excepción de ThreadPool viene porque mientras que su desplazamiento rápido el adaptador está intentando fijar una imagen a una posición que no esté disponible generalmente para parar este problema usted inhabilitaría el reciclaje pero eso haría solamente su lista lenta al manejar un sistema grande de datos . Así que le aconsejo utilizar volley para cargar la imagen es fácil de implementar y maneja el almacenamiento en caché fácilmente.
<com.android.volley.toolbox.NetworkImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/mainImage" android:scaleType="centerCrop" android:adjustViewBounds="true" android:maxHeight="270dp" />
Utilícelo en lugar de su vista de imagen y cree la clase volleySingleton para manejar todas las solicitudes de red
public class VolleySingleton { private static VolleySingleton sInstance = null; private RequestQueue mRequestQueue; private ImageLoader imageLoader; private VolleySingleton(){ mRequestQueue = Volley.newRequestQueue(Application.getAppContext()); imageLoader = new ImageLoader(mRequestQueue, new ImageLoader.ImageCache() { private final LruCache<String, Bitmap> cache = new LruCache<String, Bitmap>(200); @Override public Bitmap getBitmap(String url) { return cache.get(url); } @Override public void putBitmap(String url, Bitmap bitmap) { cache.put(url, bitmap); } }); } public static VolleySingleton getsInstance(){ if(sInstance == null){ sInstance = new VolleySingleton(); } return sInstance; } public RequestQueue getmRequestQueue(){ return mRequestQueue; } public ImageLoader getImageLoader() { return imageLoader; } }
Obtener una instancia de su clase singleton luego agregarlo a la imageView y su buena para ir
imageLoader = VolleySingleton.getsInstance().getImageLoader(); networkImageVeiw.setImageUrl(imageUrl, imageLoader);
Cree una clase extendiendo android.app.Application para que pueda obtener el contexto en la clase volleySingleton
public class Application extends android.app.Application { private static Application sInstance; public static Application getsInstance() { return sInstance; } public static Context getAppContext() { return sInstance.getApplicationContext(); } public static AppEventsLogger logger(){ return logger; } @Override public void onCreate() { super.onCreate(); sInstance = this; } }
No te olvides de ir a tu manifest.xml y agregar el atributo de nombre a la etiqueta de la aplicación para que sea el nombre de la clase de aplicación que acaba de extender
<application android:name="com.example.ApplicationClass"
Aquí es un enlace para obtener instrucciones sobre la instalación de volley y algunos consejos útiles volley biblioteca aquí
- Cómo recibir paquetes de multidifusión en Android
- Android: onCreate () se llama varias veces (y no por mí)