大图显示内存优化

背景:最近做一个游戏化的项目,项目中会加载许多比较大的场景图。经过测试一张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
    }

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,391评论 25 707
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,169评论 11 349
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 12,016评论 4 62
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,946评论 1 16
  • 2016.9.4 小时候,从乡下看城市的灯火 总觉得是个神话 那么多五光十色的路灯 还有川流不息的车 能走在城市的...
    风木吟阅读 254评论 0 1