突破 Silverlight 自身限制, 做更好的动态加载导航机制(二)

上一篇文章中, 动态导航的思路已经比较完善了, 现在来实现动态导航机制。

实现按需加载 Silverlight 组件

Silverlight 客户端加载一个程序集很容易, 关键是如何分析并加载程序集引用的其它程序集, 这些程序集又会引用另外的程序集, 然后再加在这些程序集。

借助于 Mono.Cecil ,可以在客户端很容易的分析出程序集引用的其它程序集

至于如何加载, 我的实现思路是, 做一个下载队列, 每下载一个程序集, 分析其引用的程序集列表, 找出其没有加载过的程序集, 添加到下载队列, 从下载队列中删除下载过的程序集, 如果队列不为空, 则依次进行递归; 否则,触发下载完成事件。实现代如下:

public class AssemblyDownloader {



   private static readonly IDictionary
 
   
    
   LoadedAssemblies = new Dictionary
  
    
      (StringComparer.OrdinalIgnoreCase); private readonly ISet 
     
       _loadingSet = new HashSet 
      
        (); 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 
       
         DownloadAssemblyCommpleted; public event EventHandler 
        
          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 
         
           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 
          
            GetReferenceAssemblyNames(Stream assemblyStream) { var asmDef = AssemblyDefinition.ReadAssembly(assemblyStream); return asmDef.MainModule.AssemblyReferences.Select(anr => anr.Name + ".dll"); } } 
           
          
         
        
       
      
    
 
   

实现一个自定义的 ContentLoader

实现自定义的 ContentLoader 很容易, 只要实现 INavigationContentLoader 接口即可, 结合上面的AssemblyDownloader, 实现代码如下:

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
 
   
    
  [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;

   }

}
 
   

使用自定义的 ContentLoader

只要设置 Frame 控件的 ContentLoader 为自定义的 ContentLoader 即可, 比如可以这样使用:

 
   


 
   


 
   

   
  
     
      </SDK:FRAME.CONTENTLOADER /> 
    
 
   

当加载第一个程序集的时候, 会自动加载引用的程序集, 如下图:

image

当加载第二个程序集的时候, 重复引用的程序集将不会被加载, 如下图:

image

与 Silverlight 内置的导航机制比较

与内置的导航机制相比, 最大的优点是实现了真正的按需加载, 可以有效地减少主程序的大小, 减少用户的初次等待时间, 而且可以支持 OOB 模式。

你可能感兴趣的:(silverlight)