大图显示内存优化

作者: 海到尽头天为岸 | 来源:发表于2017-10-17 16:39 被阅读133次
    背景:最近做一个游戏化的项目,项目中会加载许多比较大的场景图。经过测试一张9000*7000高清的png场景图加载到内存会消耗200M以上的内存空间,就这一点就是开发所不可接受的。还有什么加载时间长、卡顿等问题就不说了。

    接下来介绍一下我的优化历程:
    1、当时赶项目进度,我就直接把图片等比压缩成4000*3111像素,内存降到了50M,也就暂且接受了。
    缺点:大家都清楚了,图片清晰度得不到保证了,还需要进行缩放。

    2、我直接交UI帮我把PNG图片转换成了JPG,这样图片大小得到了缩减,而且加载到内存中也消耗很少的内存资源。对于图片透明度等无要求的需求,这样也是一种不错的方式。
    缺点:不适合需要保留图片透明度的场景。

    3、我把大图用PS拆分成了54张小图。在scrollViewDidScroll代理方法中计算当前应该显示的图片,然后手动释放掉不需要显示的图片。这种方式内存消耗确实非常小,而且加载的是PNG图片。
    缺点:滑动时scrollViewDidScroll方法调用非常频繁,所以会非常频繁的遍历这些图片哪些会显示,哪些需要释放,频繁的读写文件也带来了性能问题。

    注:以上三种方式均通过UIImage.init(contentsOfFile: filePath)方式加载图片(保证内存可以及时的释放掉)

    相关代码如下:

    let deviceWidth: CGFloat = UIScreen.main.bounds.width
    let deviceHeight: CGFloat = UIScreen.main.bounds.height
    func initFrameView() {
            self.scrollView.frame = CGRect(x: 0, y: 0, width: deviceWidth, height: deviceHeight)
            //设置滑动范围
            self.scrollView.contentSize = CGSize(width: 9514, height: 6388)
            //设置最大缩放比例
            self.scrollView.maximumZoomScale = 1.0
            //设置最小缩放比例
            self.scrollView.minimumZoomScale = 0.3
            //关闭缩放反弹
            self.scrollView.bouncesZoom = false
            //关闭遇边框反弹
            self.scrollView.bounces = false
            self.scrollView.delegate = self
            self.view.addSubview(self.scrollView)
            
            imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 9514, height: 6388))
            self.scrollView.addSubview(imageView)
            
            for i in 0..<54 { //拆分成小图片的张数
                let imageV = UIImageView.init(frame: CGRect(x: CGFloat(i%6)*imageWidth, y: CGFloat(i/9)*imageHeight, width: imageWidth, height: imageHeight))
                imageV.tag = i+1
                imageView.addSubview(imageV)
            }
        }
    

    UIScrollViewDelegate的代理方法:

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
            return imageView //缩放的View
        }
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            let views = self.imageView.subviews //取出UIImageView上的所有小块UIimageView
            for view in views {
                if view is UIImageView {
                    //通过该方法判断这个View是否在UIScrollView的当前可视范围内
                    let contain = self.calculateFrame(view: view)
                    let imageV = view as! UIImageView
                    if contain == true { //如果在就显示出来
                        //根据该View的tag值找到它对应该显示的图片进行显示
                        self.imageOfFile(tag: imageV.tag, imageV: imageV)
                    } else { //不在就释放掉
                        imageV.image = nil
                    }
                }
            }
        }
    
    func imageOfFile(tag:Int,imageV:UIImageView) {
            DispatchQueue.global().async { //在子线程中进行文件的读取操作
                let filePath = Bundle.main.path(forResource: String.init(format: "image_%d",tag), ofType: "jpg")!
                DispatchQueue.main.async { //在主线程中进行显示操作
                    imageV.image = UIImage.init(contentsOfFile: filePath)
                }
            }
        }
        
        //判断该view是否在scrollView的可视范围内
        func calculateFrame(view:UIView) -> Bool {
            let scrollX = self.scrollView.contentOffset.x
            let scrollY = self.scrollView.contentOffset.y
            let scale = self.scrollView.zoomScale //scrollView当前的缩放系数
            
            if fabsf(Float((scrollX+deviceWidth/2) - view.center.x*scale)) <= Float(view.width/2*scale + deviceWidth/2) &&
                fabsf(Float((scrollY+deviceHeight/2)-view.center.y*scale)) <= Float(view.height/2*scale + deviceHeight/2) {
                return true
            }
            return false
        }
    

    4、最后发现了一种更加简便的方式来显示大图。-----CATiledLayer
    苹果推荐的大图显示方式,API会自动把大图拆分成若干小贴片,在需要显示的时候才会进行加载。自带渐入的效果。
    缺点:滑动的时候比较消耗CPU,通过CPU操作减小了内存的损耗。

    func addTiledLayer() {
            scrollView = UIScrollView.init(frame: self.view.bounds)
            self.scrollView.contentSize = CGSize(width: 9514, height: 6388)
            self.scrollView.maximumZoomScale = 1.0
            self.scrollView.minimumZoomScale = 0.3
            self.scrollView.bouncesZoom = false
            self.scrollView.bounces = false
            self.scrollView.delegate = self
            self.view.addSubview(scrollView)
            
            imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 9514, height: 6388))
            self.scrollView.addSubview(imageView)
            
            tiledLayer.frame = CGRect(x: 0, y: 0, width: 9514, height: 6388)
            tiledLayer.delegate = self
            //tiledLayer.tileSize的默认大小是256*256
            scrollView.contentSize = tiledLayer.frame.size
            imageView.layer.addSublayer(tiledLayer)
            tiledLayer.setNeedsDisplay()
        }
    

    CALayerDelegate的代理方法:

    //我使用PS把大图切割成了950张小图
    func draw(_ layer: CALayer, in ctx: CGContext) {
            let layer = layer as! CATiledLayer
            let bounds = ctx.boundingBoxOfClipPath
            let x:Int = Int(floor(bounds.origin.x / layer.tileSize.width))//列数
            let y:Int = Int(floor(bounds.origin.y / layer.tileSize.height))//行数
            //加载贴图
            let tileImage = UIImage.init(named: String.init(format: "test_%02ld.png",38*y+x))  //38是大图切割的最大列数
            UIGraphicsPushContext(ctx)
            tileImage?.draw(in: bounds)
            UIGraphicsPopContext()
        }
    

    我通过PS进行的图片切割,当然也可以通过代理来实现,然后从沙盒中把图片拷贝出来,代码如下:

    //切图保存在沙河中
        func cutImageAndSave() {
            let filePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last
            let imageName = String.init(format: "%@/test-00-00.png", filePath!)
            let tileImage = UIImage.init(contentsOfFile: imageName)
            if tileImage != nil {
                return
            }
            let image = UIImage.init(named: "city") //大图
            let imageV = UIImageView.init(image: image)
            let WH:CGFloat = imageWidth
            let HH:CGFloat = imageHeight
            let size = image?.size
            
            //ceil 向上取整
            let rows:Int = Int(ceil((size?.height)! / HH))
            let cols:Int = Int(ceil((size?.width)! / WH))
            
            for y in 0..<rows {
                for x in 0..<cols {
                    let subImage = self.captureView(theView: imageV, fra: CGRect(x: x*Int(WH), y: y*Int(HH), width: Int(WH), height: Int(HH)))
                    let path = String.init(format: "%@/test-%02ld-%02ld.png", filePath!,x,y)
                    do {
                        try UIImagePNGRepresentation(subImage)?.write(to: URL.init(fileURLWithPath: path))
                    } catch {
                        print("error")
                    }
                }
            }
        }
    
        //切图
        func captureView(theView:UIView, fra:CGRect) -> UIImage {
            UIGraphicsBeginImageContext(theView.frame.size)
            let context = UIGraphicsGetCurrentContext()
            theView.layer.render(in: context!)
            
            //获取图片
            let img = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            let ref = img?.cgImage?.cropping(to: fra)
            let i = UIImage.init(cgImage: ref!)
            return i
        }
    

    如有不对的地方欢迎指正。

    相关文章

      网友评论

        本文标题:大图显示内存优化

        本文链接:https://www.haomeiwen.com/subject/rcyduxtx.html