Silverpath 我的Silverlight开发心得 [1] 动态加载XAP时出现的致命错误

有哪些朋友现在在用Silverlight程序引导启动另一个Silverlight?如果你是其中的一员,你可能难免碰到了如下的诡异问题。如果你不是,也许你会在读完本文后放下js + xaml的Loader...改用Silverlight做引导 :)

首先简单说一下背景,最近做了几个项目的部分内容,发现XAP包很大,让那个蓝色的Loading小圈圈转来转去很不友好。于是就考虑写一个加载程序,界面美观,进度条优雅,速度流畅。于是便按照传统的方法进行加载:

1)使用WebClient下载某XAP

2)(可选)通过分析配置文件得到Dll列表

3)加载每个Dll

4)在MainPage所在Dll加载过程中使用反射创建MainPage的实例

5)将其放到当前的Grid/Page里

写起来不难,如果写过的朋友请跳过下一段,直接进入诡异的问题部分。 如果没有写过的朋友,可以借鉴下面的代码作为铺垫&入门~

----------------------------------可看可不看的----例子部分分割线-----------------------------------------

image

1)新建项目

添加一个项目叫LargeXAP,别忘了往里面放一个视频。。为了增加大小,将这个视频设定成Resource 资源,编译。

image

应该就可以看到两个XAP出现。

一个是LargeXAP,我们的伪-大数据包

另外一个是我们要真正启动的。不要忘记设定LoaderSampleTestPage.html为启动页,这样才能用Loader引导。做完这一切准备工作后我们就可以进入代码了

 

 

 

2)XAML代码部分

双击打开Loader的MainPage.xaml,添加如下代码到LayoutRoot节点里

<TextBlock x:Name="_progress" FontSize="30" Text="0%"/>

这样我们就创建好了所有需要的XAML... 因为只是为了后面的恐怖问题做Demo,所以没有任何Silverlight的Cool炫效果。当然大家可以自己加入。

 

为了看出我们的程序是否被加载,我们需要打开LargeXAP的XAML并加入如下代码:

<MediaElement Source="Wildlife.wmv" AutoPlay="True"/>
我们嵌入的WMV文件就能在这里得到体现,我们也能清楚地知道LargeXAP程序是否被启动了

3)C#代码部分

进入Loader的MainPage.xaml.cs,修改代码到如下状态

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace LoaderSample
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.Loaded += new RoutedEventHandler(MainPage_Loaded);
        }

        void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            WebClient _wc = new WebClient();
            _wc.DownloadProgressChanged += 
                new DownloadProgressChangedEventHandler(_wc_DownloadProgressChanged);
            _wc.OpenReadCompleted += new OpenReadCompletedEventHandler(_wc_OpenReadCompleted);
            _wc.OpenReadAsync(new Uri("LargeXAP.xap", UriKind.Relative));

        }

        void _wc_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
        {
            _progress.Text = e.ProgressPercentage.ToString() + "%";
        }

        void _wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
        {
            System.Windows.Resources.StreamResourceInfo Info = 
                new System.Windows.Resources.StreamResourceInfo(e.Result, "application/binary");
            AssemblyPart AP = new AssemblyPart();
            System.Reflection.Assembly Asm;
            System.Windows.Resources.StreamResourceInfo streamInfo = 
                Application.GetResourceStream(Info, new Uri("LargeXAP.dll", UriKind.Relative));
            Asm = AP.Load(streamInfo.Stream);


            this.Content = (Asm.CreateInstance("LargeXAP.MainPage") as FrameworkElement);
        }
    }
}

关键语句解释:

System.Windows.Resources.StreamResourceInfo Info = new System.Windows.Resources.StreamResourceInfo(e.Result, "application/binary");
将下载得到的XAP文件流转换为Silverlight支持的资源信息,其类型为程序文件,application/binary
AssemblyPart AP = new AssemblyPart();
动态创建一个应用程序组件,可以表示一个Dll的存在并加载它
System.Reflection.Assembly Asm;
准备应用程序集对象,为了后面的反射操作做准备
System.Windows.Resources.StreamResourceInfo streamInfo = Application.GetResourceStream(Info, new Uri("LargeXAP.dll", UriKind.Relative));
创建另一个资源流,对前面的资源流进行进一步读取,读取到的是XAP内部的LargeXAP.dll 
Asm = AP.Load(streamInfo.Stream);
将DLL的资源流读取到程序组件中,同时返回一个更新了的当前程序集(Asm).
this.Content = (Asm.CreateInstance("LargeXAP.MainPage") as FrameworkElement);
通过反射创建一个MainPage对象,并将其放置到当前Page上以完成Load进程

 

我们已经完成了这个Demo,运行结果如下:

载入中:

image

载入完成

image

 

------------------------------------------入门例子完---------------------------------------------------

 

一切看来很好,但事情总有特殊情况。错误的发生有时候是发生在意料之外的。如果有这样一个应用程序,它使用了App.xaml作为载体存储了大量的Resource,也就是App.Resources 中存储了大量的样式,使用这个Loader就会出现很重大的问题:

我们来修改LargeXAP中的App.xaml 使其拥有一个Resource,

<Application.Resources>
    <SolidColorBrush Color="Blue" x:Key="SColorBrush_"/>
</Application.Resources>

可以看到我增加了一条Resource记录,这个时候我们修改LargeXAP中MainPage.xaml,使其能显示这个颜色

<Gridx:Name="LayoutRoot"Background="{StaticResourceSColorBrush_}">
       <
MediaElementSource="Wildlife.wmv"AutoPlay="True"/>
</
Grid>

在添加了绑定后,运行LargeXAP项目,[不是Loader],查看结果,会发现屏幕边上出现了蓝色的底色,这说明我们的Resource被调用了。

这个时候再运行Loader,就会出现错误:

image

我们能发现VS会自动把Debug引入到LargeXAP中,并告诉我们没有找到资源。

凭经验大家可能和我想的一样,可能的解决方案就应该是:

让App.xaml也Load到刚才那个程序集里面,而不是只创建MainPage的实例

于是我就按照这个线索修改了Loader MainPage.xaml.cs中的代码, 尝试创建一个App的Instance,这样资源就应该都Load进来了:

Application _a= (Asm.CreateInstance("LargeXAP.App") as Application);
this.Content = (Asm.CreateInstance("LargeXAP.MainPage") as FrameworkElement);

这个时候运行了程序

错误再次出现 而且内容相同!

也就是说我们创建的App并无起到作用,到底起到没有?设定断点调试。image

仔细看看_a的状态,就可以发现,所有的资源,都已经成功的加载了。可为什么还是无法找到呢?

仔细思索了Silverlight程序默认的开始方式,发现Silverlight本身并无一种机制让多个App同时运行,也就是说SL程序应该是地地道道的单实例应用程序

可是这里我们用了不寻常的方法强行Create了一个App,难道这个App的产生会对我们当前的App,造成一些毁灭性的影响?

同时,追溯Resource绑定的根源,其原理也是向父容器中提交查询,将合乎Key名的资源索引过来。如果父容器其中没有,则在App中索引,这样App就成了全局资源库[顺理成章的]。换句话说,App成为资源库的前提,是成为所有UIElement的Parent, 也是最高级Parent. 在初始化中我们能看到RootVisual这个东西被设定,也就是App的唯一下级了。那么如此推断,唯一的可能就是,这个新创建的App没有与我们当前操作的所有UIElement构成关系,造成了意外的孤立。

大家可能也想到了,如果使用Application.Current获取当前的活动App, 创建一个App对其资源进行遍历,再写入到Current中,不就合乎要求了?

于是我们修改代码:

Application _a= (Asm.CreateInstance("LargeXAP.App") as Application);
foreach (var Res in _a.Resources.Keys)
{
    Application.Current.Resources.Add(Res, _a.Resources[Res]);
}
this.Content = (Asm.CreateInstance("LargeXAP.MainPage") as FrameworkElement);

但这段看似有理的代码却得到了如下的结果:

image

重复键?跟踪Application.Current发现,当前App中资源是存在的,也就是Load进来了。可是为什么无法对MainPage造成影响呢??

想来想去,想到了重要的问题, Silverlight并没有多App并存的机制,那么当App被反射创建也就是不正常的创建的时候,一个默认逻辑会被调用,Application.Current会变更成刚刚创建的这个App,导致我们无法访问原来的有意义的App.

于是我尝试了将源代码段修改成如下诡异的代码段:

Application _real = Application.Current;
Application _a= (Asm.CreateInstance("LargeXAP.App") as Application);
foreach (var Res in _a.Resources.Keys)
{
    _real.Resources.Add(Res, _a.Resources[Res]);
}
this.Content = (Asm.CreateInstance("LargeXAP.MainPage") as FrameworkElement);

仔细看看,其实很简单,在创建App之前缓下一个对象,到时候操作这个缓存的App——原来的有意义的App 即可

 

image

 

这个文章的目的是少让大家走弯路了~

说的不对,大家多多指正,

 

如果没什么特别多的技术错误,我就继续写这个系列,把我的心得共享出来~ 也谢谢大家支持!

 

欢迎大家线上线下交流~ 互相学习~

 

--------------------------------

Mike Luan

luancfan@hotmail.com

15801397431

--------------------------------

提供Silverlight解决方案 和Silverlight项目承包

posted @ 2010-01-23 22:56  struggle-luan  阅读(1953)  评论(9编辑  收藏  举报