本文共 17345 字,大约阅读时间需要 57 分钟。
转自:http://blog.csdn.net/lvshaorong/article/details/51732520
前言:
加载并显示gif是App常见的一个功能,像加载普通图片一样,大体应该包含以下几项功能:
1、自动下载GIF到本地文件作为缓存,第二次加载同一个url的图片不需要下载第二遍
2、由于GIF往往较大,要显示圆形的进度条提示下载进度
3、在GIF完全下载完之前,先显示GIF的第一帧图像进行占位,完全下载完毕之后自动播放动画。
4、两个不同的页面加载同一张GIF,两个页面的加载进度应该一致
5、支持ViewPager同时加载多个GIF动图
效果演示:
项目github地址:
github上放了一个“demo效果.apk”可以下载装上看看效果,注意在网络较差的时候才能看清进度条的效果
实现思路:
1、关于下载和磁盘缓存:
我这里使用HttpConnection根据url进行下载,在下载之前先将url字符串使用16位MD5进行转换,让下载的文件名为url的MD5码,然后以4096字节为单位,使用ByteStremBuffer进行边读边写,防止下载过程中内存溢出,而且不时的向磁盘写入还可以帮助实现GIF第一帧占位的效果。
2、关于进度指示:
我这里使用了一个圆形的第三方Progress Bar和一个TextView实现,由于在下载过程中以4096为缓冲,所以每下载4096字节就会更新一次进度UI。文件总大小由http返回报文的头部的Content-length返回,通过已下载大小除以这个length得出下载百分比。
3、关于不同页面的下载同步:
用户在首页会看到一个gif,这时候点击图片可以跳进大图页继续这个gif的下载,用户在首页的下载进度到带到大图页来,不能让用户下载两遍,也不能在大图页打开一个才下载了一半的图像。
首先在下载开始之前,建立一个MD5.tmp的文件用来存储下载内容,在下载完毕之后将.tmp文件名后缀去掉,这样通过文件系统检索一个GIF是否已被下载的时候,没有下载完成的图片就不会被检索出来。
如果有一个url已经开始了一次下载,这时候又有一个下载请求同一个url,此时会将请求的imageView,textView和progressBar使用一个WeakReference引用起来,防止内存泄漏,然后把这三个空间添加到一个HashMap里去,这个HashMap的key是url,value就是这些控件的弱引用组成的list。当下载线程更新进度或完成的时候,会从这个HashMap中根据url取出所有和这张gif有关的控件,然后把这些控件统一的更新状态,这样就可以保证不同页面的控件的进度相同,也避免了一个文件下载多次的情况。
4、关于使用GIF的第一帧进行下载占位:
GIF的显示使用了github上的开源项目:-gif-drawable,地址:https://github.com/koral--/android-gif-drawable。是一个非常优秀的框架,其内部使用编写了一些效率非常高的执行代码。
这个框架的可以直接根据输入流进行加载,也就是说不用等gif文件完全下载完毕就可以显示已经下载完毕的内容,甚至可以向浏览器那样一行像素一行像素的进行加载,十分好用。
根据框架的这个特性,只需要将还没有下载好的文件直接传到Drawable里,让道gifImageView中显示即可,并且在这之前要判断能否拿到第一帧,然后设置播放选项为暂停。
5、关于VIewPager的使用
在ViewPager的Adapter使用的时候遇到了很多麻烦,主要是由于ViewPager的缓存机制引起的,会引起显示重复,无控件显示等等问题,要解决在ViewPager中的使用,并让GifImageView和普通ImageView一起在ViewPager中和平共处,需要先研究好ViewPager的缓存机制。在这里我是先根据所有图片数量生成同等多的imageView放在一个数组里,然后ViewPager切换到哪张就从数组里拿出哪张放到ViewPager的里。GIfImageVIew也是这样,不过是放在另一个数组里,根据position取得相应的GIFImageView,然后用container来add,这里对于add过一遍的GIfImageView会报异常,通过catch解决。
关于如何在项目中引入android-gif-drawable这个库,请看我的另一篇博文《》
具体代码:
加载工具类:
- import android.os.Handler;
- import android.util.Log;
- import android.view.View;
- import android.view.ViewGroup;
- import android.widget.ImageView;
- import android.widget.TextView;
-
- import java.io.BufferedInputStream;
- import java.io.BufferedOutputStream;
- import java.io.FileOutputStream;
- import java.io.OutputStream;
- import java.lang.ref.WeakReference;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.security.MessageDigest;
-
- import com.imaginato.qravedconsumer.task.AlxMultiTask;
- import com.lidroid.xutils.HttpUtils;
- import com.pnikosis.materialishprogress.ProgressWheel;
- import com.qraved.app.R;
-
- import java.io.File;
- import java.io.IOException;
- import java.security.NoSuchAlgorithmException;
- import java.util.ArrayList;
- import java.util.concurrent.ConcurrentHashMap;
-
- import pl.droidsonroids.gif.GifDrawable;
- import pl.droidsonroids.gif.GifImageView;
-
-
-
-
- public class AlxGifHelper {
-
-
- public static class ProgressViews{
- public ProgressViews(WeakReference<GifImageView> gifImageViewWeakReference, WeakReference<ProgressWheel> progressWheelWeakReference, WeakReference<TextView> textViewWeakReference,int displayWidth) {
- this.gifImageViewWeakReference = gifImageViewWeakReference;
- this.progressWheelWeakReference = progressWheelWeakReference;
- this.textViewWeakReference = textViewWeakReference;
- this.displayWidth = displayWidth;
- }
-
- public WeakReference<GifImageView> gifImageViewWeakReference;
- public WeakReference<ProgressWheel> progressWheelWeakReference;
- public WeakReference<TextView> textViewWeakReference;
- public int displayWidth;
- }
-
- public static ConcurrentHashMap<String,ArrayList<ProgressViews>> memoryCache;
-
-
-
-
-
-
-
- public static void displayImage(final String url, GifImageView gifView, ProgressWheel progressBar , TextView tvProgress, int displayWidth){
-
- String md5Url = getMd5(url);
- String path = gifView.getContext().getCacheDir().getAbsolutePath()+"/"+md5Url;
-
- JLogUtils.i("AlexGIF","gif图片的缓存路径是"+path);
- final File cacheFile = new File(path);
- if(cacheFile.exists()){
- JLogUtils.i("AlexGIF","本图片有缓存");
- if(displayImage(cacheFile,gifView,displayWidth)) {
- if (progressBar != null) progressBar.setVisibility(View.GONE);
- if (tvProgress!=null)tvProgress.setVisibility(View.GONE);
- return;
- }
- }
-
- final WeakReference<GifImageView> imageViewWait= new WeakReference<GifImageView>(gifView);
- final WeakReference<ProgressWheel> progressBarWait= new WeakReference<ProgressWheel>(progressBar);
- final WeakReference<TextView> textViewWait= new WeakReference<TextView>(tvProgress);
- if(gifView.getId()!= R.id.gif_photo_view)gifView.setImageResource(R.drawable.qraved_bg_default);
- if(memoryCache!=null && memoryCache.get(url)!=null){
- JLogUtils.i("AlexGIF","以前有别的ImageView申请加载过该gif"+url);
-
- memoryCache.get(url).add(new ProgressViews(imageViewWait,progressBarWait,textViewWait,displayWidth));
- return;
- }
- if(memoryCache==null)memoryCache = new ConcurrentHashMap<>();
- if(memoryCache.get(url)==null)memoryCache.put(url,new ArrayList<ProgressViews>());
-
- memoryCache.get(url).add(new ProgressViews(imageViewWait,progressBarWait,textViewWait,displayWidth));
-
- final HttpUtils http = new HttpUtils();
-
-
- startDownLoad(url, new File(cacheFile.getAbsolutePath()+".tmp"), new DownLoadTask() {
- @Override
- public void onStart() {
- JLogUtils.i("AlexGIF","下载GIF开始");
- ProgressWheel progressBar = progressBarWait.get();
- TextView tvProgress = textViewWait.get();
- if(progressBar!=null){
- progressBar.setVisibility(View.VISIBLE);
- progressBar.setProgress(0);
- if(tvProgress==null)return;
- tvProgress.setVisibility(View.VISIBLE);
- tvProgress.setText("1%");
- }
- }
-
- @Override
- public void onLoading(long total, long current) {
- int progress = 0;
-
- if(total>0)progress = (int)(current*100/total);
- JLogUtils.i("AlexGIF","下载gif的进度是"+progress+"%"+" 现在大小"+current+" 总大小"+total);
- ArrayList<ProgressViews> viewses = memoryCache.get(url);
- if(viewses ==null)return;
- JLogUtils.i("AlexGIF","该gif的请求数量是"+viewses.size());
- for(ProgressViews vs : viewses){
- ProgressWheel progressBar = vs.progressWheelWeakReference.get();
- if(progressBar!=null){
- progressBar.setProgress((float)progress/100f);
- if(total==-1)progressBar.setProgress(20);
- }
- TextView tvProgress = vs.textViewWeakReference.get();
- if(tvProgress != null)tvProgress.setText(progress+"%");
- }
-
- getFirstPicOfGIF(new File(cacheFile.getAbsolutePath()+".tmp"),vs.gifImageViewWeakReference.get());
- }
-
- public void onSuccess(File file) {
- if(file==null)return;
- String path = file.getAbsolutePath();
- if(path==null || path.length()<5)return;
- File downloadFile = new File(path);
- File renameFile = new File(path.substring(0,path.length()-4));
- if(path.endsWith(".tmp"))downloadFile.renameTo(renameFile);
- Log.i("AlexGIF","下载GIf成功,文件路径是"+path+" 重命名之后是"+renameFile.getAbsolutePath());
- if(memoryCache==null)return;
- ArrayList<ProgressViews> viewArr = memoryCache.get(url);
- if(viewArr==null || viewArr.size()==0)return;
- for(ProgressViews ws:viewArr){
-
- GifImageView gifImageView = ws.gifImageViewWeakReference.get();
- if (gifImageView!=null)displayImage(renameFile,gifImageView,ws.displayWidth);
-
- TextView tvProgress = ws.textViewWeakReference.get();
- ProgressWheel progressBar = ws.progressWheelWeakReference.get();
- if(progressBar!=null)progressBar.setVisibility(View.GONE);
- if(tvProgress!=null)tvProgress.setVisibility(View.GONE);
- }
- JLogUtils.i("AlexGIF",url+"的imageView已经全部加载完毕,共有"+viewArr.size()+"个");
- memoryCache.remove(url);
- }
-
- @Override
- public void onFailure(Throwable e) {
- Log.i("Alex","下载gif图片出现异常",e);
- TextView tvProgress = textViewWait.get();
- ProgressWheel progressBar = progressBarWait.get();
- if(progressBar!=null)progressBar.setVisibility(View.GONE);
- if(tvProgress!=null)tvProgress.setText("image download failed");
- if(memoryCache!=null)memoryCache.remove(url);
- }
- });
- }
-
-
-
-
-
-
-
-
-
- public static boolean displayImage(File localFile,GifImageView gifImageView,int displayWidth){
- if(localFile==null || gifImageView==null)return false;
- JLogUtils.i("AlexGIF","准备加载gif"+localFile.getAbsolutePath()+"显示宽度为"+displayWidth);
- GifDrawable gifFrom;
- try {
- gifFrom = new GifDrawable(localFile);
- int raw_height = gifFrom.getIntrinsicHeight();
- int raw_width = gifFrom.getIntrinsicWidth();
- JLogUtils.i("AlexGIF","图片原始height是"+raw_height+" 图片原始宽是:"+raw_width);
- if(gifImageView.getScaleType() != ImageView.ScaleType.CENTER_CROP && gifImageView.getScaleType()!= ImageView.ScaleType.FIT_XY){
-
- if(raw_width<1 || raw_height<1)return false;
- int imageViewWidth = displayWidth;
- if(imageViewWidth < 1)imageViewWidth = raw_width;
- int imageViewHeight = imageViewWidth*raw_height/raw_width;
- JLogUtils.i("AlexGIF","缩放完的gif是"+imageViewWidth+" X "+imageViewHeight);
- ViewGroup.LayoutParams params = gifImageView.getLayoutParams();
- if(params!=null){
- params.height = imageViewHeight;
- params.width = imageViewWidth;
- }
- }else {
- JLogUtils.i("AlexGIF","按照固定大小进行显示");
- }
- gifImageView.setImageDrawable(gifFrom);
- return true;
- } catch (IOException e) {
- JLogUtils.i("AlexGIF","显示gif出现异常",e);
- return false;
- }
- }
-
-
-
-
-
-
- public static String getMd5(String str) {
- if(str==null || str.length()<1)return "no_image.gif";
- MessageDigest md5 = null;
- try {
- md5 = MessageDigest.getInstance("MD5");
- byte[] bs = md5.digest(str.getBytes());
- StringBuilder sb = new StringBuilder(40);
- for(byte x:bs) {
- if((x & 0xff)>>4 == 0) {
- sb.append("0").append(Integer.toHexString(x & 0xff));
- } else {
- sb.append(Integer.toHexString(x & 0xff));
- }
- }
- if(sb.length()<24)return sb.toString();
- return sb.toString().substring(8,24);
- } catch (NoSuchAlgorithmException e) {
- JLogUtils.i("Alex","MD5加密失败");
- return "no_image.gif";
- }
- }
-
- public static abstract class DownLoadTask{
- abstract void onStart();
- abstract void onLoading(long total, long current);
- abstract void onSuccess(File target);
- abstract void onFailure(Throwable e);
- boolean isCanceled;
- }
-
-
-
-
-
-
-
- public static void startDownLoad(final String uri, final File targetFile, final DownLoadTask task){
- final Handler handler = new Handler();
- new AlxMultiTask<Void,Void,Void>(){
-
- @Override
- protected Void doInBackground(Void... params) {
- task.onStart();
- downloadToStream(uri,targetFile,task,handler);
- return null;
- }
- }.executeDependSDK();
- }
-
-
-
-
-
-
-
-
-
- public static long downloadToStream(String uri, final File targetFile, final DownLoadTask task, Handler handler) {
-
- if (task == null || task.isCanceled) return -1;
-
- HttpURLConnection httpURLConnection = null;
- BufferedInputStream bis = null;
- OutputStream outputStream = null;
-
- long result = -1;
- long fileLen = 0;
- long currCount = 0;
- try {
-
- try {
- final URL url = new URL(uri);
- outputStream = new FileOutputStream(targetFile);
- httpURLConnection = (HttpURLConnection) url.openConnection();
- httpURLConnection.setConnectTimeout(20000);
- httpURLConnection.setReadTimeout(10000);
-
- final int responseCode = httpURLConnection.getResponseCode();
- if (HttpURLConnection.HTTP_OK == responseCode) {
- bis = new BufferedInputStream(httpURLConnection.getInputStream());
- result = httpURLConnection.getExpiration();
- result = result < System.currentTimeMillis() ? System.currentTimeMillis() + 40000 : result;
- fileLen = httpURLConnection.getContentLength();
- } else {
- Log.e("Alex","downloadToStream -> responseCode ==> " + responseCode);
- return -1;
- }
- } catch (final Exception ex) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- task.onFailure(ex);
- }
- });
- return -1;
- }
-
-
- if (task.isCanceled) return -1;
-
- byte[] buffer = new byte[4096];
- int len = 0;
- BufferedOutputStream out = new BufferedOutputStream(outputStream);
- while ((len = bis.read(buffer)) != -1) {
- out.write(buffer, 0, len);
- currCount += len;
- if (task.isCanceled) return -1;
- final long finalFileLen = fileLen;
- final long finalCurrCount = currCount;
- handler.post(new Runnable() {
- @Override
- public void run() {
- task.onLoading(finalFileLen, finalCurrCount);
- }
- });
- }
- out.flush();
- handler.post(new Runnable() {
- @Override
- public void run() {
- task.onSuccess(targetFile);
- }
- });
- } catch (Throwable e) {
- result = -1;
- task.onFailure(e);
- } finally {
- if (bis != null) {
- try {
- bis.close();
- } catch (final Throwable e) {
- handler.post(new Runnable() {
- @Override
- public void run() {
- task.onFailure(e);
- }
- });
- }
- }
- }
- return result;
- }
-
-
-
-
-
-
- public static void getFirstPicOfGIF(File gifFile,GifImageView imageView){
- if(imageView==null)return;
- if(imageView.getTag(R.style.AppTheme) instanceof Integer)return;
- try {
- GifDrawable gifFromFile = new GifDrawable(gifFile);
- boolean canSeekForward = gifFromFile.canSeekForward();
- if(!canSeekForward)return;
- JLogUtils.i("AlexGIF","是否能显示第一帧图片"+canSeekForward);
-
-
-
-
-
-
-
- gifFromFile.seekToFrame(0);
- gifFromFile.pause();
- imageView.setImageDrawable(gifFromFile);
- imageView.setTag(R.style.AppTheme,1);
- } catch (IOException e) {
- JLogUtils.i("AlexGIF","获取gif信息出现异常",e);
- }
- }
- }
线程池: - import android.os.AsyncTask;
- import android.os.Build;
-
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
-
-
-
-
-
- public abstract class AlxMultiTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
- private static ExecutorService photosThreadPool;
- private final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
- private final int CORE_POOL_SIZE = CPU_COUNT + 1;
- public void executeDependSDK(Params...params){
- if(photosThreadPool==null)photosThreadPool = Executors.newFixedThreadPool(CORE_POOL_SIZE);
- if(Build.VERSION.SDK_INT<11) super.execute(params);
- else super.executeOnExecutor(photosThreadPool,params);
- }
-
- }
ViewPager Adpater的写法(截取) - public class PhotoImageViewPageAdapter extends PagerAdapter {
- @Override
- public Object instantiateItem(ViewGroup container, int position) {
-
- String imageUrl = "http://xxx.com/sdf/xxx.gif";
- JLogUtils.i("AlexGIF","当前图片->"+imageUrl);
- if(imageUrl.endsWith(".gif")){
- JLogUtils.i("AlexGIF","现在是gif大图");
- View rl_gif = LayoutInflater.from(activity).inflate(R.layout.layout_photo_loading_gif_imageview, null);
- GifImageView gifImageView = (GifImageView) rl_gif.findViewById(R.id.gif_photo_view);
- ProgressWheel progressWheel = (ProgressWheel) rl_gif.findViewById(R.id.progress_wheel);
- CustomTextView tv_progress = (CustomTextView) rl_gif.findViewById(R.id.tv_progress);
- AlxGifHelper.displayImage(imageUrl,gifImageView,progressWheel,tv_progress,0);
- try {
- container.addView(rl_gif);
- }catch (Exception e){
- JLogUtils.i("AlexGIF","父控件重复!!!!,这里出现异常很正常",e);
- }
- return rl_gif;
-
- }
- }
- return container;
- }
- }
布局文件 - <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:wheel="http://schemas.android.com/apk/res-auto"
- android:id="@+id/rl_gif"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <pl.droidsonroids.gif.GifImageView
- android:id="@+id/gif_photo_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_centerVertical="true"
- android:layout_centerHorizontal="true"
- />
- <TextView
- android:id="@+id/tv_progress"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textSize="15sp"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:textColor="@color/white"
- android:text="2%"
- />
- <com.pnikosis.materialishprogress.ProgressWheel
- android:id="@+id/progress_wheel"
- android:layout_width="60dp"
- android:layout_height="60dp"
- android:layout_centerHorizontal="true"
- android:layout_centerVertical="true"
- android:layout_gravity="center"
- wheel:matProg_barColor="#5097DA"
- wheel:matProg_progressIndeterminate="true" />
-
- </RelativeLayout>
中间的ProgressBar使用了一个第三方库 - dependencies {
- compile 'com.pnikosis:materialish-progress:1.7'
- }