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:

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

}

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 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í

  • Valor devuelto del hilo
  • Thread para capturar un tiempo de espera de InputStream.read () BluetoothSocket
  • Android AsyncTask.THREAD_POOL_EXECUTOR vs ThreadPool personalizado con Runnables
  • Android Looper y pila de llamadas
  • Cómo esperar el inicio de un subproceso en java
  • ¿Cómo evito esta espera ocupada?
  • CancelaciónExcepción al utilizar ExecutorService
  • Accediendo simultáneamente a diferentes miembros del mismo objeto en Java
  • Android: AsyncTask OR normal Java threads con ExecutorService
  • ¿Por qué no se detiene el hilo?
  • Android equivalente de iOS GCD dispatch_group API
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.