图片模块封装:Glide高级使用+使用设计模式图片封装+Bitmap尺寸压缩和质量压缩+Bitmap加载大图长图

一.如何更换图片框架

框架设计过程中对于对于架构要求高内聚低耦合,图片加载框架中引
入三方框架提示开发效率,对于技术选型后的方案可能后面需求的变更原三方
sdk无法满足当前业务需求,故而需要更换原有sdk,为了将更改降到最低,所
有前面设计图片加载框架时要考虑当前这个风险点

使用设计模式来进一步解耦代码耦合度,来解决隔离风险点的目的,
即定义接口层业务依赖抽象即当前接口,具体实现有不同三方sdk完成。
因为业务依赖的是接口层对应后期代码维护更改量会控制在最小,对于原软件
稳定性影响也会极小达到更换图片加载框架的目的。

二.策略模式+构建者模式图片框架搭建

1.ImageOptions图片参数设置

* @Author : yaotianxue * @Time : On 2023/5/22 15:04 * @Description : ImageOptions图片设置 class ImageOptions ( var placeImage : Int , //占位符 var errorImage : Int , //错误图 var isStaticImage : Boolean , //是否为静态图片 var isGif : Boolean , //是否为动图 var imageSize : Array < Int > , //图片大小 var skipMemoryCache : Boolean , //关闭内存缓存 var skipDiskCache : Boolean //关闭磁盘缓存 constructor ( builder : Builder ) : this ( builder . placeImage , builder . errorImage , builder . isStaticImage , builder . isGif , builder . imageSize , builder . skipMemoryCache , builder . skipDiskCache class Builder { var placeImage : Int = 0 //占位符 var errorImage : Int = 0 //错误图 var isStaticImage : Boolean = true //是否为静态图片 var isGif : Boolean = false //是否为动图 var imageSize : Array < Int > = arrayOf ( 0 , 0 ) //图片大小 var skipMemoryCache : Boolean = false //关闭内存缓存 var skipDiskCache : Boolean = false //关闭磁盘缓存 fun setPlaceImage ( placeImage : Int ) : Builder { this . placeImage = placeImage return this fun setErrorImage ( errorImage : Int ) : Builder { this . errorImage = errorImage return this fun build ( ) : ImageOptions { return ImageOptions ( this )

2.IImageLoader接口以及实现子类

* @Author : yaotianxue * @Time : On 2023/5/22 15:03 * @Description : IImageLoader interface IImageLoader { * 加载本地图片到指定图片控件 fun loadFileIntoImageView ( context : Context , file : File , target : ImageView , config : ImageOptions ) * 加载网络图片到指定图片控件 fun loadUrlIntoImageView ( context : Context , url : String , target : ImageView , config : ImageOptions ) * 加载资源图片到指定图片控件 fun loadResourceIntoImageView ( context : Context , source : Int , target : ImageView , config : ImageOptions ) * 加载Uri图片到指定图片控件 fun loadUriIntoImageView ( context : Context , uri : Uri , target : ImageView , config : ImageOptions ) * 加载二进制数组到指定图片控件 fun loadByteArrayIntoImageView ( context : Context , bytes : Array < Byte > , target : ImageView , config : ImageOptions )

GlideImageLoader/FrscoImageLoader/PicassoImageLoader

* @Author : yaotianxue * @Time : On 2023/5/22 16:50 * @Description : GlideImageLoader class GlideImageLoader : IImageLoader { override fun loadFileIntoImageView ( context : Context , file : File , target : ImageView , config : ImageOptions , loadImageView ( context , file , target , config ) override fun loadUrlIntoImageView ( context : Context , url : String , target : ImageView , config : ImageOptions , loadImageView ( context , url , target , config ) override fun loadResourceIntoImageView ( context : Context , source : Int , target : ImageView , config : ImageOptions , loadImageView ( context , source , target , config ) override fun loadUriIntoImageView ( context : Context , uri : Uri , target : ImageView , config : ImageOptions , loadImageView ( context , uri , target , config ) override fun loadByteArrayIntoImageView ( context : Context , bytes : Array < Byte > , target : ImageView , config : ImageOptions , loadImageView ( context , bytes , target , config ) private fun loadImageView ( context : Context , source : Any , target : ImageView , config : ImageOptions , //内存泄漏:该回收的资源无法被回收掉 context是activity,当页面销毁该回收activity,但是由于Glide引用当前activity、 导致activity无法回收 //解决方案1:使用弱应用 //解决方案2:activity传入上下文不要传this,传入application var weakReference = WeakReference < Context > ( context ) //弱引用 if ( weakReference . get ( ) == null ) { return var builder = GlideApp . with ( context ) . load ( source ) setBuilderOptions ( builder , config ) builder . into ( target ) * 设置图片参数 private fun setBuilderOptions ( builder : GlideRequest < Drawable > , config : ImageOptions ) { config . let { var options = RequestOptions ( ) if ( config . placeImage != 0 ) { options . placeholder ( config . placeImage ) if ( config . errorImage != 0 ) { options . error ( config . errorImage ) config . imageSize . let { if ( config . imageSize . size != 2 ) { throw IllegalArgumentException ( "please set imageSize length size is 2" ) options . override ( config . imageSize [ 0 ] , config . imageSize [ 1 ] ) if ( config . skipDiskCache ) options . diskCacheStrategy ( DiskCacheStrategy . NONE ) if ( config . skipMemoryCache ) options . skipMemoryCache ( true ) builder . apply ( options )

3.图片加载策略

* @Author : yaotianxue * @Time : On 2023/5/22 18:51 * @Description : ImageStrategy enum class ImageStrategy { Glide , Picasso , Fresco

4.ImageLoaderManager

* @Author : yaotianxue * @Time : On 2023/5/23 15:56 * @Description : ImageLoaderManager class ImageLoaderManager ( var imageStrategy : ImageStrategy ) { //策略模式创建不同的ImageLoader private var imageLoader : IImageLoader = when ( imageStrategy ) { ImageStrategy . Glide -> GlideImageLoader ( ) ImageStrategy . Fresco -> FrescoImageLoader ( ) ImageStrategy . Picasso -> PicassoImageLoader ( ) * 加载不同的资源类型 fun loadIntoImageView ( context : Context , source : Any , target : ImageView , config : ImageOptions ) { //is 判断source数据类型 as 是强转 when ( source ) { is String -> imageLoader . loadUrlIntoImageView ( context , source , target , config ) is File -> imageLoader . loadFileIntoImageView ( context , source , target , config ) is Uri -> imageLoader . loadUriIntoImageView ( context , source , target , config ) is Int -> imageLoader . loadResourceIntoImageView ( context , source , target , config ) is Array < * > -> imageLoader . loadByteArrayIntoImageView ( context , source as Array < Byte > , target , config )

6.业务模块中使用:

* @Author : yaotianxue * @Time : On 2023/5/23 16:03 * @Description : ImageUtils class ImageUtils { companion object { val manager = ImageLoaderManager ( ImageStrategy . Glide ) val options : ImageOptions = ImageOptions . Builder ( ) . setPlaceImage ( R . mipmap . ic_launcher ) . setErrorImage ( R . mipmap . ic_launcher ) . setSkipDiskCache ( false ) . setSkipMemoryCache ( true ) . build ( ) fun loadImageView ( context : Context , source : Any , target : ImageView ) { manager . loadIntoImageView ( context , source , target , options )
ImageUtils.loadIageView(this,"",iv)

三.Glide配置

1.依赖:

config.gradle配置:

 //Glide
    // Glide集成OkHttp时需要使用的库,库已经将需要适配Okhhtp的大部分代码封装,注意如果之前已经使用了okhttp依赖注释掉
    libIntegration =  'com.github.bumptech.glide:okhttp3-integration:4.13.0'
    libGlide = 'com.github.bumptech.glide:glide:4.13.0'
    libGlideCompiler = 'com.github.bumptech.glide:compiler:4.13.0'//Glide注解处理器的依赖

library-base封装网络

   //glide图片框架
    api libGlide
    api libIntegration
    kapt libGlideCompiler

项目结构:
在这里插入图片描述

2.缓存配置:

* @Author : yaotianxue * @Time : On 2023/5/22 17:03 * @Description : MyGlideModule 配置glide缓存 @GlideModule class CacheGlideModule:AppGlideModule() { override fun applyOptions(context: Context, builder: GlideBuilder) { //super.applyOptions(context, builder) //设置内存缓存大小:根据机器自动计算 // var memorySizeCalculator = MemorySizeCalculator.Builder(context).build() // builder.setMemoryCache(LruResourceCache(memorySizeCalculator.memoryCacheSize.toLong())) //设置内存缓存大小:10M builder.setMemoryCache(LruResourceCache(10*1024*1024)) //设置磁盘缓存大小:500M 默认250M 设置磁盘缓存文件夹名称 "my_image" 默认 "image_manager_disk_cache" builder.setDiskCache(InternalCacheDiskCacheFactory(context,"my_image",500*1024*1024))//修改磁盘缓存的文件夹和磁盘大小

3.网络配置:glide默认使用httpUrlConnection完成网络请求,可以改成okhttp

* @Author : yaotianxue * @Time : On 2023/5/22 17:07 * @Description : OkhttpGlideModule:配置okhttp认证所有证书,可以认证自定义ca证书 @GlideModule class OkhttpGlideModule:LibraryGlideModule() { override fun registerComponents(context: Context, glide: Glide, registry: Registry) { // super.registerComponents(context, glide, registry) var client = OkHttpClient.Builder() .sslSocketFactory(sSLSocketFactory,trustManager) .build() registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(client)) /** 获取一个SSLSocketFactory */ val sSLSocketFactory: SSLSocketFactory get() = try { val sslContext = SSLContext.getInstance("SSL") sslContext.init(null, arrayOf(trustManager), SecureRandom()) sslContext.socketFactory } catch (e: Exception) { throw RuntimeException(e) /** 获取一个忽略证书的X509TrustManager */ val trustManager: X509TrustManager get() = object : X509TrustManager { override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) { } override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) { } override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }

四.Bitmap三级缓存二次采样

传送门走你!!

五.长图大图处理

https://www.jianshu.com/p/5ec13b295dd0

import android.app.appsearch.GetByDocumentIdRequest; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.InputDevice; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; * @Author : yaotianxue * @Time : On 2023/5/22 19:47 * @Description : LargeImageView public class LargeImageView extends View implements GestureDetector.OnGestureListener { private BitmapRegionDecoder mDecoder; //绘制的区域 private volatile Rect mRect = new Rect(); private int mScaledTouchSlop; //分别记录上次的滑动的坐标 private int mLastX = 0; private int mLastY = 0; //图片的宽度和高度 private int mImageWidth,mImageHeight; //手势控制器 private GestureDetector mGestureDetector; //Bitmap工厂参数配置 private BitmapFactory.Options mOptions; private String name; public void setName(String name) { this.name = name; public LargeImageView(Context context) { super(context); init(context,null); public LargeImageView(Context context, AttributeSet attrs) { super(context, attrs); init(context,attrs); public LargeImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context,attrs); public LargeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(context,attrs); private void init(Context context, AttributeSet attrs) { //设置图片参数,如果对图片要求高采用ARGB_8888 mOptions = new BitmapFactory.Options(); mOptions.inPreferredConfig = Bitmap.Config.RGB_565; mScaledTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); Log.d("ytx", "init: "+mScaledTouchSlop); //初始化手势控制器 mGestureDetector = new GestureDetector(context,this); InputStream inputStream = null; try { inputStream = context.getResources().getAssets().open("demo.jpg"); //初始化BitmapRegionDecoder,并用他显示图片 mDecoder = BitmapRegionDecoder.newInstance(inputStream,false); //设置为true只采取图片的宽度和高度,不加载进内存 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeStream(inputStream,null,options); mImageHeight = options.outHeight; mImageWidth = options.outWidth; } catch (IOException e) { e.printStackTrace(); }finally { if(inputStream != null){ try { inputStream.close(); } catch (IOException e) { e.printStackTrace(); //把触摸事件交给手势控制器处理 @Override public boolean onTouchEvent(MotionEvent event) { return mGestureDetector.onTouchEvent(event); @Override public boolean onDown(MotionEvent e) { mLastX = (int) e.getRawX(); mLastY = (int) e.getRawY(); return true; @Override public void onShowPress(MotionEvent e) { @Override public boolean onSingleTapUp(MotionEvent e) { return false; @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { int x = (int) e2.getRawX(); int y = (int) e2.getY(); move(x,y); return true; //移动的时候更新图片的显示的区域 private void move(int x, int y) { int deltaX = x - mLastX; int deltaY = y - mLastY; if(mImageWidth > getWidth()){ mRect.offset(-deltaX,0); if(mRect.right < mImageWidth){ mRect.right = mImageWidth; mRect.left = mImageWidth - getWidth(); if(mRect.left < 0){ mRect.left = 0; mRect.right = getRight(); invalidate(); if(mImageHeight > getHeight()){ mRect.offset(0,-deltaY); if(mRect.bottom > mImageHeight){ mRect.bottom = mImageHeight; mRect.top = mImageHeight - getHeight(); if(mRect.top < 0){ mRect.top = 0; mRect.bottom = getHeight(); invalidate(); mLastX = x; mLastY = y; @Override public void onLongPress(MotionEvent e) { mLastX = (int) e.getRawX(); mLastY = (int) e.getRawY(); @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { int x = (int)e2.getRawX(); int y = (int) e2.getRawY(); move(x,y); return true; @Override protected void onDraw(Canvas canvas) { Bitmap bitmap = mDecoder.decodeRegion(mRect,mOptions); canvas.drawBitmap(bitmap,0,0,null); @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMeasuredWidth(); int height = getMeasuredHeight(); int imageWidth = mImageWidth; int imageHeight = mImageHeight; mRect.left = imageWidth/2 - width/2; mRect.top = imageHeight/2 - height/2; mRect.right = mRect.left +width; mRect.bottom = mRect.top + height; 网络配置:glide默认使用httpUrlConnection完成网络请求,可以改成okhttp。sdk无法满足当前业务需求,故而需要更换原有sdk,为了将更改降到最低,所。入三方框架提示开发效率,对于技术选型后的方案可能后面需求的变更原三方。因为业务依赖的是接口层对应后期代码维护更改量会控制在最小,对于原软件。即定义接口层业务依赖抽象即当前接口,具体实现有不同三方sdk完成。框架设计过程中对于对于架构要求高内聚低耦合,图片加载框架中引。使用设计模式来进一步解耦代码耦合度,来解决隔离风险点的目的,
前言:这一节里面我们将介绍Glide如何对图片进行压缩,这一点在加载图片较多或者加载图片像素很高的程序里面至关重要1.Android图片显示相关知识这里会讲一下图片显示相关的基础知识,如果不关心的可以直接跳到第二点,不过建议是最好看一下1.1图片质量分类安卓图片显示的质量配置主要分为四种: ARGB_8888 :32位图,带透明度,每个像素占4个字节 ARGB_4444 :16位图,带透明度,每个
等比例缩放图片在聊天列表中比较常见,而不是显示固定宽高的图片。最近对IM项目迁移到Androidx时,顺便升级了glide,发现glide等比例缩放图片出现bug(自定义ImageViewTarget实现图片缩放),第一次能正常加载,第二次无法正常等比例缩放。原来项目是使用glide 3.7.0,现在是使用gilde 4.11.0 (4.10.0开始支持AndroidX) 不同版本...
项目中遇到,需要用户上传图片的场景。结果用户上传的、特别是拍摄后的图片,分辨率很大,长宽2000多3000甚至更高,一个图片5MB以上。 造成之后,从网络上加载这些图片,比较慢。 所以,不得不在上传前进行压缩后,再上传。