GIF и Animated WebP Это самый популярный формат анимации в Интернете. Но в iOS В разработке, родной UIImage не поддерживает напрямую GIF а также Animated WebP отображать, Поэтому существуют различные отличные сторонние решения с открытым исходным кодом, Например SDWebImage
а также YYImage
ждать. Эта статья будет основана на QQ музыка iOS Основываясь на практике оптимизации анимации в конце, Представить различные решения и идеи плюсы и минусы, И предоставить план оптимизации.
В течение долгого времени некоторые модели были склонны к сбоям при просмотре изображений и текстовых потоков Q Sound, а также существует множество отчетов о сбоях, связанных с анимацией в других службах терминала.
Причина сбоя в том, что при загрузке изображений в терминал они заранее декодируются в асинхронном потоке. Декодирование большого количества кадров анимации за короткий промежуток времени будет быстро расходовать доступную память, что напрямую приводит к сбою. Сбой NSMallocException (не удалось увеличить буфер) перед запуском системного уведомления MemoryWarning, также легко вызвать OOM. Через два месяца мы запустили решение покадрового декодирования анимированных изображений в оттенках серого и инкапсулировали его как универсальный компонент загрузки изображений QMAnimatedImageView. Оптимизация принесла следующие улучшения:
2. iOS Как отображать анимацию
Сначала мы познакомим вас с некоторыми общими причинами. отображать анимацию:
Как упоминалось выше, UIImage Он не поддерживает напрямую отображение анимированных изображений. Используйте напрямую [UIImage imageNamed:]
а также [UIImage imageWithData:]
Метод загрузки файлов анимации, Вы получите только статичное изображение. Используйте родной API выставка GIF Нужно использовать ImageIO.framework
Источник data Разобрать каждый кадр в Прошло одновременно UIImageView из animationImages
Свойства для достижения анимации изпод поддержки. Пример кода выглядит следующим образом:
// 1. производить CGImageSourceRef CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
int count = CGImageSourceGetCount(source); NSMutableArray *images = [NSMutableArray array]; for (int i = 0; i < count; i++) { // 2. Траверс для получения каждого кадра CGImageRef image = CGImageSourceCreateImageAtIndex(source, i, NULL); [images addObject:[UIImage imageWithCGImage:image scale:[UIScreen mainScreen].scale orientation:UIImageOrientationUp]]; // 3. Обратите внимание на выпуск, Вы можете добавить один здесь autorelease CGImageRelease(image); } // CGImageSourceRef Также выпустите CFRelease(source);
UIImageView *imageView = [[UIImageView alloc] init]; // 4. проходить animationImages настраиватьанимация imageView.animationImages = images; // 5. Обработка получения каждого кадра немного сложна, Сначала следите за каждым кадром 100ms Бар imageView.animationDuration = 0.1 * count; // 6. Запустить анимацию [imageView startAnimating];
FLAnimatedImage да FlipBoard Ранний открытый исходный код библиотеки загрузки анимации, Идея реализации представляет собой типичную модель потребитель-производитель.
CGImageSourceCreateImageAtIndex
Получите кадр и раскодируйте его, Также кэшируйте данные кадра; YYAnimatedImageView внедрение и FLAnimatedImage похожий:
CADisplayLink
Чтобы сделать анимацию отображаемой, Также добавьте задачу декодирования кадров.Реализация YYImageDecoder
YYImageDecoder Отвечает за анализ изображений или кадров анимации в YYImageFrame объект, поддерживать APNG а также WebP, Краткий процесс декодирования выглядит следующим образом:
[self _updateSourceImageIO]
декодирование.[self _updateSourceWebP]
декодирование, Положитесь на WebP.framework Соответствующая информация была проанализирована.[self _updateSourceAPNG]
декодирование._updateSourceImageIO
Постройте первый кадр,yy_png_info_create
Метод анализируется из исходного файла APNG Связанные параметры._newUnblendedImageAtIndex:extendToCanvas:decoded:
Получить кадриз CGImageRef, Этот шаг также даиспользовать CGImageSourceCreateImageAtIndex
Получите соответствующий кадр,YYCGImageCreateDecodedCopy()
декодирование, 默认использовать CGContextDrawImage & CGBitmapContextCreateImage
Это набор методов для декодирования.Следующее декодирование YYAnimatedImageView и процесс фотосъемки:
YYAnimatedImageView не может удовлетворить потребности бизнеса
YYAnimatedImageView существуют одновременно выставка Большое количество анимационных сцен не может удовлетворить потребности бизнеса:
Выше сказано, что обе сторонние библиотеки загружают файлы локально,Не прямойподдерживатьсуществоватьзагрузка линии, в YYAnimatedImageView
Сотрудничать YYWebImage
Онлайн-загрузка может быть легко реализована, Но опыт даиспользовать не так хорош, как SDWebImage.
Кратко представим это с помощью блок-схемы. SDWebImage3 процесс загрузки изображения, Последующие версии в основном продолжили эту идею:
[UIImage animatedImageWithImages: duration:]
Прямой анализ GIF для _UIAnimatedImage (UIImage изPrivate подкласс)SDAnimatedImageView
, То же, что SDWebImage Простой интерфейс, может быть напрямуюиспользоватьSDWebImageMatchAnimatedImageClass
options загрузить анимацию, Код выглядит следующим образом:SDAnimatedImageView *imageView = [SDAnimatedImageView new];[imageView sd_setImageWithURL:[NSURL URLWithString:url] placeholderImage:nil options:SDWebImageMatchAnimatedImageClass];
Да Обратите внимание наизда, С помощью вышеуказанного метода Изображение загружается в кэш памяти, 那么картинаиз ПримердаодинSDAnimatedImage
объект, Используйте другие UIImageView Загрузите url попасть в кеш памяти, На странице выставкисуществовать только одно статичное изображение.
SDAnimatedImageView
проходить SDAnimatedImagePlayer
Осуществить анимационную извыставку.
setImage:
время встречиинициализацияновыйиз player.
SDDisplayLink
(для CADisplayLink из упаковки, в то же времяподдерживать iOS/tvOS/macOS/watchOS) Зайдите на выставку соответствующего кадра.NSOperationQueue
существовать背景线程进行декодирование, Затем存储существоватьplayer
изframeBuffer
средняя работадлякэш.
Подводя итог, идеи заключаются в следующем: YYAnimatedImageView почти.我们项глаз中картина加载дас раннегоиз SDWebImage эволюционировал из Позже, по мере развития бизнеса, Добавлена логика, такая как асинхронное декодирование/загрузка статистики/переключение на внутренние сетевые компоненты.
При загрузке анимированных изображений с помощью этого решения возникают три проблемы:
Исходя из вышеперечисленных проблем, должно бытьЗагружать покадрово思路应использовать到端内, Когда анимация загружается в память, Декодируйте только первый кадр из двоичных данных; затем в CADisplayLink
При срабатывании проанализировать требуемый в данный момент кадр фотоиз, При этом рационально используйте кадровый буфер, избегайте вышеперечисленного YYAnimatedImageView Непрерывная загрузка анимации приводит к кизвопрос.
Q звук iOS Процесс асинхронной загрузки изображений терминала аналогичен описанному выше. SDWebImage Процесс загрузки аналогичен Процесс декодирования будет немного отличаться, Q Блок-схема декодирования звукового изображения выглядит следующим образом:
Ниже приведены вопросы о депонировании существования по одному:
Как реализовать анимированную графику из Загрузить покадрово на основе платформы асинхронной загрузки?
в настоящий моментизкартина加载流程из Основные болевые точкида, Анимация напрямую проходит и декодирует каждый кадр. Взять на себя многое в одно мгновение CPU а также Память.
Идеи оптимизации следующие:
После завершения трансформации, Необходимо проверить Загрузить покадровопланданетвстречасуществовать Первый кадр Улучшения были сделаны в разделе «Загружено».
По данным онлайн-статистики, Для оптимизации декодирования предыдущих данных, а такжеоптимизация покадрового декодирования трех схем, Средние данные загрузки первого кадра следующие:
По сравнению с расшифровкой всего заранее, Покадровое декодирование из Первый кадр требует времяуменьшать половину; В период оттенков серого Среднее время, необходимое для загрузки первого кадра анимации, равно 25ms колебаться вверх и вниз, Покадровое декодирование не оказывает очевидного влияния на общие данные.
4.2 Искажение движущегося изображенияизвопрос
потому что QMAnimatedImageView дапроходить CADisplayLink
управлять кадрами, отображать, существующая выставка будет показывать следующий кадр только тогда, когда временной интервал от предыдущего кадра превысит продолжительность кадра, Натуральное решение движущегося изображенияизвопрос, Он также может избежать таких вещей, как SDWebImage4 Считайте каждый кадр так общий делитель.
заранее Асинхронный декодирование изображений для общих идей и оптимизация, После декодирования CGRasterData кэшируется в памяти, Подождите, пока основной поток отрендерит изображение и перестанет его декодировать. 以解决系统неявное декодированиепривести киз Катон.
Но в анимированной сцене, Непрерывное декодирование анимации быстро потребляет память. Недостаток памяти приводит к снижению частоты попадания в кэш анимации. Новая анимация запускает декодирование, которое будет дополнительно потреблять CPU, MemoryWarning Авария произошла до срабатывания; CPU и Память бегут друг против друга, Сложно оптимизировать.
YYAnimatedImageView Декодировать только первый кадр, И сохраните анимацию NSData, Декодирование кадров в фоновом потоке. Но даже в этом случае, При постоянной загрузке анимации На слабых машинах по-прежнему существуют проблемы с производительностью. существующие пользователи быстро проводят пальцем по экрану или обновляют данные за пределами сцены, YYAnimatedImageView Все данные кадра предыдущего изображения будут удалены. На следующей выставке эта картинка будет снова расшифрована с начала, вызвать дополнительные CPU потребление, Продолжайте выполнять следующие оптимизации здесь.
YYAnimatedImageView использовать NSDictionary кэшировать декодированные кадры, Да iOS Система ответит, когда памяти не хватает NSDictionary сделать сжатие, Из ипроизводить дополнительно из CPU потребление, и освобождение фреймбуфера зависит от MemoryWarning уведомить. и NSCache Больше подходит для данных с большими издержками кэша. И потокобезопасность, Система автоматически соответствии с Памятьиспользовать Состояниеа такжеcost
Удалить кеш напрямую, В этой оптимизации Декодируйте кадры с помощью NSCache кэшировать.
YYAnimatedImageView Буфер кадра да находится непосредственно View держись из, привести к View При переключении изображений Буфер кадра был ранее освобожден, существовать Cell В сценариях мультиплексирования YYAnimatedImageView Буду продолжать разбирать картинки к CPU Расход слишком высокий.
В этой оптимизации QMAnimatedImageView Не хранит фреймбуфер напрямую, идапроходить QMAnimatedWebImage хранить фреймбуфер, Если анимация SDImageCache освобожденный из памяти, QMAnimatedWebImage Это также очистит буфер кадров, существовать Cell повторное использование сцен, Пока буфер кадра декодирован, декодирование не будет выполняться повторно. Пока анимация не освобождена из кэша памяти, Буфер кадра не будет очищен.
4.3.3 субдискретизация, Время для космоса
существоватьдействительный В разработке, Часто возникают ситуации, когда размер изображения значительно превышает площадь отображения. Потеря памяти при анимации ужасна. В этом случае можно использовать понижающую дискретизацию. Downsampling Технология снижения После декодирования Память.
Как и подвеска ниже, Даже если согласно 3X Экрану нужно только 175*105 размера достаточно, Да Проблемаизкартина却да 500*300. существуетпрохождение онлайн-мониторинга для проверки этих case в то же время, Включение пониженной дискретизации может мгновенно решить проблему потребления Память.
Справочник по понижению разрешения WWDC Image and Graphics Best Practices[2] Просто предоставьте реализацию кода:
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage { let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)! let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true, kCGImageSourceShouldCacheImmediately: true, kCGImageSourceCreateThumbnailWithTransform: true, kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)! return UIImage(cgImage: downsampledImage)}
QMAnimatedImageView Предоставляет интерфейс даунсемплинга, После открытия настроек Если ты сможешь сэкономить больше половины из Память, Кадры анимации будут автоматически сжаты в соответствии с размером экрана.
4.3.4 существующее декодирование не удалось при попытке вручную освободить Память
существовать App бег, часть API Что произойдет, если память не может быть запрошена NSMallocException крах, Авария описана как «Не удалось to grow buffer”. Картинки, как правило, да Память потребление крупных домохозяйств, Таким образом, вы можете увидеть, что декодирование изображения не удается, Активно попытайтесь освободить кэш памяти изображений, Позитивные фотографии существованияиспользоватьиз не будут опубликованы, Картина, которая не была использованаиз, была выпущена сначала, чтобы освободить Память, Избегайте дефицита Памяти, вызванного крахами.
существуют быстрый слайд из сцены, CPU Обычно он довольно занят. Так что можно сдвинуть не генерируя задачу декодирования кадра из иуменьшать CPU давление, QMAnimatedImageView Также предусмотрена функция экранирования интерфейса.
SDImageCache 提供了最大кэшиз ПараметрыmaxMemoryCost
, Да Мы раньше этого не делали сами, SDWebImage Постараюсь максимально занять Память, существовать MemoryWarning Освободите кэш памяти, когда Кривая памяти изменится, как горная вершина, существование продолжает испытывать грань опасности.
и В этой оптимизации я буду maxMemoryCost
ценитьнастраиватьстановитьсяМаксимально доступная памятьиз 30%(онлайн ABT полученный), Кривая памяти будет очень плавной. может эффективно уменьшить OOM.
Максимально доступная памятьизвычислить Код выглядит следующим образом:
// Получить процесс доступный единица (Byte)- (int64_t)memoryUsageLimitByByte{ int64_t memoryLimit = 0; // Получить актуальные данные Памятьиспользовать if (@available(iOS 13.0, *)) { task_vm_info_data_t vmInfo; mach_msg_type_number_t count = TASK_VM_INFO_COUNT; kern_return_t kr = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t)&vmInfo, &count); if (kr == KERN_SUCCESS) {
// Косвенно получить верхний предел максимально доступного размера текущего процесса. // iOS13+ можно рассчитать так: текущий процесс занимает Память+alsouseizПамять=верхнее предельное значение int64_t memoryCanBeUse = (int64_t)(os_proc_available_memory()); if (memoryCanBeUse > 0) { int64_t memoryUsed = (int64_t)(vmInfo.phys_footprint); memoryLimit = memoryUsed + memoryCanBeUse; } } }
if (memoryLimit <= 0) { NSLog(@"Не удалось получить доступную Память, использоватьфизика Памятьделатьдля进程可использовать Память"); int64_t deviceMemory = [NSProcessInfo processInfo].physicalMemory; memoryLimit = deviceMemory * 0.55; }
if (memoryLimit <= 0) { NSLog(@"Не удалось получить физическую Память, использовать可использовать Памятьделатьдля进程可использовать Память"); // Обычно это значение намного меньше, Ничего из вышеперечисленного получить невозможно. mach_port_t host_port = mach_host_self(); mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(integer_t); vm_size_t page_size; vm_statistics_data_t vm_stat; kern_return_t kr;
kr = host_page_size(host_port, &page_size); if (kr == KERN_SUCCESS) { kr = host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); if (kr == KERN_SUCCESS) { memoryLimit = vm_stat.free_count * page_size; } } } return memoryLimit;}
Ручная настройка maxMemoryCost
После уменьшения картинки Память кэша попала из-за риска, Я также сделал соответствующую статистику, По мере увеличения коэффициента серой шкалы, Также Больше бизнеса переключитесь на QMAnimatedImageView , Скорость попадания в кэш Память на самом деле постепенно увеличивалась (в то же время MemoryWarning Количество триггеров также уменьшилось).
Учитывая, что во многих сценах используется смесь статических изображений и анимации, Прежде чем загрузка существования будет завершена, Программа не знает url да нет нет анимированная картинка, QMAnimatedImageView После загрузки проверьте тип файла и номер кадра и логику, в соответствии Картинка актуального типа, чтобы открыть Загрузить покадрово,в то же времяподдерживать GIF/Animated WebP/APNG Три формата анимации, существующие могут загружать анимацию, а сцены можно использовать напрямую.
После завершения трансформации, Является ли производительность нового решения лучше, чем у основного решения?
Я подготовил более экстремальный сценарий, Создайте поток анимации, каждый Cell Содержит от трех до девяти анимированных картинок,Примерно тот же экран 20 个гифкасуществоватьвыставка, Общее количество использованных анимаций 200+, Проверьте ход анимации, проведя пальцем сверху вниз, а затем вверх и вниз вперед и назад. продолжительность 2 минута, Повторите для каждой сцены 3 в результате субсредний.
Принимая во внимание онлайн-обвал, в основном да 3x Резолюция из 3G модель памяти, использовать iPhone 7 Plus в качестве испытательного оборудования.
Сбор данныхиспользовать Instrument Инструменты для проверки использования памяти, использовать PerfDog Номер кадра теста также Катон, Сравнить одновременно UIImageView
/ SDAnimatedImageView
/ YYAnimatedImageView
а также QMAnimatedImageView производительность, Результат следующий:
индекс | UIImageView | SDAnimatedImageView | YYAnimatedImageView | QMAnimatedImageView |
---|---|---|---|---|
пик памяти | 1.9G | 1.1G | 1.3G | 0.8G |
Напряжение памяти триггера раз/мин | 12 | 0.67 | 0 | 0 |
среднее количество кадров в секунду | 52 | 36 | 57 | 58.3 |
Количество зависаний/10 минут | 38 | 107 | 23 | 0 |
серьезный Количество зависаний/10 минут | 25 | 9.8 | 15 | 0 |
Застрял и остановилсяпродолжительность Пропорция | 12% | 1.9% | 0.9% | 0% |
Средняя загрузка ЦП приложения | 48% | 43% | 81% | 27% |
данет авария | Тест вылетает через 5~40 секунд | нет | После 1-2 минут тестирования произойдет сбой. | нет |
Подведите итог:
основнойоптимизацияозначаета такжеглазиз:
[1]. iOS Memory Deep Dive
https://developer.apple.com/videos/play/wwdc2018/416/
[2]. Image and Graphics Best Practices
https://developer.apple.com/videos/play/wwdc2018/219/
[3]. APP&Нужны игрысосредоточиться на Джанк лаг и скорость лага?
https://bbs.perfdog.qq.com/article-detail.html?id=6