編輯:關於Android編程
在ListView中加載圖片是非常常見的場景,圖片的加載要滿足下面的幾個要求:
(1)不管圖片是位於網絡還是本地,加載都不應該是同步的,而是應該異步去加載,比如用AsyncTask。
(2)為了避免重復下載圖片和頁面展示的速度,一般要做緩存,比如最常見的LruCache。
(3)為了提高Listview的性能,我們一般會用holder來重用Listview的item。
代碼大概就是這樣的:
public class MainActivity extends Activity { private ImageLoader imageLoader; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageLoader = new ImageLoader(new ImageDownloader(){ @Override public Bitmap download(String path, int width, int height) { return HttpUtil.download(path); } }); final ListView listview = (ListView)this.findViewById(R.id.listview); Button btn = (Button)this.findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ListdataList = getDataList(); listview.setAdapter(new ListViewAdapter(MainActivity.this, dataList)); } }); } @Override protected void onDestroy() { super.onDestroy(); imageLoader.destory(); } private class ListViewAdapter extends BaseAdapter{ private Context context; private List dataList; public ListViewAdapter(Context context, List dataList){ this.context = context; this.dataList = dataList; } @Override public int getCount() { return dataList.size(); } @Override public Object getItem(int position) { return dataList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { Holder holder = null; if(convertView == null){ holder = new Holder(); convertView = new ItemView(context); holder.itemView = (ItemView)convertView; convertView.setTag(holder); }else{ holder = (Holder)convertView.getTag(); } ItemView itemView = holder.itemView; ImageView itemImageView = itemView.getImageView(); ItemBean item = dataList.get(position); // 先設置一個默認的圖片 // 假如不設置,當頁面滑到了某個正在加載的item上,恰好這個item是復用的前面的已經顯示的item // 那麼這個item首先會顯示前一個item的圖片,等自己的下載完成以後,再替換掉這個圖片, // 假如下載時間很長,會讓用戶感覺圖片錯亂了! itemImageView.setImageResource(R.drawable.ic_launcher); //隨後下載實際的圖片 imageLoader.loadImage(item.getImagePath(), 50, 50, itemImageView); return itemView; } class Holder{ ItemView itemView; } }
現在問題就出現了,考慮下面的場景:
下載一幅圖片的時間很長,比如說10s,每一頁顯示3個item。
用戶第一次打開頁面,第一頁應該展示item0,item1,item2。在item0還沒下載完的時候,用戶滑到了第3頁,第3頁應該展示的是item6,item7,item8。那麼這一頁的item肯定是重用的第一頁的那些item。此時,用戶等待頁面加載。假如,item6重用的是item0,item7重用的是item1,item8重用的是item2,當item0下載完成以後,item6上展示的是item0上的圖片,這就混亂了!只有當item6自己的圖片下載完以後,item6展示的才是正確的圖片!如果在加載的過程中,用戶不停的滑動,那麼用戶看到的頁面就是完全錯亂的!
本文的圖片加載器就可以避免這個問題,是一個同事寫的,感覺很不錯,就直接拿過來了,看下代碼:
public class ImageLoader { private static final String TAG = "ImageLoader"; private ImageCache cache; private HashSet源碼在這裡:http://download.csdn.net/download/goldenfish1919/7320823cacheKeys = new HashSet (); private ImageDownloader downloader; // 保存filepath和ImageView的關系,因為ImageView會復用,所以只有這個關系才是正確的關系 // 一個imageView只能對應一個filepath,一個filepath對應一個物理文件 private WeakHashMap imageView2FileMap = new WeakHashMap (); // 一個filepath可能對應多個imageView,因為有可能會有多個imageView顯示同一張圖片 private HashMap > file2ImageViewMap = new HashMap >(); // 正在讀的或者已經在列隊裡的filepath,讀完刪除 private HashSet fileInLoadSet = new HashSet (); public ImageLoader(ImageDownloader downloader) { if(downloader == null){ throw new RuntimeException("ImageDownloader can not be null"); } this.cache = ImageCache.getInstance(); this.downloader = downloader; } /** * 給imageView設置圖片 * * @param filePath * 圖片路徑 * @param width * 寬 * @param height * 高 * @param imageView * @return 緩存中有,直接設置,並返回true,沒有異步讀取,讀完再設置,返回false */ public boolean loadImage(String filePath, int width, int height, ImageView imageView) { String filePathKey = getKeyForFilePath(filePath, width, height); Bitmap bmp = cache.get(filePathKey); if (bmp == null) { ImageViewReference imageViewRef = new ImageViewReference(imageView); // 更新imageView和filepath的最新的關系 imageView2FileMap.put(imageView, filePathKey); HashSet imageViewSet = file2ImageViewMap.get(filePathKey); if (imageViewSet == null) { imageViewSet = new HashSet (); file2ImageViewMap.put(filePathKey, imageViewSet); } imageViewSet.add(imageViewRef); // 不會重復下載 if (fileInLoadSet.contains(filePathKey)) { return false; } else { fileInLoadSet.add(filePathKey); } Holder holder = new Holder(); holder.width = width; holder.height = height; holder.filePath = filePath; holder.filePathKey = filePathKey; holder.imageViewRef = imageViewRef; new ImageLoadTask().execute(holder); return false; } else { imageView.setImageBitmap(bmp); return true; } } private class ImageLoadTask extends AsyncTask { @Override protected Holder doInBackground(Holder... params) { Holder holder = params[0]; int width = holder.width; int height = holder.height; String filePath = holder.filePath; String filePathKey = holder.filePathKey; // 找到key對應的所有imageView,如果imageView的數量是0說明不用下載了 int count = getCountOfImageViewForKey(filePathKey); if (count <= 0) { return null; } try { Random rnd = new Random(); Thread.sleep((int) (1000 * rnd.nextDouble())); } catch (Exception e) { e.printStackTrace(); } // 開始讀取,放入cache if(downloader != null){ //Bitmap bmp = ImageUtil.compressBitmap(filePath, width, height); Bitmap bmp = downloader.download(filePath, width, height); if(bmp != null){ cache.put(filePathKey, bmp); cacheKeys.add(filePath); holder.imageData = bmp; } } return holder; } @Override protected void onPostExecute(Holder holder) { super.onPostExecute(holder); // 讀完圖片,把key移除 String filePathKey = holder.filePathKey; fileInLoadSet.remove(filePathKey); Bitmap data = holder.imageData; if(data == null){ return; } ArrayList imageViewArrayList = getImageViewListForKey(filePathKey); if (imageViewArrayList.size() == 0) { return; } // 遍歷imageview列表,通過imageView2FileMap查找該imageView對應的最新的latestFilePathKey是不是剛剛下載好的這個filePathKey // 只有一直才需要顯示,如果不一致,說明該imageView已經被復用,對應到了新的key for (ImageView imageView : imageViewArrayList) { String latestFilePathKey = imageView2FileMap.get(imageView); if (latestFilePathKey != null && latestFilePathKey.equals(filePathKey)) { if (imageView != null) { imageView.setImageBitmap(data); Log.e(TAG, "設置圖片 "); /* * boolean isSet; * try{ * isSet=(Boolean) * imageView.getTag(); * }catch(Exception e) { * isSet=true; * } * if(isSet) { * imageView.setImageBitmap(result); * Log.e(TAG,"設置圖片 "); * } */ } // 即使不remove,也會自動回收 imageView2FileMap.remove(imageView); } else { } } file2ImageViewMap.remove(filePathKey); } } class Holder { int width,height; String filePath, filePathKey; Bitmap imageData; ImageViewReference imageViewRef; } private String getKeyForFilePath(String imagePath, int width, int height) { return imagePath + "_" + width + "_" + height; } /** * 銷毀ImageLoader * * */ public void clear(){ imageView2FileMap.clear(); file2ImageViewMap.clear(); fileInLoadSet.clear(); for(String cacheKey : cacheKeys){ cache.remove(cacheKey); } cacheKeys.clear(); imageView2FileMap = null; file2ImageViewMap = null; fileInLoadSet = null; cacheKeys = null; downloader = null; cache = null; } /** * 銷毀ImageLoader, 應用退出的時候調用 * * */ public void destory() { clear(); ImageCache.destroy(); } public interface ImageDownloader{ public Bitmap download(String path,int width, int height); } /** * 通過file2ImageViewMap獲取filePath對應的所有imageView列表 同時刪除被回收的imageView, * * @param filePathKey * @return */ private ArrayList getImageViewListForKey(String filePathKey) { ArrayList imageViewArrayList = new ArrayList (); HashSet imageViewReferences = file2ImageViewMap.get(filePathKey); if(imageViewReferences == null){ return null; } Iterator it = imageViewReferences.iterator(); while (it.hasNext()) { ImageViewReference reference = it.next(); if (reference.get() != null) { imageViewArrayList.add(reference.get()); } else { it.remove(); } } return imageViewArrayList; } /** * 獲取指定的filePath對應的有效imageView的數量 * * @param filePathKey * @return */ private int getCountOfImageViewForKey(String filePathKey) { ArrayList imageViewArrayList = getImageViewListForKey(filePathKey); if(imageViewArrayList == null){ return 0; }else{ return imageViewArrayList.size(); } } private static class ImageCache extends LruCache { private static final int cacheSize = 10 * 1024 * 1024; private static ImageCache instance = new ImageCache(cacheSize); public static ImageCache getInstance(){ return instance; } private ImageCache(int maxSize) { super(maxSize); } @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); } public static void destroy(){ if(instance == null){ return; } instance.evictAll(); instance = null; } } private static class ImageViewReference extends WeakReference { public ImageViewReference(ImageView r) { super(r); } @Override public boolean equals(Object o) { ImageViewReference other=(ImageViewReference)o; return this.get()==other.get(); } @Override public int hashCode() { ImageView imageView = this.get(); if(imageView != null){ return imageView.hashCode(); } return 0; } } }
0x1 開始Anddroid上的ART從5.0之後變成默認的選擇,可見ART的重要性,目前關於Dalvik Hook方面研究的文章很多,但我在網上
本文實例講述了Android持久化技術之SharedPreferences存儲。分享給大家供大家參考,具體如下:1、SharedPreferences存儲在前面一篇文章《
(項目中需要實現圖片輪番效果,就查資料著重學習,本地圖片實現)原理就是利用定時任務器定時切換ViewPager的頁面,根據圖片個數動態生成下端的圓點。效果圖: 1、獲取本
關於IPC應該不用多介紹了,Android系統中的進程之間不能共享內存,那麼如果兩個不同的應用程序之間需要通訊怎麼辦呢?比如公司的一個項目要更新,產品的需求是依附於當前項