实现按需加载 Silverlight 组件
Silverlight 客户端加载一个程序集很容易, 关键是如何分析并加载程序集引用的其它程序集, 这些程序集又会引用另外的程序集, 然后再加在这些程序集。
借助于 Mono.Cecil ,可以在客户端很容易的分析出程序集引用的其它程序集。
至于如何加载, 我的实现思路是, 做一个下载队列, 每下载一个程序集, 分析其引用的程序集列表, 找出其没有加载过的程序集, 添加到下载队列, 从下载队列中删除下载过的程序集, 如果队列不为空, 则依次进行递归; 否则,触发下载完成事件。实现代如下:
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
|
public
class
AssemblyDownloader {
private
static
readonly
IDictionary<
string
assembly=
""
,=
""
> LoadedAssemblies =
new
Dictionary<
string
assembly=
""
,=
""
>(StringComparer.OrdinalIgnoreCase);
private
readonly
ISet<
string
> _loadingSet =
new
HashSet<
string
>();
private
static
readonly
object
LoadingSetLock =
new
object
();
private
static
readonly
string
[] SilverlightRuntimeAssemblyNames =
new
[] {
"Microsoft.VisualBasic.dll"
,
"mscorlib.dll"
,
"System.Core.dll"
,
"System.dll"
,
"System.Net.dll"
,
"System.Runtime.Serialization.dll"
,
"System.ServiceModel.dll"
,
"System.ServiceModel.Web.dll"
,
"System.Windows.Browser.dll"
,
"System.Windows.dll"
,
"System.Windows.RuntimeHost.dll"
,
"System.Xml.dll"
};
private
bool
_isbusy;
private
string
_loadingAssemblyName;
public
event
EventHandler<downloadassemblycommpletedeventargs> DownloadAssemblyCommpleted;
public
event
EventHandler<asynccompletedeventargs> DownloadFailed;
public
Assembly GetAssembly(
string
assemblyName) {
return
LoadedAssemblies.ContainsKey(assemblyName) ? LoadedAssemblies[assemblyName] :
null
;
}
public
void
OnDownloadFailed(Exception ex) {
var
handler =
this
.DownloadFailed;
if
(handler !=
null
) {
handler(
this
,
new
AsyncCompletedEventArgs(ex,
true
,
null
));
}
}
private
void
OnDownloadAssemblyCommpleted(DownloadAssemblyCommpletedEventArgs e) {
var
handler =
this
.DownloadAssemblyCommpleted;
if
(handler !=
null
) {
handler(
this
, e);
}
}
public
void
DownloadAssemblyAsync(
string
assemblyName) {
if
(
this
._isbusy) {
throw
new
InvalidOperationException(
string
.Format(
"AssemblyDownloader is loading {0}, please waite ..."
,
this
._loadingAssemblyName));
}
assemblyName = EnsureEndWdithDll(assemblyName);
this
._loadingAssemblyName = assemblyName;
if
(IsAssemblyLoaded(assemblyName)) {
this
.DownloadCompleted();
}
else
{
DownloadAssemblyAsyncCore(assemblyName);
}
}
private
void
DownloadAssemblyAsyncCore(
string
assemblyName) {
this
._isbusy =
true
;
assemblyName = EnsureEndWdithDll(assemblyName);
var
name = assemblyName;
var
webClient =
new
WebClient();
webClient.OpenReadCompleted += (sender, e) =>
this
.OnReadOneAssembly(name, e);
try
{
webClient.OpenReadAsync(
new
Uri(assemblyName, UriKind.Relative));
}
catch
(Exception ex) {
this
.OnDownloadFailed(ex);
}
}
private
static
string
EnsureEndWdithDll(
string
assemblyName) {
if
(!assemblyName.EndsWith(
".dll"
, StringComparison.OrdinalIgnoreCase)) {
assemblyName +=
".dll"
;
}
return
assemblyName;
}
private
void
OnReadOneAssembly(
string
name, OpenReadCompletedEventArgs e) {
if
(e.Error !=
null
) {
this
.OnDownloadFailed(e.Error);
this
._isbusy =
false
;
return
;
}
var
assemblyStream = e.Result;
var
references = GetReferenceAssemblyNames(assemblyStream);
AddNotLoadedReferenceAssemblyToLoadingSet(references);
assemblyStream.Seek(0, SeekOrigin.Begin);
LoadToAssemblyPart(assemblyStream, name);
if
(
this
._loadingSet.Count > 0) {
var
asm =
this
._loadingSet.First();
lock
(LoadingSetLock) {
this
._loadingSet.Remove(asm);
}
this
.DownloadAssemblyAsyncCore(asm);
}
else
{
this
.DownloadCompleted();
}
}
private
void
DownloadCompleted() {
this
._isbusy =
false
;
var
assembly =
this
.GetAssembly(
this
._loadingAssemblyName);
this
.OnDownloadAssemblyCommpleted(
new
DownloadAssemblyCommpletedEventArgs(assembly));
}
private
static
void
LoadToAssemblyPart(Stream assemblyStream,
string
name) {
var
part =
new
AssemblyPart {
Source = name
};
var
assembly = part.Load(assemblyStream);
LoadedAssemblies.Add(name, assembly);
}
private
void
AddNotLoadedReferenceAssemblyToLoadingSet(IEnumerable<
string
> references) {
var
referencesNotLoaded =
from
reference
in
references
where
!(IsAssemblyLoaded(reference))
select
reference;
foreach
(
var
@
ref
in
referencesNotLoaded) {
lock
(LoadingSetLock) {
if
(!
this
._loadingSet.Contains(@
ref
)) {
this
._loadingSet.Add(@
ref
);
}
}
}
}
private
static
bool
IsAssemblyLoaded(
string
assemblyName) {
return
SilverlightRuntimeAssemblyNames.Any(asmName => asmName.Equals(assemblyName, StringComparison.OrdinalIgnoreCase))
|| LoadedAssemblies.ContainsKey(assemblyName)
|| Deployment.Current.Parts.Any(ap => ap.Source.Equals(assemblyName, StringComparison.OrdinalIgnoreCase));
}
private
static
IEnumerable<
string
> GetReferenceAssemblyNames(Stream assemblyStream) {
var
asmDef = AssemblyDefinition.ReadAssembly(assemblyStream);
return
asmDef.MainModule.AssemblyReferences.Select(anr => anr.Name +
".dll"
);
}
}</
string
></
string
></asynccompletedeventargs></downloadassemblycommpletedeventargs></
string
></
string
></
string
></
string
>
|
实现一个自定义的 ContentLoader
实现自定义的 ContentLoader 很容易, 只要实现 INavigationContentLoader 接口即可, 结合上面的AssemblyDownloader, 实现代码如下:
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
|
public
class
MyContentLoader : INavigationContentLoader {
private
static
AssemblyDownloader _assemblyDownloader;
public
IAsyncResult BeginLoad(Uri targetUri, Uri currentUri, AsyncCallback userCallback,
object
asyncState) {
var
typeFullName = targetUri.ToString();
if
(
string
.IsNullOrEmpty(typeFullName)) {
return
null
;
}
var
arr = typeFullName.Split(
','
);
var
typeName = arr[0];
var
assemblyName = arr[1];
if
(!assemblyName.EndsWith(
".dll"
, StringComparison.OrdinalIgnoreCase)) {
assemblyName +=
".dll"
;
}
var
asyncResult =
new
MyContentLoaderAsyncResult {
AsyncState = asyncState,
TypeName = typeName,
AssemblyName = assemblyName
};
BeginLoadCore(userCallback, asyncResult);
return
asyncResult;
}
private
static
void
BeginLoadCore(AsyncCallback userCallback, MyContentLoaderAsyncResult result) {
if
(_assemblyDownloader ==
null
) {
_assemblyDownloader =
new
AssemblyDownloader();
}
var
handlers =
new
EventHandler<downloadassemblycommpletedeventargs>[1];
handlers[0] = (sender, e) => {
_assemblyDownloader.DownloadAssemblyCommpleted -= handlers[0];
result.Assembly = e.Result;
userCallback(result);
};
_assemblyDownloader.DownloadAssemblyCommpleted += handlers[0];
_assemblyDownloader.DownloadAssemblyAsync(result.AssemblyName);
}
public
void
CancelLoad(IAsyncResult asyncResult) {
}
public
LoadResult EndLoad(IAsyncResult asyncResult) {
var
result = asyncResult
as
MyContentLoaderAsyncResult;
if
(result ==
null
) {
throw
new
InvalidOperationException(
string
.Format(
"Wrong kind of {0} passed in. The {0} passed in should only come from {1}."
,
"IAsyncResult"
,
"MyContentLoader.BeginLoad"
));
}
var
loadResult =
new
LoadResult(result.GetResultInstance());
return
loadResult;
}
public
bool
CanLoad(Uri targetUri, Uri currentUri) {
return
targetUri.ToString().Split(
','
).Length == 2;
}
}</downloadassemblycommpletedeventargs>
|
使用自定义的 ContentLoader
只要设置 Frame 控件的 ContentLoader 为自定义的 ContentLoader 即可, 比如可以这样使用:
1
2
3
4
5
6
7
|
<
hyperlinkbutton
navigationuri="Widget1.HomeWidget, Widget1" navigationtarget="NavFrame">
<
hyperlinkbutton
navigationuri="Widget2.HomeWidget, Widget2" navigationtarget="NavFrame">
<
sdk:frame
name="NavFrame">
<
sdk:frame.contentloader
>
<
my:mycontentloader
>
</
SDK:FRAME.CONTENTLOADER
/>
</
my:mycontentloader
></
sdk:frame.contentloader
></
sdk:frame
></
hyperlinkbutton
></
hyperlinkbutton
>
|
当加载第一个程序集的时候, 会自动加载引用的程序集, 如下图:
当加载第二个程序集的时候, 重复引用的程序集将不会被加载, 如下图:
与 Silverlight 内置的导航机制比较
与内置的导航机制相比, 最大的优点是实现了真正的按需加载, 可以有效地减少主程序的大小, 减少用户的初次等待时间, 而且可以支持 OOB 模式。