列表的单元格中包含有图片在开发中很常见。通常我们可以直接在tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)中给单元格设置图片。

但有时这些图片要从远程加载,或者要给图片作裁减,添加滤镜等操作。如果这些操作还是直接在主线程中进行,由于上下拖动表格滚动条的时候,单元格渲染是实时进行的。那么单元格便会不断地进行图片加载,渲染,影响效率造成卡顿。如果图片大的话还会浪费流量。 
下面通过一个展示“热门电影”表格,演示如何进行优化。 
1,最初的代码
这个是功能的最简单实现,后面我们要在这个基础上进行改进。
UITableView里除了显示文字,还从网络下载海报图片。同时为了使CPU资源占用更明显,还给图片添加了棕褐色滤镜。
由于这些都在主线程操作,可以发现滚动表格的时候,界面比较卡。(而且下载过的图片由于没有保存本地,来回拖动滚动条会发现图片在重复下载。)

movies.plist - 存储电影名字和对应的海报url地址
(下面是部分代码片断)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<plist version="1.0">
<dict>
    <key>港囧</key>
    <key>第三种爱情</key>
    <key>碟中谍5</key>
    <key>小黄人大眼萌</key>
    <key>夏洛特烦恼</key>
    <key>魔镜</key>
    <key>长江7号</key>
    <key>九层妖塔</key>
    <key>hangge.com</key>
    <string>NO URL</string>
</dict>
</plist>

ListViewController.swift - 主页面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import UIKit
import CoreImage
 
//图片数据源地址
let dataSourcePath = NSBundle.mainBundle().pathForResource("movies", ofType: "plist")
 
class ListViewController: UITableViewController {
     
    //电影图片字典集合(使用了懒加载)
    lazy var movies = NSDictionary(contentsOfFile: dataSourcePath!)!
     
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "热门电影"
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
     
    //获取记录数
    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        return movies.count
    }
     
    //创建单元格
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
        -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier",
            forIndexPath: indexPath) as UITableViewCell
         
        //设置单元格文本
        let rowKey = movies.allKeys[indexPath.row] as! String
        cell.textLabel?.text = rowKey
         
        //设置单元格图片
        var image : UIImage?
        if let imageURL = NSURL(string:movies[rowKey] as! String) {
            let imageData = NSData(contentsOfURL:imageURL)
            let unfilteredImage = UIImage(data:imageData!)
            image = self.applySepiaFilter(unfilteredImage!)
        }
         
        if image != nil {
            cell.imageView?.image = image!
        }else{
            //cell.imageView?.image = nil //未加载到海报则空白
            cell.imageView?.image = UIImage(named: "failed") //未加载到海报显示默认的“暂无图片”
        }
         
        return cell
    }
     
    //给图片添加棕褐色滤镜
    func applySepiaFilter(image:UIImage) -> UIImage? {
        let inputImage = CIImage(data:UIImagePNGRepresentation(image)!)
        let context = CIContext(options:nil)
        let filter = CIFilter(name:"CISepiaTone")
        filter!.setValue(inputImage, forKey: kCIInputImageKey)
        filter!.setValue(0.8, forKey: "inputIntensity")
        if let outputImage = filter!.outputImage {
            let outImage = context.createCGImage(outputImage, fromRect: outputImage.extent)
            return UIImage(CGImage: outImage)
        }
        return nil
    }
}

源码下载: T1.zip

2,功能改进:使用后台线程进行图片下载,和滤镜添加

(1)通过使用NSOperation和NSOperationQueue,创建两个队列。一个用来下载图片,一个用来处理图片。由于这些不在主线程中进行,所以拖动滚动条的时候就不会卡顿了。
(2)下载和滤镜是分别实现,因为有可能在图片下载时,用户将图片划动出屏幕了,这时不会再添加滤镜。当下一次用户划到当前行时,就不需要再下载图片了;只需要添加滤镜!这样可以提高效率。
(3)为便于观察,这里将maxConcurrentOperationCount设置为1。表示一个队列中同时只有一个线程运行。(实际应用中可根据情况多开几个线程)
(4)开始的时候,会显示默认占位图片。图片下载成功后,会替换显示真实图片。图片下载失败,则显示失败图片。滤镜处理完毕后,替换显示处理后的图片。
(5)为增强用户体验,刚开始单元格尾部附件图片是一个转圈动画。一旦图片处理完毕或下载失败,便将其去除。

(1)电影条目类和状态枚举 - MovieRecord.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import UIKit
 
// 这个枚举包含所有电影图片的状态
enum MovieRecordState {
    case New, Downloaded, Filtered, Failed
}
 
// 电影条目类
class MovieRecord {
    let name:String
    let url:NSURL
    var state = MovieRecordState.New
    //默认初始图片
    var image = UIImage(named: "placeholder")
     
    init(name:String, url:NSURL) {
        self.name = name
        self.url = url
    }
}
(2)队列管理类 - MovieOperations.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Foundation
 
//队列管理类,追踪每个操作的状态
class MovieOperations {
    //追踪进行中的和等待中的下载操作
    lazy var downloadsInProgress = [NSIndexPath:NSOperation]()
    //图片下载队列
    lazy var downloadQueue:NSOperationQueue = {
        var queue = NSOperationQueue()
        queue.name = "Download queue"
        queue.maxConcurrentOperationCount = 1
        return queue
        }()
     
    //追踪进行中的和等待中的滤镜操作
    lazy var filtrationsInProgress = [NSIndexPath:NSOperation]()
    //图片处理队列
    lazy var filtrationQueue:NSOperationQueue = {
        var queue = NSOperationQueue()
        queue.name = "Image Filtration queue"
        queue.maxConcurrentOperationCount = 1
        return queue
        }()
}
(3)图片下载操作任务类 - ImageDownloader.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import UIKit
 
//图片下载操作任务
class ImageDownloader: NSOperation {
    //电影条目对象
    let movieRecord: MovieRecord
     
    init(movieRecord: MovieRecord) {
        self.movieRecord = movieRecord
    }
     
    //在子类中重载NSOperation的main方法来执行实际的任务。
    override func main() {
        //在开始执行前检查撤消状态。任务在试图执行繁重的工作前应该检查它是否已经被撤消。
        if self.cancelled {
            return
        }
        //sleep(1) //这个只是为了便于测试观察
         
        //下载图片。
        let imageData = NSData(contentsOfURL:self.movieRecord.url)
         
        //再一次检查撤销状态。
        if self.cancelled {
            return
        }
         
        //如果有数据,创建一个图片对象并加入记录,然后更改状态。如果没有数据,将记录标记为失败并设置失败图片。
        if imageData?.length > 0 {
            self.movieRecord.image = UIImage(data:imageData!)
            self.movieRecord.state = .Downloaded
        }
        else
        {
            self.movieRecord.state = .Failed
            self.movieRecord.image = UIImage(named: "failed")
        }
    }
}
(4)滤镜处理任务类 - ImageFiltration.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import UIKit
import CoreImage
 
//滤镜处理任务
class ImageFiltration: NSOperation {
    //电影条目对象
    let movieRecord: MovieRecord
     
    init(movieRecord: MovieRecord) {
        self.movieRecord = movieRecord
    }
     
    //在子类中重载NSOperation的main方法来执行实际的任务。
    override func main () {
        if self.cancelled {
            return
        }
         
        if self.movieRecord.state != .Downloaded {
            return
        }
         
        if let filteredImage = self.applySepiaFilter(self.movieRecord.image!) {
            self.movieRecord.image = filteredImage
            self.movieRecord.state = .Filtered
        }
    }
     
    //给图片添加棕褐色滤镜
    func applySepiaFilter(image:UIImage) -> UIImage? {
        let inputImage = CIImage(data:UIImagePNGRepresentation(image)!)
         
        if self.cancelled {
            return nil
        }
        let context = CIContext(options:nil)
        let filter = CIFilter(name:"CISepiaTone")
        filter?.setValue(inputImage, forKey: kCIInputImageKey)
        filter?.setValue(0.8, forKey: "inputIntensity")
        let outputImage = filter?.outputImage
         
        if self.cancelled {
            return nil
        }
         
        let outImage = context.createCGImage(outputImage!, fromRect: outputImage!.extent)
        let returnImage = UIImage(CGImage: outImage)
        return returnImage
    }
}
(5)主页代码 - ListViewController.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import UIKit
import CoreImage
 
class ListViewController: UITableViewController {
     
    var movies = [MovieRecord]()
    let movieOperations = MovieOperations()
     
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "热门电影"
         
        //加载,处理电影列表数据
        fetchMovieDetails();
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
     
    //加载,处理电影列表数据
    func fetchMovieDetails() {
        //图片数据源地址
        let dataSourcePath = NSBundle.mainBundle().pathForResource("movies", ofType: "plist")
        let datasourceDictionary = NSDictionary(contentsOfFile: dataSourcePath!)
         
        for(key,value) in datasourceDictionary!{
            let name = key as? String
            let url = NSURL(string:value as? String ?? "")
            if name != nil && url != nil {
                let movieRecord = MovieRecord(name:name!, url:url!)
                self.movies.append(movieRecord)
            }
        }
         
        self.tableView.reloadData()
    }
     
    //获取记录数
    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
        return movies.count
    }
     
    //创建单元格
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
        -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier",
                forIndexPath: indexPath) as UITableViewCell
             
            //为了提示用户,将cell的accessory view设置为UIActivityIndicatorView。
            if cell.accessoryView == nil {
                let indicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
                cell.accessoryView = indicator
            }
            let indicator = cell.accessoryView as! UIActivityIndicatorView
             
            //获取当前行所对应的电影记录。
            let movieRecord = movies[indexPath.row]
             
            //设置文本和图片
            cell.textLabel?.text = movieRecord.name
            cell.imageView?.image = movieRecord.image
             
            //检查图片状态。设置适当的activity indicator 和文本,然后开始执行任务
            switch (movieRecord.state){
            case .Filtered:
                indicator.stopAnimating()
            case .Failed:
                indicator.stopAnimating()
                cell.textLabel?.text = "Failed to load"
            case .New:
                indicator.startAnimating()
                startDownloadForRecord(movieRecord, indexPath: indexPath)
            case .Downloaded:
                indicator.startAnimating()
                startFiltrationForRecord(movieRecord, indexPath: indexPath)
            }
             
            return cell
    }
     
    //执行图片下载任务
    func startDownloadForRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){
        //判断队列中是否已有该图片任务
        if let _ = movieOperations.downloadsInProgress[indexPath] {
            return
        }
         
        //创建一个下载任务
        let downloader = ImageDownloader(movieRecord: movieRecord)
        //任务完成后重新加载对应的单元格
        downloader.completionBlock = {
            if downloader.cancelled {
                return
            }
            dispatch_async(dispatch_get_main_queue(), {
                self.movieOperations.downloadsInProgress.removeValueForKey(indexPath)
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            })
        }
        //记录当前下载任务
        movieOperations.downloadsInProgress[indexPath] = downloader
        //将任务添加到队列中
        movieOperations.downloadQueue.addOperation(downloader)
    }
     
    //执行图片滤镜任务
    func startFiltrationForRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){
        if let _ = movieOperations.filtrationsInProgress[indexPath]{
            return
        }
         
        let filterer = ImageFiltration(movieRecord: movieRecord)
        filterer.completionBlock = {
            if filterer.cancelled {
                return
            }
            dispatch_async(dispatch_get_main_queue(), {
                self.movieOperations.filtrationsInProgress.removeValueForKey(indexPath)
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            })
        }
        movieOperations.filtrationsInProgress[indexPath] = filterer
        movieOperations.filtrationQueue.addOperation(filterer)
    }
}

源码下载:T2.zip

3,功能再次改进:拖动滚动条的时候不加载,停止后只加载当前页
当滚动表格时,比如快速的滚动到最后一条数据。虽然前面的单元格都已移出可视区域。但其实这些图片的下载和处理任务已经添加到后台队列中,并默默地在执行。
(1)解决这个问题,就要保证表格在滚动的情况下,不添加任务到队列中。而且只有当表格停止后,只把当前可见区域的图片添加到任务队列中。
(2)同时,对于那些原来在可视区域的队列任务。如果对应单元格被移出可视区域,那么其任务也要取消。
只需在主页面代码里稍作修改即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
import UIKit
import CoreImage
 
class ListViewController: UITableViewController {
     
    var movies = [MovieRecord]()
    let movieOperations = MovieOperations()
     
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "热门电影"
         
        //加载,处理电影列表数据
        fetchMovieDetails();
    }
     
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
     
    //加载,处理电影列表数据
    func fetchMovieDetails() {
        //图片数据源地址
        let dataSourcePath = NSBundle.mainBundle().pathForResource("movies",
            ofType: "plist")
        let datasourceDictionary = NSDictionary(contentsOfFile: dataSourcePath!)
         
        for(key,value) in datasourceDictionary!{
            let name = key as? String
            let url = NSURL(string:value as? String ?? "")
            if name != nil && url != nil {
                let movieRecord = MovieRecord(name:name!, url:url!)
                self.movies.append(movieRecord)
            }
        }
         
        self.tableView.reloadData()
    }
     
    //获取记录数
    override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int)
        -> Int {
        return movies.count
    }
     
    //创建单元格
    override func tableView(tableView: UITableView,
        cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier",
                forIndexPath: indexPath) as UITableViewCell
             
            //为了提示用户,将cell的accessory view设置为UIActivityIndicatorView。
            if cell.accessoryView == nil {
                let indicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
                cell.accessoryView = indicator
            }
            let indicator = cell.accessoryView as! UIActivityIndicatorView
             
            //获取当前行所对应的电影记录。
            let movieRecord = movies[indexPath.row]
             
            //设置文本和图片
            cell.textLabel?.text = movieRecord.name
            cell.imageView?.image = movieRecord.image
             
            //检查图片状态。设置适当的activity indicator 和文本,然后开始执行任务
            switch (movieRecord.state){
            case .Filtered:
                indicator.stopAnimating()
            case .Failed:
                indicator.stopAnimating()
                cell.textLabel?.text = "Failed to load"
            case .New, .Downloaded:
                indicator.startAnimating()
                //只有停止拖动的时候才加载
                if (!tableView.dragging && !tableView.decelerating) {
                    self.startOperationsForMovieRecord(movieRecord, indexPath: indexPath)
                }
            }
             
            return cell
    }
     
    //图片任务
    func startOperationsForMovieRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){
        switch (movieRecord.state) {
        case .New:
            startDownloadForRecord(movieRecord, indexPath: indexPath)
        case .Downloaded:
            startFiltrationForRecord(movieRecord, indexPath: indexPath)
        default:
            NSLog("do nothing")
        }
    }
     
    //执行图片下载任务
    func startDownloadForRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){
        //判断队列中是否已有该图片任务
        if let _ = movieOperations.downloadsInProgress[indexPath] {
            return
        }
         
        //创建一个下载任务
        let downloader = ImageDownloader(movieRecord: movieRecord)
        //任务完成后重新加载对应的单元格
        downloader.completionBlock = {
            if downloader.cancelled {
                return
            }
            dispatch_async(dispatch_get_main_queue(), {
                self.movieOperations.downloadsInProgress.removeValueForKey(indexPath)
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            })
        }
        //记录当前下载任务
        movieOperations.downloadsInProgress[indexPath] = downloader
        //将任务添加到队列中
        movieOperations.downloadQueue.addOperation(downloader)
    }
     
    //执行图片滤镜任务
    func startFiltrationForRecord(movieRecord: MovieRecord, indexPath: NSIndexPath){
        if let _ = movieOperations.filtrationsInProgress[indexPath]{
            return
        }
         
        let filterer = ImageFiltration(movieRecord: movieRecord)
        filterer.completionBlock = {
            if filterer.cancelled {
                return
            }
            dispatch_async(dispatch_get_main_queue(), {
                self.movieOperations.filtrationsInProgress.removeValueForKey(indexPath)
                self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
            })
        }
        movieOperations.filtrationsInProgress[indexPath] = filterer
        movieOperations.filtrationQueue.addOperation(filterer)
    }
     
    //视图开始滚动
    override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        //一旦用户开始滚动屏幕,你将挂起所有任务并留意用户想要看哪些行。
        suspendAllOperations()
    }
     
    //视图停止拖动
    override func scrollViewDidEndDragging(scrollView: UIScrollView,
        willDecelerate decelerate: Bool) {
        //如果减速(decelerate)是 false ,表示用户停止拖拽tableview。
        //此时你要继续执行之前挂起的任务,撤销不在屏幕中的cell的任务并开始在屏幕中的cell的任务。
        if !decelerate {
            loadImagesForOnscreenCells()
            resumeAllOperations()
        }
    }
     
    //视图停止减速
    override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
        //这个代理方法告诉你tableview停止滚动,执行操作同上
        loadImagesForOnscreenCells()
        resumeAllOperations()
    }
     
    //暂停所有队列
    func suspendAllOperations () {
        movieOperations.downloadQueue.suspended = true
        movieOperations.filtrationQueue.suspended = true
    }
     
    //恢复运行所有队列
    func resumeAllOperations () {
        movieOperations.downloadQueue.suspended = false
        movieOperations.filtrationQueue.suspended = false
    }
     
    //加载可见区域的单元格图片
    func loadImagesForOnscreenCells () {      
        //开始将tableview可见行的index path放入数组中。
        if let pathsArray = self.tableView.indexPathsForVisibleRows {
            //通过组合所有下载队列和滤镜队列中的任务来创建一个包含所有等待任务的集合
            let allMovieOperations = NSMutableSet()
            for key in movieOperations.downloadsInProgress.keys{
                allMovieOperations.addObject(key)
            }
            for key in movieOperations.filtrationsInProgress.keys{
                allMovieOperations.addObject(key)
            }
             
            //构建一个需要撤销的任务的集合。从所有任务中除掉可见行的index path,
            //剩下的就是屏幕外的行所代表的任务。
            let toBeCancelled = allMovieOperations.mutableCopy() as! NSMutableSet
            let visiblePaths = NSSet(array: pathsArray)
            toBeCancelled.minusSet(visiblePaths as Set<NSObject>)
             
            //创建一个需要执行的任务的集合。从所有可见index path的集合中除去那些已经在等待队列中的。
            let toBeStarted = visiblePaths.mutableCopy() as! NSMutableSet
            toBeStarted.minusSet(allMovieOperations as Set<NSObject>)
             
            // 遍历需要撤销的任务,撤消它们,然后从 movieOperations 中去掉它们
            for indexPath in toBeCancelled {
                let indexPath = indexPath as! NSIndexPath
                if let movieDownload = movieOperations.downloadsInProgress[indexPath] {
                    movieDownload.cancel()
                }
                movieOperations.downloadsInProgress.removeValueForKey(indexPath)
                if let movieFiltration = movieOperations.filtrationsInProgress[indexPath] {
                    movieFiltration.cancel()
                }
                movieOperations.filtrationsInProgress.removeValueForKey(indexPath)
            }
             
            // 遍历需要开始的任务,调用 startOperationsForPhotoRecord
            for indexPath in toBeStarted {
                let indexPath = indexPath as! NSIndexPath
                let recordToProcess = self.movies[indexPath.row]
                startOperationsForMovieRecord(recordToProcess, indexPath: indexPath)
            }
        }
    }
}

源码下载:T3.zip
原文出自:www.hangge.com  转载请保留原文链接:http://www.hangge.com/blog/cache/detail_890.html

Swift - 表格图片加载优化(拖动表格时不加载,停止时只加载当前页图片)的更多相关文章

  1. react 首屏加载优化

    react 首屏加载优化,原本是在入口HTML文件中加载loading动画,但是部署在测试环境上的时候一直无法显示loading的部分,也是奇怪了,我们测试环境的部署一直跟本地的都不太一样,内外网的转 ...

  2. iOS Cell异步图片加载优化,缓存机制详解

    最近研究了一下UITbleView中异步加载网络图片的问题,iOS应用经常会看到这种界面.一个tableView上显示一些标题.详情等内容,在加上一张图片.这里说一下这种思路. 为了防止图片多次下载, ...

  3. iOS异步图片加载优化与常用开源库分析

    网络图片显示大体步骤: 1.下载图片: 2.图片处理(裁剪,边框等): 3.写入磁盘: 4.从磁盘读取数据到内核缓冲区: 5.从内核缓冲区复制到用户空间(内存级别拷贝): 6.解压缩为位图(耗cpu较 ...

  4. android 图片加载优化,避免oom问题产生

    1,及时回收bitmap,在activity的onstop()和onDestory()里面调用如下代码进行bitmap的回收: // 先判断是否已经回收 if(bitmap != null & ...

  5. 关于android 图片加载优化

    android应用对图片处理算是比较频繁的了,尤其是在程序加载大量图片和高分辨率图片时,最容易产生oom异常,下面是个人平时一些省内存加载方法 方法一: public Bitmap decodeFil ...

  6. (BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明

    原文:http://blog.csdn.net/java_jh/article/details/20068915 迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后 前几天的原文有一个线程管 ...

  7. 阿里无线前端性能优化指南 (Pt.1 加载优化)

    前言 阿里无线前端团队在过去一年对所负责业务进行了全面的性能优化.以下是我们根据实际经验总结的优化指南,希望对大家有所帮助. 第一部分仅包括数据加载期优化. 图片控制 对于网页特别是电商类页面来说,图 ...

  8. 【5】-阿里面试题android网络图片加载优化

    题目: 遇到网络不好,卡顿的时候如何对网络的加载图片进行优化? 思路: 从加载的图片的本身和手机的存储两方面考虑 解决办法: 1.找现有图片格式的替换者 在众多的图片格式中,选择了Google的Web ...

  9. Android批量图片加载经典系列——使用LruCache、AsyncTask缓存并异步加载图片

    一.问题描述 使用LruCache.AsyncTask实现批量图片的加载并达到下列技术要求 1.从缓存中读取图片,若不在缓存中,则开启异步线程(AsyncTask)加载图片,并放入缓存中 2.及时移除 ...

随机推荐

  1. Yii2 数据操作DAO

    参考: http://www.yiiframework.com/doc/guide/1.1/zh_cn/database.dao http://blog.csdn.net/hzqghost/artic ...

  2. MYSql和PHP计算数据性能

    MYSQL不是有很多内部计算函数吗? 比如我要计算一列数据的平均值,,那我是直接用MYSQL里面的函数在SQL语句中计算 快 :还是用SQL把数据取出来了,用PHP计算快呢?(SQL语句PHP语句都用 ...

  3. FPGA知识大梳理(一)对FPGA行业的一点感言

    今天想开始把这FPGA行业的知识点做一个大整理,从个人感想,到语法,到器件基础,难点攻克,到项目应用.把自己这几年接触到的知识做一个全面的回顾,看看自己这几年走过的路. 人生无常,几年的跌跌撞撞勉强算 ...

  4. 转: cmd和amd的区别

    AMD 规范在这里:https://github.com/amdjs/amdjs-api/wiki/AMDCMD 规范在这里:https://github.com/seajs/seajs/issues ...

  5. dzz使用总结(添加云盘,好用的Web文件管理器,网络播放器)

    dzz添加云盘: http://www.lebook.me/book/22822#fid_3990471 呆萌http://pan.diemoe.net/s/GcdFI4 网络播放器 mediaele ...

  6. 55. 略谈Lotus Notes的与众不同及系列文章至此的总结

    在二十多年的悠久历史里,Lotus Notes发展出一整套独特的概念.技术和思维.由于它早期惊人的领先时代和后续发展中同样惊人的忠于传统,这位软件领域的寿星在如今发展更新速度远超往日和技术愈趋公开互通 ...

  7. 一个包含所有c++的头文件的头文件

    #include <bits/stdc++.h> 做CF看见别人用这个函数,然后就能直接用vector,set,string那些函数了,摸不着头脑,感觉特神奇就百度了一下,才发现这个是C+ ...

  8. OpenWRT推理client线上的数

    有两种方法: 一. 经DHCP client通讯组列表 (缺点:client列表会依据超时时间刷新,一般超时时间为12h,) 二. 通过arp缓存列表/proc/net/arp(缺点:arp刷新时间默 ...

  9. MFC、C++ 、Windows编程高手

    cnblogs: DoubleLi 1. DoubleLi  白手起家Win32SDK应用程序 http://www.cnblogs.com/lidabo/p/3450178.html#_Toc309 ...

  10. OpenGL ES 如何能看到一个物体内部和象3dmax中能只显示网格线

    上一篇 OpenGL ES 正反面设置指令 中分析了正反面的判区方法,那么正反面有什么用呢?接下来我们就要引入一个叫做背面消除的概念.在3dmax中有个选项,当你用挤压修改器挤出一个中空的长方体时,在 ...