SDWebImage 加載顯示 GIF 與性能問題


支持 GIF 的圖片組件 BBWebImage

SDWebImage 加載顯示 GIF 與性能問題

SDWebImage 4.0 之前,可以用 UIImageView 顯示 GIF 圖。如果 SDWebImage 4.0 還這么做,只會顯示靜態圖。SDWebImage 4.0 用 FLAnimatedImageView 通過 FLAnimatedImage 顯示 GIF 圖。本文的這兩個庫的版本分別為 SDWebImage 4.0.0 和 FLAnimatedImage 1.0.12。

CocoaPods 安裝

pod 'SDWebImage'
pod 'SDWebImage/GIF'

一般用法

用 FLAnimatedImageView 代替 UIImageView,顯示 GIF。FLAnimatedImage 的 README.md 中介紹的用法

FLAnimatedImage *image = [FLAnimatedImage animatedImageWithGIFData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://upload.wikimedia.org/wikipedia/commons/2/2c/Rotating_earth_%28large%29.gif"]]];
FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init];
imageView.animatedImage = image;
imageView.frame = CGRectMake(0.0, 0.0, 100.0, 100.0);
[self.view addSubview:imageView];

千萬別這么寫,這段代碼會阻塞主線程!在主線程通過 URL 獲取 NSData,等下載結束才執行下一步。

FLAnimatedImageView 的用法和 UIImageView 相似,初始化、設置 frame、添加到視圖上、用 UIImage 給 image 屬性賦值顯示靜態圖片;不一樣的是,用 FLAnimatedImage 給 animatedImage 屬性賦值顯示動態圖片。以上代碼的問題在於 FLAnimatedImage 的生成部分。

SDWebImage 給 FLAnimatedImageView 添加了異步加載 GIF 的方法,與異步加載靜態圖片一樣

imageView.sd_setImage(with: url, placeholderImage: placeholder)

如果顯示少量的 GIF,這樣寫應該可以。然而,如果需要用 UITableView 或 UICollectionView 展示大量 GIF,這么寫可能會有性能問題,滑動時發生頓卡。

提升性能

為了提高性能,可以指定 RunLoopMode,在 default mode 進行動畫,在 tracking mode (比如 scroll view 滑動時) 停止動畫

imageView.runLoopMode = RunLoopMode.defaultRunLoopMode.rawValue

我的代碼中,這么寫還是會有頓卡。查看 SDWebImage 的源碼,發現了問題。

sd_setImage(with:placeholderImage:) 會調用 sd_internalSetImageWithURL: 方法

注意,sd_internalSetImageWithURL: 方法中的 setImageBlock 參數,在此生成 FLAnimatedImage。進一步查看 sd_internalSetImageWithURL: 方法的實現

宏定義 dispatch_main_async_safe(block) 保證 block 在主線程中執行,其中包含 setImageBlock。因此 setImageBlock 在主線程中執行,也就是說 FLAnimatedImage 在主線程中生成,這一步比較耗時,阻塞主線程,造成頓卡。

解決辦法是,把 FLAnimatedImage 的生成放到子線程中。可以直接修改 SDWebImage 的源碼,但不建議這么做。比較好的辦法是,給 FLAnimatedImageView 添加方法

extension FLAnimatedImageView {
    
    func setImage(with url: URL?, placeholderImage: UIImage?) {
        sd_internalSetImage(with: url, placeholderImage: placeholderImage, options: SDWebImageOptions(rawValue: 0), operationKey: nil, setImageBlock: { [weak self] (image, imageData) in
            guard let strongSelf = self else { return }
            
            let imageFormat = NSData.sd_imageFormat(forImageData: imageData)
            if imageFormat == .GIF {
            	// Enter global queue
                DispatchQueue.global(qos: .userInteractive).async { [weak self] in
                	// Create FLAnimatedImage in global queue
                    let animatedImage = FLAnimatedImage(animatedGIFData: imageData)
                    DispatchQueue.main.async { [weak self] in
                        guard let strongSelf = self else { return }
                        // Set image in main queue
                        strongSelf.animatedImage = animatedImage
                        strongSelf.image = nil
                    }
                }
            } else {
            	// Set image in main queue
                strongSelf.image = image
                strongSelf.animatedImage = nil
            }
            }, progress: nil, completed: nil)
    }
}

同樣調用 sd_internalSetImageWithURL: 方法,只是修改 setImageBlock 參數,在子線程中創建 FLAnimatedImage,然后在主線程中設置圖片。

這個方法也適用於靜態圖片。如果圖片是靜態圖片,直接在主線程中設置圖片,不用進入子線程。

調用這個方法很簡單

imageView.setImage(with: url, placeholderImage: placeholder)

這樣寫,UITableView 滑動就很流暢了。

代碼已上傳 GitHub:https://github.com/Silence-GitHub/GIFDemo

這個解決方案雖然能用,但是從設計的角度看,SDWebImage 使用 FLAnimatedImage 並不合適。有興趣可以試試支持 GIF 的圖片組件 BBWebImage

轉載請注明出處:http://www.cnblogs.com/silence-cnblogs/p/6682867.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM