Xamarin做为移动端的跨平台原生开发框架的老牌劲旅,一直被视做Mono Project寄予厚望的当家花旦之一。近年来,虽然React Native/Ionic等后起之秀夺去大半江山,但随着Xamarin入驻微软并宣告免费,加之.NET/C#生态的日益完善与精进,Xamarin已然重焕青春!css
那么,若是咱们将它与上期技术分享介绍的业界新贵WebAssembly双剑合璧,又会迸发出怎样的化学反应呢?今天咱们就将Autodesk Forge Viewer离线方案整合到Xamarin,并发布为基于WebAssembly的Progressive Web App(简称PWA,渐进式应用) - 即实现浏览器URL访问直接安装的神奇效果!html
首先,让咱们First things first:
💗Autodesk ADN(开发者社区团队)祝您在新的一年大吉大利!!🐖事顺利!!💗前端
在原生和H5应用如火纯青的今天,WebAssembly和PWA的相对意义与优点在于:react
相比Cordova/PhoneGap、Ionic、Appgyver等H5混合框架,以及React Native、Titanium等原生框架,咱们为何要关注Xamarin呢?git
今天实战的环节为:Forge Viewer渐进应用 > Xamarin应用 > 发布为WebAssembly的PWA > 移动端测试github
往期咱们有介绍过利用ServiceWorker和Cache等API实现Forge Viewer离线方案,可是悉心的朋友或许已经发现该方案仍有很多瑕疵(将在下期着重阐述),如今咱们更一进步:将整个加载过程离线缓存与客户端! 其成果是独立的Forge Viewer PWA,知足移动和桌面端的离线使用。ajax
首先定义Viewer渐进应用的ServiceWorker,有关该API的详细介绍能够参考往期。不一样于往期中介绍的方案,此次咱们将要缓存全部Viewer脚本、样式和加载模型的请求,且在预设的缓存列表中只有CSS,其他脚本依赖与模型数据所有在应用首次加载阶段缓存,省去手动适配不一样Viewer版本与模型资源的麻烦。首先咱们来监听请求事件,收集全部须要缓存的请求,并记录从后台获取的Access Token,以供Forge API认证所需。出于性能考虑,待模型加载完成后再统一缓存全部资源:json
const urlsToCache = [ 'viewer.html', //Viewer页面路径 'viewer-serviceworker.js', //本ServiceWorker路径 'https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/style.min.css' //Viewer.js样式 ]; ... self.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then( async response => { if (response) return response; if (event.request.url.endsWith('/api/token')) { \\判断请求指向获取Access Token的后台服务 const response = await fetch(event.request); fetchOptions.headers = { 'Authorization': 'Bearer ' + response.access_token } \\设定访问Forge API请求的Access Token return response; } else fetches.push(event.request.url); return fetch(event.request) }) ) });
在ServiceWorker中定义缓存操做bootstrap
self.addEventListener('message', async event => { switch (event.data.operation) { case 'EXECUTE_CACHE': await caches.open(CACHE_NAME).then(async cache => await Promise.all(fetches.map(url=>fetch(url, fetchOptions).then(resp => cache.put(url, resp))))); event.ports[0].postMessage({ status: 'ok', fetches }); break; } });
待Viewer触发GEOMETRY_LOADED_EVENT
,即模型加载完毕,一切所需资源已记录在案,再统一触发缓存小程序
navigator.serviceWorker.register('/service-worker.js').then((registration) => { alert('Service worker registered', registration.scope); let script = document.createElement('script'); script.onload = function () { const viewer = new Autodesk.Viewing.Private.GuiViewer3D(myViewerDiv); Autodesk.Viewing.Initializer(options, () => { ... //按需以在线或离线模式初始化Viewer并加载模型 viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, () => { const channel = new MessageChannel(); channel.port1.onmessage = (event) => console.log(event); navigator.serviceWorker.controller.postMessage({ operation: 'EXECUTE_CACHE' }, [channel.port2]); // 模型加载完成,该模型所需资源已做记录,遂向ServiceWorker发送消息,开始缓存所需资源 }) }); }; script.src = "https://developer.api.autodesk.com/modelderivative/v2/viewers/6.*/viewer3D.min.js"; document.head.appendChild(script) });
因为Viewer采用WebGL实现,其PWA的整合亦采用WebView,建立基于Xamarin XAML的Top Banner+WebView的UI界面,其中WebView指向咱们以前定义的Viewer PWA页面
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <StackLayout BackgroundColor="DimGray" VerticalOptions="FillAndExpand" HorizontalOptions="Fill"> <StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center"> <ContentView Padding="0,10,0,10" VerticalOptions="FillAndExpand"> <Image Source="{Binding logo}" VerticalOptions="Center" HeightRequest="24" /> </ContentView> </StackLayout> </StackLayout> <ScrollView Grid.Row="1"> <StackLayout Orientation="Vertical" > <ContentView VerticalOptions="FillAndExpand" > <WebView Source="URL/TO/YOUR/VIEWER/PWA.html"></WebView> </ContentView> </StackLayout> </ScrollView> </Grid>
经过NuGet安装如下依赖,其中Ooui系列用于发布WebAssembly
在Program.cs
中定义发布过程
using OurXamarinApp static void Main(string[] args) { Forms.Init(); var mainPage = new MainPage(); //以MainPage为应用入口为例 UI.Publish("/", mainPage.GetOouiElement()); }
\bin\Debug\netcoreapp2.1\dist
路径下生成WebAssembly和配套的前端页面与脚本:const urlsToCache = [ 'index.html', "https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css", //Ooui依赖 "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css", //Ooui依赖 'service-worker.js' //本ServiceWorker路径 ]; ... self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request) .then(function (response) { // Cache hit - return response if (response) { return response; } return fetch(event.request); } ) ); });
在发布生成的index.html
页面中注册该ServiceWorker
window.addEventListener('load', function () { navigator.serviceWorker.register('service-worker.js') })
定义应用清单 (manifest.json),让浏览器识别咱们的PWA并定义主题颜色、应用图标等元数据
{ "name": "Forge Viewer PWA", "short_name": "FVPWA", "icons": [ { "src": "icons/icon-128x128.png", "sizes": "128x128", "type": "image/png" }, { "src": "icons/icon-144x144.png", "sizes": "144x144", "type": "image/png" }, { "src": "icons/icon-152x152.png", "sizes": "152x152", "type": "image/png" }, { "src": "icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" } ], "start_url": "index.html", "display": "standalone", "background_color": "#3498DB", "theme_color": "#3498DB" }
在index.html
页面中引用清单
<header> ... <link rel="manifest" href="/manifest.json"> </header>