前言
前面我们讲解了渐进式加载的基础,接下来我们就讲一下在app中具体如何实现渐进式加载
1.基础
关于渐进式加载的基础知识,我们已经在前面讲解过了,这里就不重复讲解了,但是不代表不重要.
2.背景
Android图片加载主要有四大框架,但是只有Fresco框架是可以支持渐进式加载(需要设置)
笔者是由于项目已经使用Glide,且跟换框架成本太大,所以才尝试用Glide实现渐进式加载,但是Glide本身框架目前是不适合做渐进式加载,里面的请求是将整个InputStream转化为bitmap后才通过回调设置给ImageView,这个过程只能走一次.笔者做了很多尝试来让Glide实现渐进式加载,虽然最后实现了这个功能,但是会破坏Glide的结构,影响内部的一些优化和设置,比如缓存和防止错位等,所以这里就不介绍这个实现过程了,但是在这个过程中,使用到一些实现渐进式加载的操作,可以介绍一下.
3.实现
首先我们定义一个类,来通过网络请求来获取InputStream(HttpUrlFetcher是glide里面的一个实现类,这里将里面的代码简化一下,拿来使用)
//HttpUrlFetcher.javapublic class HttpUrlFetcher { private static final String TAG = "HttpUrlFetcher"; private HttpURLConnection urlConnection; private int size; public int getSize() { return size; } public InputStream loadData(URL url) throws IOException { urlConnection = (HttpURLConnection) url.openConnection(); urlConnection.setConnectTimeout(2500); urlConnection.setReadTimeout(2500); urlConnection.setUseCaches(false); urlConnection.setDoInput(true); size = urlConnection.getContentLength(); final int statusCode = urlConnection.getResponseCode(); if (statusCode / 100 == 2) { return urlConnection.getInputStream(); } return null; }}
找到控件和设置图片链接(此链接的图片是支持渐进式加载的JPEG图片)
//MainActivity url = "http://www.reasoft.com/tutorials/web/img/progress.jpg"; iv1 = (ImageView) findViewById(R.id.iv1);
在子线程中请求网络并实现渐进式加载
//MainActivity.javapublic void show(View v) { isCancel = false; new Thread(new Runnable() { @Override public void run() { try { //网络请求 InputStream inputStream = httpUrl.loadData(new URL(url)); //创建一个和总数据一样大的数组来当容器 byte[] bytes = new byte[httpUrl.getSize()]; byte lastOne = 0; byte lastTwo = 0; int offest = 0; while (!isCancel) { //本次读取的字节 byte[] get = getBytes(inputStream); //放入本次读取的数据 System.arraycopy(get, 0, bytes, offest, get.length); offest = offest + get.length; //记录最后两位字符 lastOne = bytes[offest - 1]; lastTwo = bytes[offest - 2]; //替换掉最后两个字节为FFD9,否则无法转化成bitmap bytes[offest - 2] = -1; bytes[offest - 1] = -39; //生成bitmap Bitmap result = BitmapFactory.decodeByteArray(bytes, 0, offest); //还原最后两个字节 bytes[offest - 2] = lastTwo; bytes[offest - 1] = lastOne; Message obtain = Message.obtain(); Bundle bundle = new Bundle(); bundle.putParcelable(BITMAP, result); obtain.setData(bundle); handler.sendMessage(obtain); } } catch (IOException e) { e.printStackTrace(); } } }).start();}
//MainActivity.javaprivate Handler handler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); Bitmap bitmap = msg.getData().getParcelable(BITMAP); if (bitmap != null) { iv1.setImageBitmap(bitmap); } }};
//MainActivity.javapublic static byte[] getBytes(InputStream is) throws IOException { ByteArrayOutputStream outstream = new ByteArrayOutputStream(); //这里设置每次读取的数量,设置小一点是为了让效果更明显 byte[] buffer = new byte[10]; // 用数据装 int len = -1; //要实现比较理想的渐进式加载效果,其实不应该写死每次读取量,应该是根据FFDA来判断读到第几帧了,然后决定是否需要显示了 if ((len = is.read(buffer)) != -1) { outstream.write(buffer, 0, len); } else { is.close(); } outstream.close(); // 关闭流一定要记得。 return outstream.toByteArray();}
这里有几个地方要注意的 :
1.BitmapFactory.decodeByteArray方法读取的字节数组,jpg文件开头得是FFD8,结尾得是FFD9,不然的话会返回null,所以这边在每次解析的时候都将最后两位临时换成FFD9
2.这里 FF D9是十六进制的,需要转换为二进制,并且取补码,最后的字节是 -1,-39
3.记得增加网络权限
4.其他
本节代码很多地方写得不严禁,效率也不高,目的主要是介绍如何实现渐进式加载.但是笔者认为Java在处理这类情况时,其实本身效率并不高,在实际项目使用的中,如果对性能要求比较高的话,这类操作还是用C语言等底层来写,Android只是通过JNI来调用,这样更加合理和高效.