高效加载本地相册图片的ImageLoader类

当我们相册中的图片有几千张的时候,你快速的拖动滚动条到底部,怎么样才能保证图片加载的流畅性以及避免OOM呢

1.使用Lru算法对图片进行缓存保证流畅性以及避免OOM

2.图片加载肯定是要异步进行的,那么就涉及到多线程的并发进行,使用线程池对任务进行调度

3.使用android内部的异步消息机制Looper+Handler对taskQueue进行轮询执行

1.图片加载器,task任务列频繁调用,所以要做成单例模式,编写一个单例的ImageLoader类,并声明必要的成员变量

/**
 * @author Administrator
 *	图片加载器,task任务列频繁调用,所以要做成单例模式
 */
public class ImageLoader {
	private static ImageLoader instance;
	/**
	 * 图片缓存的核心对象
	 */
	private LruCache<String, Bitmap> mLruCache;
	/**
	 * 线程池
	 */
	private ExecutorService mThreadPool;
	private static final int DEFAULT_THREAD_COUNT=1;
	/**
	 * 队列调度方式
	 */
	private static Type mType=Type.LIFO;
	/**
	 * 任务队列
	 */
	private LinkedList<Runnable> mTaskQueue;
	/**
	 * 后台轮询线程
	 */
	private Thread mPoolThread;
	private Handler mPoolThreadHandler;
	/**
	 *UI线程的Handler
	 */
	private Handler mUIHandler;
	//任务队列的
	public enum Type{
		FIFO,LIFO;
	}
	/**
	 * 获取ImageLoader的单利方法
	 * @return
	 */
	public static ImageLoader getInstance(){
		//这里采用双重if判断,可以提高代码的执行效率
		//外层判断可能会有几个线程进到里面,然后线程同步加条件判断
		//保证单例唯一性
		if(instance==null){
			synchronized (ImageLoader.class) {
				if(instance==null){
					instance=new ImageLoader(DEFAULT_THREAD_COUNT,mType);
				}
			}
		}
		return instance;
	}

2.初始化成员变量

private ImageLoader(int threadCount,Type type){
		init(threadCount,type);
	}
	private Semaphore mSemaphorePoolThreadHandler=new Semaphore(0);
	private Semaphore mSemaphoreThreadPool;
	/**
	 * 构造方法中初始化一大堆成员变量
	 * @param threadCount
	 * @param type
	 */
	private void init(int threadCount,Type type) {
		//初始化后台轮询线程
		mPoolThread=new Thread(){
			@Override
			public void run() {
				Looper.prepare();//使用安卓异步消息机制处理mtaskQueue中的任务
				mPoolThreadHandler=new Handler(){
					@Override
					public void handleMessage(Message msg) {
						//从线程池取出一个任务执行
						mThreadPool.execute(getTask());

					}
				};
				mSemaphorePoolThreadHandler.release();//handler初始化完成,添加一个许可证
				Looper.loop();
			}
		};
		//启动线程池
		mPoolThread.start();
		mTaskQueue=new LinkedList<Runnable>();//任务队列
		mThreadPool=Executors.newFixedThreadPool(threadCount);//线程池管理者
		int maxSize=(int) Runtime.getRuntime().maxMemory();
		int cacheSize=maxSize/8;
		mLruCache=new LruCache<String, Bitmap>(cacheSize){

			@Override
			protected int sizeOf(String key, Bitmap value) {
				return value.getRowBytes()*value.getHeight();
			}

		};
		mType=type;
		mSemaphoreThreadPool=new Semaphore(threadCount);
	}

3.编写和新方法loadImage()

/**
	 * 图片加载的核心方法
	 * @param path
	 * @param imageView
	 */
	public void loadImage(final String path,final ImageView imageView){
		imageView.setTag(path);//带上标记,防止条目复用时出现乱跳现象
		mUIHandler=new Handler(){
			@Override
			public void handleMessage(Message msg) {
				ImageHolder holder=(ImageHolder) msg.obj;
				Bitmap bitmap=holder.mBitmap;
				ImageView imageView=holder.mImageView;
				String url=holder.path;
				if(imageView.getTag().toString().equals(url)){
					imageView.setImageBitmap(bitmap);
				}
			}
		};

		Bitmap mBitmap=getBitmapFromLruCache(path);

		if(mBitmap!=null){
			refreshHandler(path, imageView, mBitmap);
		}
		else{
			addTask(imageView,path);
		}

	}
	private void addTask(final ImageView imageView,final String path) {
		mTaskQueue.add(new Runnable() {

			@Override
			public void run() {
				//进行加载图片的逻辑
				//1.获取ImageView要显示的大小
				ImageSize imageSize = getImageSize(imageView);
				//2.开始压缩图片
				Bitmap mBitmap = compressBitmap(imageSize,path);
				//3.把图片添加到LruCache中去
				if(mLruCache.get(path)!=null){
					mLruCache.put(path, mBitmap);
				}
				//发送消息,让UiHandler把图片显示到imageview上去
				refreshHandler(path, imageView, mBitmap);
				//任务完成,可以释放许可,让后台轮询线程去处理下一个任务
				mSemaphoreThreadPool.release();
			}
		});
		try {
			if(mPoolThreadHandler==null){
				mSemaphorePoolThreadHandler.acquire();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//发送消息,让后台轮询线程去处理任务堆
		mPoolThreadHandler.sendEmptyMessage(0X110);

	}
	/**
	 * 把ImageView path Bitmap等信息发送到handler中去执行
	 * @param path
	 * @param imageView
	 * @param mBitmap
	 */
	private void refreshHandler(final String path, final ImageView imageView, Bitmap mBitmap) {
		Message message=Message.obtain();
		ImageHolder holder=new ImageHolder();
		holder.mBitmap=mBitmap;
		holder.mImageView=imageView;
		holder.path=path;
		message.obj=holder;
		mUIHandler.sendMessage(message);
	}
	/**
	 * 压缩图片
	 * @return
	 */
	private Bitmap compressBitmap(ImageSize imageSize,String path){
		BitmapFactory.Options option=new BitmapFactory.Options();
		option.inJustDecodeBounds=true;//只解析图片的宽高等信息,而不把图片加载到内存中去
		BitmapFactory.decodeFile(path, option);
		int outWidth = option.outWidth;
		int outHeight = option.outHeight;
		int width=imageSize.width;
		int height=imageSize.height;
		int inSimpleSize=1;
		if(outHeight>height||outWidth>height){
			int widthRadio=Math.round(outWidth*1.0f/width);
			int heightRadio=Math.round(outHeight*1.0f/height);
			inSimpleSize=Math.max(widthRadio, heightRadio);
		}
		option.inJustDecodeBounds=false;
		option.inSampleSize=inSimpleSize;

		return BitmapFactory.decodeFile(path, option);
	}
	/**
	 * 根据ImageView获取适当的压缩后的宽和高
	 * @param iv
	 * @return
	 */
	private ImageSize getImageSize(ImageView iv){
		LayoutParams lp = iv.getLayoutParams();
		//获取屏幕的矩阵
		DisplayMetrics dm = iv.getContext().getResources().getDisplayMetrics();
		int width=iv.getWidth();
		if(width<=0){
			width=lp.width;
		}
		if(width<=0){
			//使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本
			width=getMaxValueFromInvoke(iv,"mMaxWidth");
		}
		if(width<=0){
			width=dm.widthPixels;
		}

		int height=iv.getHeight();
		if(height<=0){
			height=lp.width;
		}
		if(height<=0){
			height=getMaxValueFromInvoke(iv,"mMaxHeight");
		}
		if(height<=0){
			height=dm.heightPixels;
		}
		return new ImageSize(width, height);
	}
	/**
	 * 使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本
	 * @param obj
	 * @param fieldName
	 * @return
	 */
	private int getMaxValueFromInvoke(Object obj,String fieldName){
		int value=0;
		try {
			Field field = ImageView.class.getDeclaredField(fieldName);
			field.setAccessible(true);
			int fieldValue = field.getInt(obj);
			if(fieldValue>0&&fieldValue<Integer.MAX_VALUE){
				value=fieldValue;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return value;
	}
	/**
	 * 封装图片宽高的对象
	 * @author Administrator
	 *
	 */
	private class ImageSize{
		int width;
		int height;
		public ImageSize(int width,int height){
			this.width=width;
			this.height=height;
		}
	}
	//handler发送的msg.obj对象
	private class ImageHolder{
		Bitmap mBitmap;
		ImageView mImageView;
		String path;
	}
	//从缓存中去取图片
	private Bitmap getBitmapFromLruCache(String key) {

		return mLruCache.get(key);
	}
	

获取ImageView的最大宽高的时候为了兼容低版本,没使用getMaxWidth()这个api,而是采用了反射获取到的

其中后台轮询线程还没初始化mThreadPoolHandler的时候,mThreadPoolHandler就可能在loadImage()方法中被调用了,所以就存在空指针的风险

为了保证并发危险,采用java提供的一个并发信号量的类Semaphore   初始化的时候在构造方法中指定有几个许可证

有两个方法:

1.acquire()每执行一次消耗一个许可证,但许可证为0的时候,阻塞线程,直到Semaphore   再次调用release()获取到许可证,结束阻塞

2.release()每执行一次获取一个许可证

下面我再把整个类的代码全部粘过来

package com.example.fandayimageloader.imageloader;

import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v4.util.LruCache;
import android.util.DisplayMetrics;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
/**
 * 这个线程中的mPoolThreadHandler可能还没有初始化完成,但是在addTask到mTaskQueue后就要用到时,就会出现null painter exception
 * 解决这个可以使用java提供的Semaphore,并发一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),
 * 然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,
 * 并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。
 * 简单来讲就是acquire()消耗掉一个许可,而release()添加一个许可,当许可为0的时候,想要再次acquire()就会阻塞
 * public class SemaphoreTest {
 *
 *	     public static void main(String[] args) {
 *	        // 线程池
 *	        ExecutorService exec = Executors.newCachedThreadPool();
 *	        // 只能5个线程同时访问
 *	        final Semaphore semp = new Semaphore(5);
 *	        // 模拟20个客户端访问
 *	        for (int index = 0; index < 20; index++) {
 *	            final int NO = index;
 *	            Runnable run = new Runnable() {
 *	                public void run() {
 *	                    try {
 * 	                        // 获取许可
 *	                        semp.acquire();
 *                       System.out.println("Accessing: " + NO);
 *	                        Thread.sleep((long) (Math.random() * 10000));
 *	                        // 访问完后,释放 ,如果屏蔽下面的语句,则在控制台只能打印5条记录,之后线程一直阻塞
 *	                        semp.release();
 *	                    } catch (InterruptedException e) {
 *	                    }
 *	                }
 *	            };
 *	            exec.execute(run);
 *	        }
 *	        // 退出线程池
 *	        exec.shutdown();
 *	    }
 *	}
 */

/**
 * @author Administrator
 *	图片加载器,task任务列频繁调用,所以要做成单例模式
 */
public class ImageLoader {
	private static ImageLoader instance;
	/**
	 * 图片缓存的核心对象
	 */
	private LruCache<String, Bitmap> mLruCache;
	/**
	 * 线程池
	 */
	private ExecutorService mThreadPool;
	private static final int DEFAULT_THREAD_COUNT=1;
	/**
	 * 队列调度方式
	 */
	private static Type mType=Type.LIFO;
	/**
	 * 任务队列
	 */
	private LinkedList<Runnable> mTaskQueue;
	/**
	 * 后台轮询线程
	 */
	private Thread mPoolThread;
	private Handler mPoolThreadHandler;
	/**
	 *UI线程的Handler
	 */
	private Handler mUIHandler;
	//任务队列的
	public enum Type{
		FIFO,LIFO;
	}
	/**
	 * 获取ImageLoader的单利方法
	 * @return
	 */
	public static ImageLoader getInstance(){
		//这里采用双重if判断,可以提高代码的执行效率
		//外层判断可能会有几个线程进到里面,然后线程同步加条件判断
		//保证单例唯一性
		if(instance==null){
			synchronized (ImageLoader.class) {
				if(instance==null){
					instance=new ImageLoader(DEFAULT_THREAD_COUNT,mType);
				}
			}
		}
		return instance;
	}

	private ImageLoader(int threadCount,Type type){
		init(threadCount,type);
	}
	private Semaphore mSemaphorePoolThreadHandler=new Semaphore(0);
	private Semaphore mSemaphoreThreadPool;
	/**
	 * 构造方法中初始化一大堆成员变量
	 * @param threadCount
	 * @param type
	 */
	private void init(int threadCount,Type type) {
		//初始化后台轮询线程
		mPoolThread=new Thread(){
			@Override
			public void run() {
				Looper.prepare();//使用安卓异步消息机制处理mtaskQueue中的任务
				mPoolThreadHandler=new Handler(){
					@Override
					public void handleMessage(Message msg) {
						//从线程池取出一个任务执行
						mThreadPool.execute(getTask());

					}
				};
				mSemaphorePoolThreadHandler.release();//handler初始化完成,添加一个许可证
				Looper.loop();
			}
		};
		//启动线程池
		mPoolThread.start();
		mTaskQueue=new LinkedList<Runnable>();//任务队列
		mThreadPool=Executors.newFixedThreadPool(threadCount);//线程池管理者
		int maxSize=(int) Runtime.getRuntime().maxMemory();
		int cacheSize=maxSize/8;
		mLruCache=new LruCache<String, Bitmap>(cacheSize){

			@Override
			protected int sizeOf(String key, Bitmap value) {
				return value.getRowBytes()*value.getHeight();
			}

		};
		mType=type;
		mSemaphoreThreadPool=new Semaphore(threadCount);
	}
	/**
	 * 从任务队列中根据Type的值取出任务
	 * @return
	 */
	public Runnable getTask(){
		try {
			//每取一个任务就消耗一个许可,消耗完成将阻塞,等待释放许可
			mSemaphoreThreadPool.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		if(mType==Type.FIFO){
			return mTaskQueue.removeFirst();
		}
		else if(mType==Type.LIFO){
			return mTaskQueue.removeLast();
		}
		return null;
	}

	/**
	 * 图片加载的核心方法
	 * @param path
	 * @param imageView
	 */
	public void loadImage(final String path,final ImageView imageView){
		imageView.setTag(path);//带上标记,防止条目复用时出现乱跳现象
		mUIHandler=new Handler(){
			@Override
			public void handleMessage(Message msg) {
				ImageHolder holder=(ImageHolder) msg.obj;
				Bitmap bitmap=holder.mBitmap;
				ImageView imageView=holder.mImageView;
				String url=holder.path;
				if(imageView.getTag().toString().equals(url)){
					imageView.setImageBitmap(bitmap);
				}
			}
		};

		Bitmap mBitmap=getBitmapFromLruCache(path);

		if(mBitmap!=null){
			refreshHandler(path, imageView, mBitmap);
		}
		else{
			addTask(imageView,path);
		}

	}
	private void addTask(final ImageView imageView,final String path) {
		mTaskQueue.add(new Runnable() {

			@Override
			public void run() {
				//进行加载图片的逻辑
				//1.获取ImageView要显示的大小
				ImageSize imageSize = getImageSize(imageView);
				//2.开始压缩图片
				Bitmap mBitmap = compressBitmap(imageSize,path);
				//3.把图片添加到LruCache中去
				if(mLruCache.get(path)!=null){
					mLruCache.put(path, mBitmap);
				}
				//发送消息,让UiHandler把图片显示到imageview上去
				refreshHandler(path, imageView, mBitmap);
				//任务完成,可以释放许可,让后台轮询线程去处理下一个任务
				mSemaphoreThreadPool.release();
			}
		});
		try {
			if(mPoolThreadHandler==null){
				mSemaphorePoolThreadHandler.acquire();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//发送消息,让后台轮询线程去处理任务堆
		mPoolThreadHandler.sendEmptyMessage(0X110);

	}
	/**
	 * 把ImageView path Bitmap等信息发送到handler中去执行
	 * @param path
	 * @param imageView
	 * @param mBitmap
	 */
	private void refreshHandler(final String path, final ImageView imageView, Bitmap mBitmap) {
		Message message=Message.obtain();
		ImageHolder holder=new ImageHolder();
		holder.mBitmap=mBitmap;
		holder.mImageView=imageView;
		holder.path=path;
		message.obj=holder;
		mUIHandler.sendMessage(message);
	}
	/**
	 * 压缩图片
	 * @return
	 */
	private Bitmap compressBitmap(ImageSize imageSize,String path){
		BitmapFactory.Options option=new BitmapFactory.Options();
		option.inJustDecodeBounds=true;//只解析图片的宽高等信息,而不把图片加载到内存中去
		BitmapFactory.decodeFile(path, option);
		int outWidth = option.outWidth;
		int outHeight = option.outHeight;
		int width=imageSize.width;
		int height=imageSize.height;
		int inSimpleSize=1;
		if(outHeight>height||outWidth>height){
			int widthRadio=Math.round(outWidth*1.0f/width);
			int heightRadio=Math.round(outHeight*1.0f/height);
			inSimpleSize=Math.max(widthRadio, heightRadio);
		}
		option.inJustDecodeBounds=false;
		option.inSampleSize=inSimpleSize;

		return BitmapFactory.decodeFile(path, option);
	}
	/**
	 * 根据ImageView获取适当的压缩后的宽和高
	 * @param iv
	 * @return
	 */
	private ImageSize getImageSize(ImageView iv){
		LayoutParams lp = iv.getLayoutParams();
		//获取屏幕的矩阵
		DisplayMetrics dm = iv.getContext().getResources().getDisplayMetrics();
		int width=iv.getWidth();
		if(width<=0){
			width=lp.width;
		}
		if(width<=0){
			//使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本
			width=getMaxValueFromInvoke(iv,"mMaxWidth");
		}
		if(width<=0){
			width=dm.widthPixels;
		}

		int height=iv.getHeight();
		if(height<=0){
			height=lp.width;
		}
		if(height<=0){
			height=getMaxValueFromInvoke(iv,"mMaxHeight");
		}
		if(height<=0){
			height=dm.heightPixels;
		}
		return new ImageSize(width, height);
	}
	/**
	 * 使用反射获取ImageView的MaxWidth和MaxHeight兼容低版本
	 * @param obj
	 * @param fieldName
	 * @return
	 */
	private int getMaxValueFromInvoke(Object obj,String fieldName){
		int value=0;
		try {
			Field field = ImageView.class.getDeclaredField(fieldName);
			field.setAccessible(true);
			int fieldValue = field.getInt(obj);
			if(fieldValue>0&&fieldValue<Integer.MAX_VALUE){
				value=fieldValue;
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		return value;
	}
	/**
	 * 封装图片宽高的对象
	 * @author Administrator
	 *
	 */
	private class ImageSize{
		int width;
		int height;
		public ImageSize(int width,int height){
			this.width=width;
			this.height=height;
		}
	}
	//handler发送的msg.obj对象
	private class ImageHolder{
		Bitmap mBitmap;
		ImageView mImageView;
		String path;
	}
	//从缓存中去取图片
	private Bitmap getBitmapFromLruCache(String key) {

		return mLruCache.get(key);
	}

}

最后要是想在外界调用ImageLoader的时候指定Type属性和线程池最大维护线程个数的话,可以再类中增加一个getInstance的方法重载

/**
	 * 获取ImageLoader的单利方法的重载
	 * @return
	 */
	public static ImageLoader getInstance(int threadCount,Type type){
		//这里采用双重if判断,可以提高代码的执行效率
		//外层判断可能会有几个线程进到里面,然后线程同步加条件判断
		//保证单例唯一性
		if(instance==null){
			synchronized (ImageLoader.class) {
				if(instance==null){
					instance=new ImageLoader(threadCount,type);
				}
			}
		}
		return instance;
	}

项目源码下载地址:下载源码

时间: 2024-10-05 20:14:42

高效加载本地相册图片的ImageLoader类的相关文章

高效加载大的图片

Loading Large Bitmaps Efficiently 高效的加载大图 图片可能以各种各样的形状,尺寸展现出来,大多数情况下它们的展现方式,是超出一般应用程序的UI需求的.举例来说,系统的"相册"应用中展现的图片(Android设备拍摄的相片)是超过你的设备屏幕的分辨率的. 假设你在一个内存有限的环境下工作,理想情况下你只需要加载一个低分辨率的版本进入内存.相对较低的分辨率的图片应该匹配UI组件的需求,并且将它们显示出来. 在这种情况下(需求的分辨率不需要很高),一张高分辨

iOS WebView 加载本地资源(图片,文件等)

NSString *path = [[NSBundle mainBundle] pathForResource:@"关于.docx" ofType:nil]; NSURL *url = [NSURL fileURLWithPath:path]; NSLog(@"%@", [self mimeType:url]); //webview加载本地文件,可以使用加载数据的方式 //第一个诶参数是一个NSData, 本地文件对应的数据 //第二个参数是MIMEType //第

Android_优化查询加载大数量的本地相册图片

一.概述 讲解优化查询相册图片之前,我们先来看下PM提出的需求,PM的需求很简单,就是要做一个类似微信的本地相册图片查询控件,主要包含两个两部分: 进入图片选择页面就要显示出手机中所有的照片,包括系统相册图片和其他目录下的所有图片,并按照时间倒叙排列 切换相册功能,切换相册页面列出手机中所有的图片目录列表,并且显示出每个目录下所有的图片个数以及封面图片 这两个需求看似简单,实则隐藏着一系列的性能优化问题.在做优化之前,我们调研了一些其他比较出名的app在加载大数量图片的性能表现(gif录制的不够

android ImageLoader加载本地图片的工具类

import android.widget.ImageView; import com.nostra13.universalimageloader.core.ImageLoader; /** * 异步加载本地图片工具类 * * @author tony * */ public class LoadLocalImageUtil { private LoadLocalImageUtil() { } private static LoadLocalImageUtil instance = null;

as3.0加载本地或网络上的图片

加载本地或网络上的图片,我们一般只用Loader及URLRequest这两个类就可以完成,URLRequest即可以加载本地的,也可以加载网络的.代码如下 import flash.display.Loader; import flash.net.URLRequest; var loader:Loader = new Loader(); var request:URLRequest = new URLRequest('img/123.png'); loader.y = 200; loader.l

android内存分析、加载本地图片内存优化

从网上学习了MAT插件来查看内存使用情况,分析之后发现手上的应用对本地图片这边的内存损耗很大,查了相关资料之后发现,如果采用setImageBitmap.setImageResource这些来加载本地资源,会产生较大的损耗.因为这些方法在完成 decode 后,最终都是通过 Java 层的 createBitmap 来完成的,需要消耗更多内存.因此,改用先通过 BitmapFactory.decodeStream 方法,创建出一个 bitmap,再将其设为 ImageView 的 source,

Android 高效加载大图片避免OOM

参考链接:http://blog.csdn.net/coderinchina/article/details/40964205 我们项目中经常会加载图片,有时候如果加载图片过多的话,小则导致程序很卡,重则OOM导致App挂了,今天翻译https://developer.android.com/training/displaying-bitmaps/index.html,学习Google高效加载大图片的方法. 图片有各种形状和大小,但在大多数情况下,这些图片都会大于我们程序所需要的大小.比如说系统

利用js加载本地图片预览功能

直接上代码: 经测试,除safari6包括6以下不支持,其他均可正常显示. 原因:safari6不支持filereader,同时不能使用IE滤镜导致失效. fix: 可以利用canvas,解决safari6的问题 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

安卓 WebView加载本地图片时居中显示

在一个项目中使用WebView显示gif图片(自定义的View无法放大gif),当图片过小时只在左侧显示,经过研究发现无论设置android:layout_gravity="center_horizontal"还是设置android:gravity="center_horizontal" 都无法居中显示,而且还设置了android:layout_width="wrap_content",但是实际上WebView并没有自适应内容,它的宽度占了屏幕宽