實戰Forge Viewer漸進應用 - 當Xamarin趕上WebAssembly

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(開發者社區團隊)祝您在新的一年大吉大利!!🐖事順利!!💗前端

爲何要WebAssembly和PWA?

在原生和H5應用如火純青的今天,WebAssembly和PWA的相對意義與優點在於:react

  • 上期技術分享介紹,WebAssembly賦能咱們在瀏覽器中以原生的性能運行CC#、Java、Rust等及基於它們的框架和技術
  • 編譯後的執行包(wasm)爲二進制,精簡高效的同時保護源代碼,各類安全機制防範惡意代碼的篡改與攻擊,加強應用的安全性
  • PWA給予用戶非原生應用勝似原生應用的瀏覽器客戶端體驗,經過瀏覽器訪問URL便可完成PWA的安裝,無需發佈至應用商店

Xamarin的優點

相比Cordova/PhoneGap、Ionic、Appgyver等H5混合框架,以及React Native、Titanium等原生框架,咱們爲何要關注Xamarin呢?git

  • 相較Cordova/PhoneGap、Ionic、Appgyver等混合框架具備原生優點
  • 相較基於JavaScript的React Native、Titanium,.NET/C#在語言某些特性上具有相對優點,也便於熟悉.NET/C#桌面與後臺開發的朋友迅速上手
  • .NET/C#生態的相對優點,如能夠使用咱們的Forge .NET Client SDK
  • 相較基於狀態的Functional Approach具備更底層原生UI的優點

圖片描述

實戰開始!

今天實戰的環節爲:Forge Viewer漸進應用 > Xamarin應用 > 發佈爲WebAssembly的PWA > 移動端測試github

Forge Viewer PWA

往期咱們有介紹過利用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)
    
        });
  • 最後定義後臺服務,獲取Access Token,.NET、Node、PHP的教程能夠參看這裏

整合Viewer PWA至Xamarin應用

  • 在Visual Studio中建立Xamarin項目,注意引用Xamarin.Forms v2.5而非v3.x!
  • 因爲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>
  • 可用所見即所得的設計器(如GorillaPlayer)設計XAML,和Visual Studio 2017自帶的XAML Previewer預覽UI效果(如箭頭所示於XAML編輯器右上角按鈕進入)

圖片描述

  • 編譯運行並檢查在UI和WebView加載剛纔完成的Forge Viewer PWA的效果

發佈爲基於WebAssembly的PWA!

  • 在Visual Studio中建立基於.NET Core 2.x的Console(控制檯應用)項目,並引用方纔建立的Xamarin項目
  • 經過NuGet安裝如下依賴,其中Ooui系列用於發佈WebAssembly

    • Ooui: https://github.com/praeclarum...
    • Ooui WASM: 發佈WebAssembly
    • Ooui Forms: Xamarin.Forms的Ooui實現
    • Xamarin.Forms 2.5 //重要!必定要使用2.5版本以兼容Ooui
  • 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和配套的前端頁面與腳本:

圖片描述

  • 定義緩存WebAssembly的ServiceWorker:
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>

移動端測試

  • 用各大Web服務器直接靜態託管應用所在目錄便可,並以支持ServiceWorker的瀏覽器訪問,支持狀況可參考:https://caniuse.com/#search=s...
  • 在非HTTPS/SSL測試環境下,如遇瀏覽器安全限制:「不安全上下文」錯誤或沒法註冊ServiceWorker (navigator.serviceWorker爲null),則需給服務器啓用HTTPS/SSL,或使用ngrok等雲管道服務爲本地環境做代理
  • 用訪問應用URL時瀏覽器會提示保存快捷方式到桌面,圖標由咱們的應用清單定義:

圖片描述

  • 保存後進入飛行模式打開應用,測試離線狀態的加載:

圖片描述

  • 大功告成!

延伸閱讀

相關文章
相關標籤/搜索