2017-06-05 52 views
9

在我的應用程序有RecyclerView與噸it.Images圖像加載爲用戶滾動RecyclerView與此代碼:安卓:java.util.concurrent.ThreadPoolExecutor中

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) 
     loader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,url); 
    else 
     loader.execute(url); 

不幸的是,有時,當用戶滾動這個快錯誤發生:

Task [email protected] rejected from 
[email protected][Running, pool size = 9, 
active threads = 9, queued tasks = 128, completed tasks = 279] 

有沒有辦法檢測poolExecutor是否已滿並跳過圖像加載?

整個圖像類:

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

}

+2

可能重複的[android應用程序崩潰時超過線程池9和排隊的任務128](https://stackoverflow.com/questions/34395004/android-app-crashes-when-超線程池9和排隊任務128) –

+1

而不是創建一個新的異步任務上傳創建一個圖像,只需創建一個並上傳所有圖像。 –

+0

它引發單個AsyncTask。 java.lang.IllegalStateException:無法執行任務:任務已在運行。 – undefined

回答

3

我只是relized我可以換加載代碼的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(); 
} 

看起來這將是可選的解決方案。

4

您可以檢查活動的線程數等於線程池的最大大小,然後你的線程池已滿通過使用此

ThreadPoolExecutor e =(ThreadPoolExecutor)Executors.newFixedThreadPool(totalnofthreads); 
if (e.getActiveCount() == e.getMaximumPoolSize()) 
{ 

} 
+0

謝謝,'totalnofthreads'是什麼? – undefined

+0

你的線程池的大小,因爲它在你的問題logcat中顯示9。 [email protected] [Running,pool size = 9,active threads = 9,queued tasks = 128,completed tasks = 279] –

+0

剛剛嘗試e.getActiveCount()總是返回0,儘管有些活動線程。 – undefined

2

要檢測用戶是否快速滾動,可以使用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); 
} 

讓我解釋一下,velocityY因爲我用recyclerView與垂直滾動(移動水平滾動條只是這個參數更改爲velocityX),RECYCLER_VIEW_FLING_VELOCITY - 你一扔速度,對我來說RECYCLER_VIEW_FLING_VELOCITY = 7000

2

而不是使用AsyncTask.THREAD_POOL_EXECUTOR的你可以用一個RejectedExecutionHandler使用自己的Executor實例,例如:

private final Executor mExecutor = new ThreadPoolExecutor(0, 8, 
      1, TimeUnit.SECONDS, 
      new LinkedBlockingQueue<Runnable>(16), 
      new ThreadPoolExecutor.DiscardPolicy()); 

這將創建一個運行最多8個線程的執行者,保持16級的任務隊列中,並丟棄所有超出這些限制的任務(DiscardPolicy是預定義的RejectedExecutionHandler,完全是這樣)。然後您可以將它傳遞給executeOnExecutor()

或者,您可能希望圖像最終加載。在這種情況下,你可以使用帶有無限(即無限制)隊列的Executor。它永遠不會拋出RejectedExecutionException。 Executors實用工具類中提供了創建這些一些不錯的工廠方法:

private final Executor mExecutor = Executors.newFixedThreadPool(8); 
6

我之前回答(herehereherehere和可能其他人),我再爲您一一解答:不要試圖重新發明車輪!

圖像加載/緩存是一個非常複雜的任務在Android和許多很好的非常開發人員已經這樣做。線程只是其中一個問題,但我可以從你的代碼中看到你有內存泄漏,沒有緩存,所以如果再次滾動它,你會重新下載圖像,HttpURLConnection是一個糟糕的網絡層。

所以,解決這個問題的方法(恕我直言)它只是重用其他開發人員完成的工作。你應該考慮庫的很好的例子對於那些:

畢加索是我最喜歡的,所以使用它,你需要只需撥打電話:

Picasso.with(context).load(url).into(imgView); 

這一切都是手帶領你。

+0

你在哪裏看到內存泄漏? 我用這條線連接緩存圖像。setUseCaches(true);'我看不到其他問題,我已經用try/catch解決了這個問題 – undefined

+0

@undefined AsyncTask有一個引用ImageView的引用Activity引用的上下文。線程正在運行,Activity被破壞 - >內存泄漏。 – Budius

+0

我在AsyncTask中看不到引用'ImageView'(除了this引用不會導致泄漏) – undefined

2

時的ImageView從窗口分離,取消載入網址的相應任務:

public class Image extends ImageView { 

    @Override protected void onDetachedFromWindow() { 
    super.onDetachedFromWindow(); 
    if(loader!=null) 
     loader.cancel(true); 
    } 

} 
1

首先,你爲什麼還在使用的AsyncTask? ThreadPool異常即將到來,因爲當您快速滾動適配器嘗試將圖像設置爲不再可用的位置時,通常會停止此問題,您將禁用回收,但這隻會在處理大量數據時使列表變慢。所以我建議你使用volley進行圖像加載,很容易實現,並且很容易處理緩存。

 <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" 
      /> 

到位您的ImageView的使用上面,並創建volleySingleton類來處理所有的網絡請求

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

讓你的單例類的實例,然後將其添加到ImageView的和你的好去

 imageLoader = VolleySingleton.getsInstance().getImageLoader(); 
    networkImageVeiw.setImageUrl(imageUrl, imageLoader); 

創建擴展android.app.Application類,所以你可以在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; 
} } 

不要忘了去你的manifest.xml和name屬性添加到應用程序標籤是你剛纔擴展

<application 
    android:name="com.example.ApplicationClass" 

在這裏你的應用程序類的名稱是一個鏈接以獲取關於安裝排除和一些有用的技巧的指示volley library here

+0

爲什麼你認爲'AsyncTask'太糟糕了?我認爲這些失敗的請求會出現在屏幕上不存在的圖像上,所以我可以用try/catch來跳過它們。如果你知道'AsyncTask'的其他任何缺點,我會喜歡聽它。 – undefined

+0

你可以使用try catch,但是如果你沒有明確地處理異常,但只是打印棧跟蹤,爲什麼不避免它發生,除非代碼明確不能沒有try catch寫入。除此之外,我對AsyncTask沒有任何反應,只是它在這種情況下並不是最好的使用,也許當android不像現在那樣發展的時候,它已經很有用了,但現在有更好的工具來模擬你的網絡,這些工具是異步的, 。如果他插入一個數據庫,那麼AsyncTask將是最好的解決方案。 – Smilecs

+0

只是爲了糾正一個印象,AsyncTask是數據庫操作的最佳選擇。目前建議使用加載器,因爲如果AsyncTasks使用不當可能會導致內存泄漏。 – Smilecs