更多内容请访问:HarmonyOSGlide组件是一款非常不错的图片处理工具,不仅支持加载多种格式的图片,还使用了磁盘缓存和内存缓存实现图片的预加载,也可以指定图片缓存的大小,节省内存。本文将通过介绍Glide组件的GIF能力来讲解Glide加载资源的过程。从上面的GIF我们可以看到网上的一张GIF图片已经成功下载并显示在Image控件上了。我们做了什么?其实核心代码只有这一段:Glide.with(classcontext).asGif().load(uri).into(image);虽然只有这么简单的一段代码,但大家可能不知道的是,Glide在幕后默默地为我们做了成吨的工作。接下来,我们将围绕这段简单的代码来讲解Glide加载GIF的过程。1.加载过程和数据转换在开始解读Glide加载GIF的过程之前,先解释一下图片的加载过程和图片加载过程中的数据转换,方便后面对整个过程的理解。如下图是GIF加载过程:如下图是GIF加载过程中的数据转换:1.加载状态下导入的模型类型2.请求状态下获取的数据类型3.原始解码和转码后的数据数据类型4、transformation转换5、动画加载动画实现2、Glide.With()with()方法是Glide类中的一组静态方法,用于获取RequestManager对象。Glide.with(Context)过程如下:1.通过Glide.get(context)2初始化Glide。通过GlideBuilder3初始化各种配置。返回requestManagerRetriever对象4。调用RequestManagerRetriever中的get方法并通过RequestManagerFactory中的build()方法创建并返回一个RequestManager用于管理Glide请求。3、Glide.asGif()通过asGif()方法指定最终的资源转换类型为GifDrawable。如果加载的资源不是GIF,则操作失败。这里需要注意的是,如果加载的是GIF文件,即使不使用asGif()方法,只要配合DraweeView使用,归根结底还是会走GIF流程。如果用户希望解析后的GIF显示为单帧图片,则必须在asBitmap()方法中声明要求,让Glide知道只需要单帧图片而不是GIF。4.Glide.load()load()方法用于创建一个目标为Drawable的图片加载请求,传入需要加载的资源(String、URL、URI等)。由于with()方法返回一个RequestManager对象,所以很容易想象load()方法在RequestManager类中。通过调用asDrawable()方法,创建一个目标为Drawable的图片加载请求RequestBuilder。加载方法比较简单,流程也比较清晰。主要保存了用户传入的参数,包括load传入的model和RequestOption构造的参数,这些都会被记录保存,以备后续构造Request时使用。如下图:5.Glide.into()如果说是准备开胃菜,现在终于要进入主菜了,因为into()方法是整个Glide图片加载过程中逻辑最复杂的,into()方法的作用是在子线程请求网络解析图片,返回主线程绘制图片。由于into()过程非常复杂,我们将这部分分成三个小节来讲解。1、资源加载Into()方法从load()创建的图片加载请求RequestBuilder开始。在资源加载过程中,通过onSizeReady()函数获取图片控件的宽高。如果知道控件的宽高,则直接进入onSizeReady函数执行后续任务。如果控件的宽高未知,会在ViewTarget中进行监听回调,在控件有宽高后执行onSizeReady函数及后续任务。进入engine.load函数后。首先,通过loadFromMemory()函数加载activeResource中的缓存资源。如果activeResource没有找到资源,就会使用loadFromLruCache()方法去LruCache缓存中寻找资源。如果通过上述方法都没有找到缓存资源,则会启动一个新的任务进行加载。在waitForExistingOrStartNewJob()方法中创建EngineJob和DecodeJob,然后通过EngineJob执行DecodeJob和解析任务。如下图所示:2.资源解析资源加载完成后,Glide会进入资源解析,通过decodeResourceWithList()方法获取对应的解析器。代码如下privateResourcedecodeResourceWithList(DataRewinderrewinder,intwidth,inheight,Optionsoptions,Listexceptions)throwsGlideException{Resourceresult=null;for(inti=0,size=decoders.size();idecoder=decoders.get(i);try{DataTypedata=rewinder.rewindAndGet();if(decoder.handles(data,options)){data=rewinder.rewindAndGet();result=decoder.decode(data,width,height,options);}}catch(IOException|RuntimeException|OutOfMemoryError){}}returnresult;}然后传DataType,ResourceType去寻找具体的实现类,发现byteBufferGifDecoder的decode才是真正的执行者。/*GIFs*/.append(Registry.BUCKET_GIF,InputStream.class,GifDrawable.class,newStreamGifDecoder(imageHeaderParsers,byteBufferGifDecoder,arrayPool))ByteBufferGifDecoderbyteBufferGifDecoder=newByteBufferGifDecoder(context,imageHeaderParsers,bitmapPool,arrayPool);下面是ByteBufferGifDecoder的资源解析过程,解析完成后会生成一个GifDrawable回调资源。//GifDecoderGifDecoderGifDecodergifDecoder=gifDecoderFactory.build(provider,header,byteBuffer,sampleSize);gifDecoder.setDefaultBitmapConfig(config);gifDecoder.advance();PixelMapfirstFrame=gifDecoder.getNextFrame();//这里生成gifDrawableGifDrawable位于gifDrawable=newGifDrawable(上下文、gifDecoder、unitTransformation、宽度、高度、firstFrame);返回newGifDrawableResource(gifDrawable);如果资源获取成功,会执行回调通知,使用onResourceReady()将图片显示在DraweeView上。publicvoidonResourceReady(@NotNullZresource,@NullableTransitiontransition){if(transition==null||!transition.transition(resource,this)){setResourceInternal(resource);}else{maybeUpdateAnimatable(resource);}}如果资源继承Animatable将触发animatable.start()加载和绘制GIF。privatevoidmaybeUpdateAnimatable(@NullableZresource){if(resourceinstanceofAnimatable){animatable=(Animatable)resource;//GIFDrawable继承了Animatable,所以接下来的GIF流程看GIFDrawable.javaanimatable.start();}else{animatable=null;}}3.GIF加载绘制GIF加载绘制是通过将GIF解析成单帧图片,然后将单帧图片循环绘制在画布上,实现动画效果。GIF加载绘制时序图如下:3.1GIF加载GlideGlide加载GIF的原理是将GIF解码成多张图片进行无限轮播。每次帧切换都是一个图片加载请求,会清除旧帧数据,然后继续下一帧数据的加载请求,以此类推。在GIF加载绘制时序图中可以看到,ImageViewTarget中的onResourceReady触发了onStart()=>realStart()=>startRunning()。GIF为单图时,直接绘制。当GIF为多张图片时,先加载第一张,然后注册frameLoader的回调。privatevoidstartRunning(){if(state.frameLoader.getFrameCount()==1){invalidateSelf();}elseif(!isRunning){isRunning=true;state.frameLoader.subscribe(this);invalidateSelf();}else{}}//注册frameLoader的回调voidsubscribe(FrameCallbackframeCallback){booleanstart=callbacks.isEmpty();callbacks.add(frameCallback);if(start){start();}}这里是整个GIF加载的关键,通过loadNextFrame加载GIF的下一帧。privatevoidloadNextFrame(){isLoadPending=true;//获取解析器当前帧到下一帧的延迟时间intdelay=gifDecoder.getNextDelay();//获取当前系统时间+延迟时间longtargetTime=SystemClock.uptimeMillis()+延迟;//移动GIF??当前帧+1gifDecoder.advance();//创建DelayTarget任务next=newDelayTarget(handler,gifDecoder.getCurrentFrameIndex(),targetTime);//启动DelayTargetrequestBuilder.apply(signatureOf(getFrameSignature())).load(gifDecoder).into(next);}然后进入DelayTarget类执行onSourceReady()方法,使用EventHandler将PixelMap资源调到主线程定时发送解析后的资源。publicvoidonResourceReady(PixelMapresource,@NullableTransitiontransition){this.resource=resource;InnerEventinnerEvent=InnerEvent.get(FrameLoaderCallback.MSG_DELAY,this);//使用handler发送消息,这里会发送解析后的资源定时给FrameLoaderCallbackhandler.sendTimingEvent(innerEvent,targetTime);}FrameLoaderCallback是EventHandler的实现类,用于接收EventHandler发送的任务,触发onFrameReady函数。privateclassFrameLoaderCallbackextendsEventHandler{staticfinalintMSG_DELAY=1;staticfinalintMSG_CLEAR=2;@SyntheticFrameLoaderCallback(){super(EventRunner.getMainEventRunner());}@OverrideprotectedvoidprocessEvent(InnerEventevent){if(event.eventId==MSG_DELAY){DelayTargettarget=(Delay.objectTarget)event/接收到消息,触发onFrameReady函数onFrameReady(target);return;}elseif(event.eventId==MSG_CLEAR){DelayTargettarget=(DelayTarget)event.object;requestManager.clear(target);}return;}}当最后一帧加载完成后,GifFrameLoader类中的onFrameReady(target)方法触发绘制回调操作,然后进入下一帧加载GIF。同时通过FrameLoaderCallback.MSG_CLEAR清除旧帧数据。清零后,通过loadNextFrame()再次加载下一帧,实现GIF循环不断加载下一帧的过程,直到加载完整个GIF。voidonFrameReady(DelayTargetdelayTarget){//触发GifDrawable.java的绘制回调操作if(delayTarget.getResource()!=null){recycleFirstFrame();DelayTargetprevious=current;current=delayTarget;for(inti=callbacks.size()-1;i>=0;i--){FrameCallbackcb=callbacks.get(i);//GifFrameLoader中注册的GifDrawable会收到onFrameReady回调通知cb.onFrameReady();}if(previous!=null){//这里之前的target被清理了InnerEventinnerEvent=InnerEvent.get(FrameLoaderCallback.MSG_CLEAR,previous);handler.sendEvent(innerEvent);}}//加载下一帧,形成一个gif循环不断执行这个过程loadNextFrame();}3.2GIF绘制GIF绘制是通过invalidateSelf()方法通知DraweeView重新绘制解析出的图片。在绘制过程中,invalideDraweeView通过调用GifDrawable的drawToCanvas()方法将图片绘制到Canvas上。GifDrawable类中onFrameReady()调用的invalidateSelf()函数用于执行绘制任务publicvoidonFrameReady(){//如果没有找到Callback的实现控制,则停止绘制最后一帧if(findCallback()==null){stop();invalidateSelf();return;}//执行绘制过程invalidateSelf();if(getFrameIndex()==getFrameCount()-1){//循环次数loopCount++;}//非无限循环并在达到设置的最大值时停止gifif(maxLoopCount!=LOOP_FOREVER&&loopCount>=maxLoopCount){stop();}}publicvoidinvalidateSelf(){finalCallbackcallback=getHmCallback();if(callback!=null){//回调这里是注册Callback函数的组件,这里是DraweeViewcallback.invalidateDrawable(this);}else{}}通过调用setImageElement(((RootShapeElement)resource))方法实现了Callback接口。protectedvoidsetResource(@NullableElementresource){if(resourceinstanceofPixelMapElement){view.setPixelMap(((PixelMapElement)resource).getPixelMap());}elseif(resourceinstanceofRootShapeElement){view.setImageElement(((RootShapeElement)resource));}}publicvoidsetImageElement(Element){if(element==null){//如果设置的内容为null,则刷新图片,清除之前的东西invalidate();return;}super.setImageElement(element);element.setCallback(this::onChange);if(elementinstanceofRootShapeElement){//将组件注册到RootShapeElement((RootShapeElement)element).setHmCallback(this);}}最后通过drawToCanvas()方法生成一个空白的PixelMap交给GifDrawable进行绘制,以及根据scaleMode()方法将其重置为上次生成图像的位置。privatevoidinit(Contextcontext){setBindStateChangedListener(this);addDrawTask(this::drawToCanvas);setTouchEventListener(this::onTouchEvent);}privatevoiddrawToCanvas(Componentcomponent,Canvascanvas){if(getImageElement()instanceofRootShapeElement){RootShapeElement=rootShapeElement(RootShapeElement=rootShapeEle);intrw=rootShapeElement.getIntrinsicWidth();intrh=rootShapeElement.getIntrinsicHeight();intcw=component.getWidth();intch=component.getHeight();PixelMap.InitializationOptionsopts=newPixelMap.InitializationOptions();opts.size=newSize(rw,rh);opts.pixelFormat=PixelFormat.ARGB_8888;opts.editable=true;PixelMapgifmap=PixelMap.create(opts);//生成空白PixelMap交给GifDrawable绘制applyDrawToCanvas(gifmap);RectFloatsrc=newRectFloat(0,0,cw,ch);//根据scaleMode重新设置最终生成图片的位置RectFloatdst=scaleTypeFixed(gifmap,component);PixelMapHolderpixelMapHolder=newPixelMapHolder(gifmap);canvas.drawPixelMapHolderRect(pixelMapHolder,src,dst,getGifDrawPaint());}}privatevoidapplyDrawToCanvas(PixelMaptargetBitmap){BITMAP_DRAWABLE_LOCK.lock();try{CanvascanvasRootShape=newCanvas(newTexture(targetBitmap));//给RootShapeElement画布,gifDrawable会调用RootShapeElement的DrawToCanvas绘制getImageElement().drawToCanvas(clearRoot(Shape));canvasRootShape);}finally{BITMAP_DRAWABLE_LOCK.unlock();}}至此,整个GIF流程就走完了。非常巨大。所以在使用GIF的时候,一定要坚持使用完后及时释放资源。这里因为HarmonyOS和Android的生命周期不同,所以在DraweeView中开启了stopGif()方法。当您的GIF不被使用时,请先调用stopGif()以防止内存泄漏。重要提示:1、目前GIF必须配合DraweeView使用。2、如果Glide使用了一个长生命周期的context,比如applicationContext,调用drawingview的stopGif方法让Glide在GIF页面结束时停止,减少资源浪费。3.如果想使用Glid的GIF能力,但是原生Image不支持这个功能,因为Image和Element是独立的,不能用Element重绘。要支持GIF,您需要一个自定义图像。详情请参考DraweeView的实现源码地址:https://gitee.com/openharmony-tpc/glide更多信息请访问:与华为官方共建的鸿蒙技术社区https://harmonyos.51cto。com