[WP8.1UI控件编程]Windows Phone大数据量网络图片列表的异步加载和内存优化

11.2.4 大数据量网络图片列表的异步加载和内存优化

    虚拟化技术可以让Windows Phone上的大数据量列表不必担心会一次性加载所有的数据,保证了UI的流程性。对于虚拟化的技术,我们不仅仅只是依赖其来给列表加载数据,还可以利用虚拟化的特性去做更多的事情。虚拟化技术有一个很重要的特性就是,它可以准确地判断出哪些列表项处于手机屏幕中,可以动态地去更新这些数据。基于这样的特性,我们可以给列表的功能做更多的优化。

    那么下面我们基于一个例子来讲解利用虚拟化技术去做列表的性能优化。有这么一个需求,需要实现一个图片的列表,图片都是来自网络的,然后数据集合也很大。做这个网络图片列表功能时会面临着两个问题,一个是图片的加载会比较耗时,两外一个是当不断地滑动会让数据集合加载的图片占用的内存会越来越高。

    对于第一个问题,可以采用异步加载的方式来解决,这样列表加载完之后,图片再显示出来,列表首次加载的速度会很快。那么我们可以通过后台线程调用网络请求下载图片,下载完图片之后再触发UI线程把图片显示出来。

    第二个问题是要解决内存的问题,那么可以使用弱引用类型(WeakReference类)来存储图片的数据。弱引用就是不保证不被垃圾回收器回收的对象,它拥有比较短暂的生命周期,在垃圾回收器扫描它所管辖的内存区域过程中,一旦发现了只具有弱引用的对象,就会回收它的内存,不过一般情况下,垃圾回收器的线程优先级很低,也就不会很快发现那些只有弱引用的对象。当内存的使用会影响到程序的流畅运行的时候,垃圾回收器,就会按照优先次序把存在时间长的弱引用对象回收,从而释放内存。所以弱引用特别适合在当前这种情况下,占用大量内存,但通过垃圾回收功能回收以后很容易重新创建的图片对象。图片下载完之后会存放在弱引用对象里面,当检查到数据被回收的时候,再进行异步加载,当然你也可以把图片用独立存储存起来,这样也就免去了再次请求网络的操作。

    下面我们来实现网络图片列表的异步加载和内存优化的示例:

代码清单11-8网络图片列表(源代码:第11章\Examples_11_8)

    (1)创建数据实体类Data类,在Data类里面封装异步加载图片和弱引用的逻辑。

Data.cs文件主要代码
------------------------------------------------------------------------------------------------------------------
    // Data类从INotifyPropertyChanged派生,要实现绑定属性改变的事件,用于图片异步请求完成之后可以更新到UI上
    public class Data: INotifyPropertyChanged
    {
        // 图片名字属性
        public string Name { get; set; }
        // 当前的页面对象,用于触发UI线程
        public Page Page { get; set; }
        // 图片的网络地址
        private Uri imageUri;
        public Uri ImageUri
        {
            get
            {
                return imageUri;
            }
            set
            {
                if (imageUri == value)
                {
                    return;
                }
                imageUri = value;
                bitmapImage = null;
            }
        }
        // 若引用对象,用于存储下载好的图片对象
        WeakReference bitmapImage;
        // ImageSource属性用于绑定到列表的Image控件上
        public ImageSource ImageSource
        {
            get
            {
                if (bitmapImage != null)
                {
                    // 如果弱引用没有没回收,则取弱引用的值
                    if (bitmapImage.IsAlive)
                        return (ImageSource)bitmapImage.Target;
                    else
                        Debug.WriteLine("数据已经被回收");
                }
                // 弱引用已经被回收那么则通过图片网络地址进行异步下载
                if (imageUri != null)
                {
                    Task.Factory.StartNew(() =>{ DownloadImage(imageUri);});
                }
                return null;
            }
        }
        // 下载图片的方法
        void DownloadImage(object state)
        {
            HttpWebRequest request = WebRequest.CreateHttp(state as Uri);
            request.BeginGetResponse(DownloadImageComplete, request);
        }
        // 完成图片下载的回调方法
        async void DownloadImageComplete(IAsyncResult result)
        {
            HttpWebRequest request = result.AsyncState as HttpWebRequest;
            HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
            // 读取网络的数据
            Stream stream = response.GetResponseStream();
            int length = int.Parse(response.Headers["Content-Length"]);
            // 注意需要把数据流重新复制一份,否则会出现跨线程错误
            // 网络下载到的图片数据流,属于后台线程的对象,不能在UI上使用
            Stream streamForUI = new MemoryStream(length);
            byte[] buffer = new byte[length];
            int read=0;
            do
            {
                read = stream.Read(buffer, 0, length);
                streamForUI.Write(buffer, 0, read);
            } while (read == length);
            streamForUI.Seek(0, SeekOrigin.Begin);
            // 触发UI线程处理位图和UI更新
            await Page.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    BitmapImage bm = new BitmapImage();
                    bm.SetSource(streamForUI.AsRandomAccessStream());
                    // 把图片位图对象存放到若引用对象里面
                    if (bitmapImage == null)
                        bitmapImage = new WeakReference(bm);
                    else
                        bitmapImage.Target = bm;

                    //触发UI绑定属性的改变
                    OnPropertyChanged("ImageSource");
                }
            );
        }
        // 属性改变事件
       async void OnPropertyChanged(string property)
        {
            var hander = PropertyChanged;
            if (hander != null)
                await Page.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                {
                    hander(this, new PropertyChangedEventArgs(property));
                });
        }
        public event PropertyChangedEventHandler PropertyChanged;
    }

    (2)使用ListView控件绑定到数据Data对象的数据集合。

MainPage.xaml文件主要代码
------------------------------------------------------------------------------------------------------------------
    <ListView x:Name="listView">
        <ListView.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Name}" Height="80"></TextBlock>
                    <Image Source="{Binding ImageSource}" Width="200" Height="200"></Image>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
MainPage.xaml.cs文件主要代码
------------------------------------------------------------------------------------------------------------------
    public MainPage()
    {
        InitializeComponent();
        // 创建一个有1000个Data对象的数据集合
        List<Data> Items = new List<Data>();
        for (int i = 0; i < 1000; i++)
        {
            // 在网络地址后面加上index=i是为了保证每个网络地址的不一样
            // 这样就不会产生网络数据缓存,更加接近真实的网络图片列表
            Items.Add(new Data { Name = "Test" + i, Page = this, ImageUri = new Uri("http://pic002.cnblogs.com/images/2012/152755/2012120917494440.png?index=" + i) });
        }
        listView.ItemsSource=Items;
    }

本文来源于《深入理解Windows Phone 8.1 UI控件编程》

源代码下载:http://vdisk.weibo.com/s/zt_pyrfNHoezI

欢迎关注我的微博@WP林政

WP8.1技术交流群:372552293

posted on 2014-06-02 14:32  linzheng  阅读(3726)  评论(15编辑  收藏  举报

导航