图片加载是几乎每个客户端都要用到的功能,这几天闲来无事,以妹子图客户端为例学习了一下android的图片加载。现在整理一下,一来便于自己理解记忆,二来和同样希望学习这方面知识的同学交流,三来贴出自己的代码希望大家能够指点一二。

好了,废话不多说,进入正题。

数据接口来自干货集中营
网上的图片框架有很多,这里使用的是volley。不熟悉的同学可以看一下郭大神的博客

首先讲一下基本思路。
图片加载最见得粗暴的方式就是直接从网上下载图片并显示。但是每次从网上下载会非常耗时,所以一般都会加入图片的缓存来优化加载速度。缓存的话一般有两种,缓存在内存和缓存在本地磁盘。具体关于缓存的知识同样可以看一下郭大神的博客。所以整个加载图片的思路就是首先发送一次请求获取图片的url,然后加载图片,首先从内存取,内存中没有则从磁盘缓存中加载,还是没有的话则从网上下载并添加到磁盘缓存。

先上效果图

就是这样一个简单的瀑布流效果,下面的按钮我给它添加了一个返回最上面的功能。图片是默认的图片,不影响效果,我也懒得改了。大家将就一下。
好了,下面代码。
布局文件就是一个recyclerview,没什么好讲的,我就不贴了,直接上逻辑控制的代码。

mainactivity:

public class MainActivity extends AppCompatActivityimplements Callback{private static final String TAG = "MainActivity";private RecyclerView recyclerView;private ImageWallAdapter imageWallAdapter;private StaggeredGridLayoutManager layoutManager;private List<String> imageDataList;//图片urlprivate GankTask gankTask;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);setSupportActionBar(toolbar);FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);fab.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//返回顶部layoutManager.smoothScrollToPosition(recyclerView,null,0);}});imageDataList = new ArrayList<>();
//        imageDataList = Arrays.asList(TestImageUrl.imageThumbUrls);recyclerView = (RecyclerView) findViewById(R.id.recycler_view);recyclerView.setHasFixedSize(true);layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);//上拉加载更多recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {@Overridepublic void onScrolled(RecyclerView recyclerView, int dx, int dy) {super.onScrolled(recyclerView, dx, dy);}@Overridepublic void onScrollStateChanged(RecyclerView recyclerView, int newState) {super.onScrollStateChanged(recyclerView, newState);if(newState == RecyclerView.SCROLL_STATE_IDLE){int[] posititons = new int[layoutManager.getSpanCount()];layoutManager.findLastVisibleItemPositions(posititons);for(int position : posititons){if(position == imageDataList.size() - 1){Log.d(TAG,"add more");gankTask.nextPage();//获取下一页内容}}}}});recyclerView.setLayoutManager(layoutManager);imageWallAdapter = new ImageWallAdapter(this, imageDataList);recyclerView.setAdapter(imageWallAdapter);gankTask = new GankTask(this,this);//每页20张图gankTask.getData(1);//获得数据}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {// Inflate the menu; this adds items to the action bar if it is present.getMenuInflater().inflate(R.menu.main, menu);return true;}@Overridepublic boolean onOptionsItemSelected(MenuItem item) {// Handle action bar item clicks here. The action bar will// automatically handle clicks on the Home/Up button, so long// as you specify a parent activity in AndroidManifest.xml.int id = item.getItemId();//noinspection SimplifiableIfStatementif (id == R.id.action_settings) {return true;}return super.onOptionsItemSelected(item);}@Overridepublic void addData(List<String> list) {//添加修改的数据int pos = imageDataList.size();
//        imageDataList.clear();imageDataList.addAll(list);
//        imageWallAdapter.notifyDataSetChanged();imageWallAdapter.notifyItemRangeInserted(pos,list.size());}
}

MyApplication:这个类用来全局控制RequestQueue

/*** Created by Lee on 2015/11/22.*/
public class MyApplication extends Application {private static final String TAG = "MyApplication";private RequestQueue requestQueue;private static MyApplication applicationInstance;@Overridepublic void onCreate() {super.onCreate();applicationInstance = this;requestQueue = Volley.newRequestQueue(getApplicationContext());}public static synchronized MyApplication getInstance(){return applicationInstance;}public RequestQueue getRequestQueue(){if (requestQueue == null){requestQueue = Volley.newRequestQueue(getApplicationContext());}return requestQueue;}public <T> void addRequest(Request<T> request){request.setTag(TAG);requestQueue.add(request);}public void cancleRequest(){requestQueue.cancelAll(TAG);}}

Util:工具类,用来生成图片的key。因为图片url中会有特殊字符,直接用来作为文件名不太合适,所以通过md5加密后用来作为文件名。

/*** 工具类* Created by Lee on 2015/11/20.*/
public class Util {/*** 通过md5 生成图片对应的key** @param imagePath 图片路径* @return 图片对应的key*/public static String keyForImage(String imagePath) {String key = null;try {MessageDigest messageDigest = MessageDigest.getInstance("md5");messageDigest.update(imagePath.getBytes());key = byteToHex(messageDigest.digest());} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return key;}/*** 将二进制数组转换成十六进制** @param digest 二进制数组* @return 十六进制字符串*/private static String byteToHex(byte[] digest) {StringBuilder builder = new StringBuilder();for (byte b : digest) {String hex = Integer.toHexString(0xff & b);if (hex.length() == 1) {builder.append("0");}builder.append(hex);}return builder.toString();}
}

下面是两个比较重要的类。
GankTask:从干货集中营获取url并通过回调函数添加到数据集中

/*** 获取数据 数据来自干货集中营 http://gank.io/* Created by Lee on 2015/11/21.*/
public class GankTask {private static final String TAG = "GankTask";//数据类型: 福利 | Android | iOS | 休息视频 | 拓展资源 | 前端 | all//目前仅支持福利  剩下的日后扩充private static final String TYPE_FL = "福利";//分类数据: http://gank.avosapps.com/api/data/数据类型/请求个数/第几页private static final String DATA_URL = "http://gank.avosapps.com/api/data/%s/%d/%d";private static final int DEFAULT_COUNT = 10;//默认每页10个private static final int DEFAULT_TIMEOUT = 5000;//默认超时请求private List<String> imageUrlList;//用于存放图片urlprivate int count = DEFAULT_COUNT;//每页数量private static int currentPage = 1;//当前页数private String dataType = TYPE_FL;//数据格式private Context context;private Callback callback;//回调函数public GankTask(Context context, Callback callback) {this(context, DEFAULT_COUNT, callback);}public GankTask(Context context, int count, Callback callback) {imageUrlList = new ArrayList<>();this.count = count;this.context = context;this.callback = callback;try {dataType = URLEncoder.encode(dataType, "utf-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** 获得第page页图片url* @param page 页数*/public void getData(int page) {String dataUrl = String.format(DATA_URL, dataType, count, page);StringRequest request = new StringRequest(dataUrl, new Response.Listener<String>() {@Overridepublic void onResponse(String response) {try {JSONObject obj = new JSONObject(response);JSONArray array = obj.getJSONArray("results");imageUrlList.clear();for (int i = 0; i < array.length(); i++) {String url = array.getJSONObject(i).getString("url");//获得图片urlimageUrlList.add(url);}callback.addData(imageUrlList);} catch (JSONException e) {e.printStackTrace();}}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {//加载出错Log.e(TAG, "error:" + error.getMessage());Toast.makeText(context, "加载出错", Toast.LENGTH_SHORT).show();}});request.setRetryPolicy(new DefaultRetryPolicy(DEFAULT_TIMEOUT, 1, 1.0f));//设置请求超时MyApplication.getInstance().addRequest(request);//将消息添加到消息队列}/*** 获取下一页内容*/public void nextPage(){currentPage += 1;getData(currentPage);}/*** 回调函数* 向数据集中添加新增的数据*/public interface Callback {void addData(List<String> list);}
}

GankImageTask:用来加载图片

/*** 下载图片* Created by Lee on 2015/11/22.*/
public class GankImageTask {private static final String TAG = "GankImageTask";private LruCache<String, Bitmap> lruCache;//内存缓存private DiskLruCache diskLruCache;//磁盘缓存private DiskLruCache.Editor editor;private ImageView imageView;private Callback callback;public GankImageTask(DiskLruCache diskLruCache, ImageView imageView, LruCache<String, Bitmap> lruCache, Callback callback) {this.callback = callback;this.diskLruCache = diskLruCache;this.imageView = imageView;this.lruCache = lruCache;}public void loadImage(String imageUrl) {Bitmap bitmap = null;final String key = Util.keyForImage(imageUrl);//获得url对应的keytry {//没有缓存图片  下载图片并缓存if (diskLruCache.get(key) == null) {ImageRequest request = new ImageRequest(imageUrl, new Response.Listener<Bitmap>() {@Overridepublic void onResponse(Bitmap response) {try {editor = diskLruCache.edit(key);final OutputStream os = editor.newOutputStream(0);response.compress(Bitmap.CompressFormat.JPEG, 100, os);//保存图片到本地editor.commit();os.flush();os.close();addImageToCache(key, response);//将图片加入缓存} catch (IOException e) {e.printStackTrace();}callback.setImage(imageView, response);}}, 0, 0, ImageView.ScaleType.CENTER_CROP, Bitmap.Config.RGB_565, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {Log.e(TAG, "load image fail:" + error.getMessage());}});MyApplication.getInstance().addRequest(request);//将请求添加到请求队列}//再查询一次图片是否存在if (diskLruCache.get(key) != null) {//将图片解析出来FileInputStream fis = (FileInputStream) diskLruCache.get(key).getInputStream(0);bitmap = BitmapFactory.decodeFileDescriptor(fis.getFD());if (bitmap != null)addImageToCache(key, bitmap);//将图片加入缓存callback.setImage(imageView,bitmap);}} catch (IOException e) {e.printStackTrace();}}//    /**
//     * 将记录写入journal文件
//     */
//    void flushCache() {//        if (diskLruCache != null) {//            try {//                diskLruCache.flush();
//            } catch (IOException e) {//                e.printStackTrace();
//            }
//        }
//    }/*** 将图片加入缓存** @param key* @param bitmap*/private void addImageToCache(String key, Bitmap bitmap) {if (lruCache.get(key) == null) {lruCache.put(key, bitmap);}}public interface Callback {void setImage(ImageView imageView, Bitmap bitmap);}
}

最后就是适配器
ImageWallAdapter:recyclerview适配器

public class ImageWallAdapter extends RecyclerView.Adapter<MyViewHolder>implements GankImageTask.Callback {private static final String TAG = "ImageWallAdapter";private static final int MAX_SIZE = 10 * 1024 * 1024;private Context context;private LruCache<String, Bitmap> lruCache;//内存缓存private DiskLruCache diskLruCache;//磁盘缓存private List<String> dataList;//图片数据public ImageWallAdapter(Context context, List<String> dataList) {this.context = context;this.dataList = dataList;int maxMemory = (int) Runtime.getRuntime().maxMemory();int memorySize = maxMemory / 8;//内存的1/8作为缓存lruCache = new LruCache<String, Bitmap>(memorySize) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount();}};//图片缓存路径File cacheFile = getCacheFile(context, "thumb");
//        Log.d(TAG, "cacheFile:" + cacheFile.getPath());if (!cacheFile.exists()) {cacheFile.mkdirs();}//设置磁盘缓存try {diskLruCache = DiskLruCache.open(cacheFile, getAppVersion(context), 1, MAX_SIZE);} catch (IOException e) {e.printStackTrace();}}/*** 得到缓存文件夹* @param context* @param fileName* @return*/private File getCacheFile(Context context, String fileName) {String filePath;if (!Environment.isExternalStorageRemovable()|| Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {filePath = context.getExternalCacheDir().getPath();} else {filePath = context.getCacheDir().getPath();}return new File(filePath + File.separator + fileName);}/*** 获得版本* @param context* @return 当前版本*/private int getAppVersion(Context context) {try {PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);return info.versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 1;}@Overridepublic MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {View view = LayoutInflater.from(context).inflate(R.layout.image_item, parent, false);return new MyViewHolder(view);}@Overridepublic void onBindViewHolder(MyViewHolder holder, int position) {String key = Util.keyForImage(dataList.get(position));//缓存中不存在图片  下载图片if (lruCache.get(key) == null) {new GankImageTask(diskLruCache, holder.imageView, lruCache, this).loadImage(dataList.get(position));
//            new ImageLoadTask(context,holder.imageView,diskLruCache,lruCache,this).execute(dataList.get(position));} else {holder.imageView.setImageBitmap(lruCache.get(key));}}@Overridepublic int getItemCount() {return dataList.size();}@Overridepublic void setImage(ImageView imageView, Bitmap bitmap) {imageView.setImageBitmap(bitmap);}
}
class MyViewHolder extends RecyclerView.ViewHolder {ImageView imageView;public MyViewHolder(View itemView) {super(itemView);imageView = (ImageView) itemView.findViewById(R.id.image_item_image);}
}

好了,主要代码就是以上这些。

总结

代码写完了,用的主要是volley框架的知识,还有图片缓存的知识。不得不说volley确实是个很好用的框架,之前没有用这个框架之前每次网络请求都要自己写httpurlconnection或者httpclient确实很麻烦,现在用了volley框架之后代码简化了不少,而且volley中自带imageloader,为图片加载也做了不少简化,用的真心方便。
虽然整个客户端还是比较简单的就是,就是一个recyclerview加载网络图片,再加上缓存和上拉加载而更多(下拉刷新这里没加,有空补上)。不过还是有一些问题。

  1. 虽然加上了缓存,但是在快速滑动的时候图片加载还是会卡顿,不够流畅
  2. 瀑布流加载图片的时候有时候会出现只有一列显示图片另外一列不显示的情况
  3. 代码不够优雅还值得修改

代码中关键的部分都有注释,应该基本看得懂,如果不明白的地方欢迎留言。还有代码写的可能比较丑陋,不足之处还希望大家能够指点。

Android图片加载--妹子图客户端相关推荐

  1. Android 图片加载框架Coil使用总结

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/122040645 本文出自[赵彦军的博客] 文章目录 简介 简单使用 高斯模糊 圆角 ...

  2. 从UIL库谈Android图片加载中需要注意的事情

    Android Universal Image Loader 算是Android中最流行的图片加载库了,作者Sergey的确牛逼,能将整个Android图片加载的点点滴滴考虑的如此全面.网上研究这个开 ...

  3. Android图片加载框架最全解析(八),带你全面了解Glide 4的用法

    本文转载自郭神的Glide分析系列:http://blog.csdn.net/guolin_blog/article/details/78582548 本文同步发表于我的微信公众号,扫一扫文章底部的二 ...

  4. Android图片加载框架 Glide 4 的用法

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/78582548 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  5. Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53939176 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  6. Android图片加载框架最全解析(五)

    由此我们可以得知,在没有明确指定的情况下,ImageView默认的scaleType是FIT_CENTER. 有了这个前提条件,我们就可以继续去分析Glide的源码了.当然,本文中的源码还是建在第二篇 ...

  7. Android图片加载框架最全解析(七),实现带进度的Glide图片加载功能

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/78357251 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  8. Android图片加载框架最全解析(三),深入探究Glide的缓存机制

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/54895665 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...

  9. Android图片加载框架——Glide(Glide v4)

    原文地址 Android图片加载框架--Glide(Glide v4) 前言 android中图片加载框架有很多,所有框架最终达到的目都是在Android平台上以极度简单的方式加载和展示图片,如果我们 ...

最新文章

  1. Lisp 家族迎来新成员,函数式语言 Lux 是什么?
  2. Windows驱动程序的分类
  3. 后台传Map到ftl
  4. java椭圆 类_java 椭圆算法
  5. visio wps 流程图_科研必备:几款好用的流程图工具,助力你的论文/科研绘图
  6. 程序高手和菜鸟的区别是什么?
  7. vue动态引入外部CDN导致线上项目页面无法显示 - 看了不亏
  8. angularjs 事件指令
  9. php 远程连接 sqlserver,Linux下PHP远程连接SqlServer数据库
  10. Springcloud服务如何在Eureka安全优雅的下线
  11. gns3中怎么把服务器虚拟化,GNS3使用详解(gns3如何模拟ids)
  12. dojo省份地市级联之地市Dao接口类(四)
  13. 8位数码管静态显示c语言,数码管静态显示介绍_8位数码管静态显示程序解析
  14. iNode用户win10开热点手机连接时总显示获取IP中的解决方法
  15. 链家网页爬虫_爬虫-链家网租金数据
  16. 【春秋云境】CVE-2015-1427靶场wp
  17. 调用第三方地图app导航(高德、百度、腾讯)
  18. Nvidia 英伟达的NSight GPU 调试如何下载
  19. java技术晨讲可以讲什么内容,晨讲和晨测都是提升郑州达内小伙们技能的好方法...
  20. 信息安全快讯丨生日快乐,我的国

热门文章

  1. iOS开发-------Sqlite3实现本地存储简易通讯录
  2. Openresty宏观概述笔记
  3. 林大师讲区块链之信任机制
  4. 在线客服php技术,WeLive开源PHP在线客服系统、在线客服源码下载
  5. 手机浏览器UCWEB的成功史
  6. 2021年中国百货零售行业发展现状及发展趋势分析:百货店仍是时尚和品质消费的主渠道[图]
  7. 5000词学英语——DAY2
  8. JS javascript 睡眠
  9. 英语学习年终总结——2014 year-end summary of English learning
  10. sql常用的语句及其逻辑