Есть страница в Windows Phone 8.1 приложении, где изначально практически ничего нет:
<Page.BottomAppBar>
<CommandBar>
<AppBarButton Icon="Add" x:Uid="AddPhotoAppButton" Click="AddPhotoClick" IsEnabled="{Binding IsButtonsEnabled}"/>
<AppBarButton Icon="Send" x:Uid="SendAppButton" Click="SendClick" IsEnabled="{Binding IsButtonsEnabled}"/>
</CommandBar>
</Page.BottomAppBar>
<ScrollViewer>
<Grid>
<Grid Grid.Row="2">
<TextBox />
</Grid>
<GridView Grid.Row="3" ItemsSource="{Binding Attachments}">
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Holding="ListViewItem_Holding">
<Image Margin="10" Height="90" Width="90" Source="{Binding Image}"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Grid>
</ScrollViewer>
(Для упрощения чтения я вырезал незначимые теги и параметры)
При нажатии на кнопку в app bar пользователь может выбрать любую картинку из галереи телефона, после чего она сразу же загружается на спец сервер (клиент получает id картинки с сервера), а тело картинки записывается в
ObservableCollection<FilePost>();
где
public class FilePost
{
public Guid ID { get; set; }
public WriteableBitmap Image { get; set; }
}
Данная коллекция связана с GridView на странице и превью всех картинок отображается на странице (тег Image).
Если картинка это скажем 5 мп фотография, то на телефоне с 512 мб озу после 3-4 загрузок таких картинок приложение падает с OutOfMemoryException.
Логика работы с файлом следующая:
1. Используя FileOpenPicker мы получаем указатель на StorageFile file.
2. Используем file.OpenAsync() метод чтобы открыть поток.
3. Создаём WriteableBitmap размером 1х1 (он не имеет пустого конструктора или других способов создания).
4. Читаем картинку из потока с помощью метода расширения из библиотеки WriteableBitmapEx WriteableBitmap.FromStream() - вот на этом моменте потребление памяти увеличивается примерно на
40-50 МБ (!)
5. После этого нам необходимо уменьшить картинку до 1920 по большей стороне, для чего мы используем метод расширения WriteableBitmap.Resize() из всё той же библиотеки WriteableBitmapEx. Вот на этом моменте сжирается ещё очень много памяти вплоть до OutOfMemoryException.
Дальнейшую логику видно по коду ниже. Как можно оптимизировать этот процесс? Необходимо чтобы как минимум штук 10 фотографий без проблем можно было выбрать и отобразить (например, в iOS версии этого же приложения можно свободно выбрать хоть 50 штук даже на iPhone 4s, у которого 512 мб озу).
WriteableBitmap wbmp;
using (var stream = await file.OpenAsync(FileAccessMode.Read))
{
wbmp = new WriteableBitmap(1, 1);
wbmp = await wbmp.FromStream(stream);
}
var ratio = wbmp.PixelWidth > wbmp.PixelHeight ? 1920.0f / wbmp.PixelWidth : 1920.0f / wbmp.PixelHeight;
if (ratio < 1)
{
var newWidth = (int)(wbmp.PixelWidth * ratio);
var newHeight = (int)(wbmp.PixelHeight * ratio);
wbmp = wbmp.Resize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
}
using (IRandomAccessStream iras = new InMemoryRandomAccessStream())
{
await wbmp.ToStreamAsJpeg(iras);
var bytes = new byte[iras.Size];
var buffer = await iras.ReadAsync(bytes.AsBuffer(), (uint)iras.Size, InputStreamOptions.None);
var response = await Uploader.UploadData(GetString("ServerApi") + "File?tokenID=" + GetToken(), buffer.ToArray());
if (response.Item != Guid.Empty)
Attachments.Add(new FilePost { ID = Guid.Empty, WImage = wbmp });
}