話說生產者-消費者模型可是並發模型中的一個經典案例了,你可能會問,這種並發情況下,一般服務端程序用的比較多把,Android端的應用程序哪有什么並發情況。雖然事實如此,但是構建生產者-消費者模型,是線程間協作的思想,工作線程的協助是為了讓UI線程更好的完成工作,提高用戶體驗。比如,下面的一種情況:
這個是我們平常開發中很常見的一種情景,大量的圖片資源的訪問,因為圖片訪問是一個網絡耗時的任務,如果完全交由UI線程去處理,顯然用戶體驗不佳,只能在適配器(Adapter)中的getView()方法做網絡異步請求。很多人,都通過第三方框架來實現異步的效果,雖然圖片加載的處理要比我們好很多,但是用戶體驗的效果還是不佳。在比如說像圖片的那樣,如何做到異步加載,這都是由工作線程協助UI線程去完成的,使用生產者-消費者模型則有助於提高的用戶體驗。
1.生產者-消費者模型的構造
在這里,我可以提前准備一個隊列或者集合,作為緩沖區,把用戶拖動作為生產者,因為用戶一拖動就會調度getView()方法,那么我們在getView()方法就像緩沖區存放網絡請求的任務進去。那么,消費者就是我們的工作線程,我們在工作線程將任務取出,並且加載到內存中,由Hander來切換到UI線程中,完成更新。更為主要的是,如果將任務放入隊列中或者什么時間取出任務;從隊列中取出哪個任務;什么時候執行任務;怎樣執行任務……這個決策權完全由我們掌握,這樣,我們就把UI線程的壓力給釋放出來了。
2.實現模型
- 用Vector來實現隊列的效果,當然我們的選擇也有很多。
- 第二步我們要做的是,實現GridView/ListView的滾動監聽
mGridView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_FLING:
isTouch = false;
isFlying = true;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
isTouch = false;
isFlying = false;
if (adapter != null) {
adapter.notifyDataSetChanged();
}
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
isFlying = false;
isTouch = true;
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
}
});
這樣我們就知道,用戶使用的狀態了。
- 其次,我們要在Adapter中啟動一條工作線程,充當消費者。
if(null == workThread){
workThread = new Thread(){
public void run() {
while(isLoad){
//這里停頓主要是為了達到瀑布流的效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(! vector.isEmpty() ){
//這里仿照微信里面的倒序加載圖片
String url = vector.remove(vector.size()-1);
if(mHandler != null)
//消耗任務並且更新UI
mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));
}
else{
try {
//如果隊列為空,則等待
synchronized (workThread) {
workThread.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
workThread.start();
}
}
- 在Adapter的getView()方法,放入任務,生產者所做的事情。
//標記ImageView,為了后續執行完任務后,將圖片放到這個ImageView上
iv.setTag(Images.imageThumbUrls[position]);
ImageContainer image = ImageMaps.get(Images.imageThumbUrls[position]);
if(image != null){
//這里防止內存中緩存的圖片已回收
if(image.getBitmap() != null){
iv.setImageBitmap(image.getBitmap());
}
} else {
if(!isFlying){
//這里防止getView()方法多次調用,導致放入重復的任務
//或者可以使用Set來存放任務
if(!vector.contains(Images.imageThumbUrls[position]))
{
//放任務
vector.add(Images.imageThumbUrls[position]);
}
//通知消費者去取任務
synchronized (workThread) {
workThread.notify();
}
}
}
- 在Handler中,將任務交給Volley處理,並且更新UI。
private static class MyHandler extends Handler{
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
public void handleMessage(Message msg) {
if(msg.what == UPDATE_IMAGE){
try {
String url = (String) msg.obj;
//找出我們標記的ImageView
SquareImageView iv = (SquareImageView)mActivity.get().mGridView.findViewWithTag(url);
//更新UI之后,我們再將從網絡上獲取的圖片資源,再放到一層硬緩存中
//目的是為了不再加載已經加載過的圖片
mActivity.get().ImageMaps.put(url,
mActivity.get().imageLoader.get(
url,
ImageLoader.getImageListener(iv, R.mipmap.aio_image_default, R.mipmap.aio_image_fail))
);
//告訴我們的硬緩存,現在應用程序已經占有多少內存空間了
//如果達到指定的空間,則清理部分圖片
mActivity.get().ImageMaps.setHasHoldMemory(
((int) Runtime.getRuntime().totalMemory())/1024/1024);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- 在此之前,指定一層硬緩存(不一定要加,可以選擇加上)。選擇繼承的是LinkedHashMap,內部采用了LRU算法,即不常用的實體,最先被清理。
/** * Created by Nipuream on 2016/5/19 0019. */
public class LruHashMap<K,V> extends LinkedHashMap<K,V>{
private int maxMemory = 1024*10;
private int totalMemory = 0;
private boolean isRemoveOldest = true;
public void setMaxMemory(int max){
maxMemory = max;
}
public void setRemoveOldest(boolean isRemoveOldest){
this.isRemoveOldest = isRemoveOldest;
}
public void setHasHoldMemory(int totalMemory){
this.totalMemory = totalMemory;
}
protected boolean removeEldestEntry(Map.Entry eldest) {
//如果應用程序現在占據的內存空間加上10MB已經要大於系統指定給我們最大的內存空間
//那趕緊清理老的圖片
if(isRemoveOldest){
if((totalMemory + 10) > maxMemory){
return true;
}else{
return false;
}
}
return false;
}
}
到此我們的生產者-消費者模型就已經構建完了,這樣一來,用戶的體驗就會提升一個檔次,不相信的可以下載代碼,運行下,哈哈。當然,如果你覺得代碼繁瑣,還有種更為簡單的方式,我們可以采用java並發庫里面的工具ConcurrentLinkedQueue來充當我們的隊列,當然這個隊列就是真的隊列,采用的是先進先出的行為,我們就不能對任務的取出的順序進行操作了,也就達不到倒序加載或者隨機加載了的效果了。但是代碼非常的簡潔。
//隊列
private ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();
//生產者
if(!queue.contains(Images.imageThumbUrls[position])){
queue.offer(Images.imageThumbUrls[position]);
}
//消費者,這里雖然不是原子操作,但是考慮到只有一個線程對它操作,所以就沒有同步了。
if(!queue.isEmpty()){
String url = queue.poll();
if(mHandler != null){
mHandler.sendMessage(mHandler.obtainMessage(UPDATE_IMAGE, url));
}
}
ConcurrentLinkedQueue隊列的特點就是內部采用了CAS算法,是一種非阻塞的同步隊列,所以就沒必要我們對其加鎖了。所以對ConcurrentLinkedQueue不熟悉的話,可以看這篇文章並發編程網腦補下。
如果對文章代碼邏輯不清楚的也可以下載我的源碼參考下,代碼地址鏈接如下: