2020年11月27日

嘗試徹底解決 Line、FB 手機 APP 無法正確開啟網頁的困境

嘗試徹底解決 Line、FB 手機 APP 無法正確開啟網頁的困境

Wayne Fu 0 A+
前幾年曾寫過「用 Line、FB 手機 APP 開啟網頁對前端工程師的困擾﹍JS 辨識內建瀏覽器(webView)的方法」,除了解釋 "什麼是內建瀏覽器"、"為何 APP 要用 webView"、"內建瀏覽器會產生什麼問題" 等等,還有提到幾個要點:
  • 這件事以目前的網路技術,沒有治本的解決方法
  • 手機 APP 只會越來越多,不可能針對所有 APP 個別處理
  • 暫時只能針對少數熱門 APP 處理,例如 Line、FB..
最近因為一些需求,想要徹底解決手機內建瀏覽器( webView 問題,想說過了兩三年看看技術上有沒有新進展,結果依然沒有根治之法。 不過在測試過程發現有些微不一樣的地方,如此一來讓我產生了不同的想法,說不定可以從別的角度切入,來解決手機內建瀏覽器的問題。 (圖片出處: pixabay.com)

一、webView 的進展

1. JS 基本問題 上一篇的留言 #2 整理了「你知道你的網站可能在 InAppBrowser/webview 無法使用嗎?」的 Javascript 基本問題:
  • alert() :無法出現對話提醒視窗
  • confirm() :對話確認視窗無法出現
  • window.open 或 window.opener:與電腦上完全不同的顯示行為。
  • window.close 或 self.close :失效。
兩三年過去軟硬體的發展都有進步,現在使用我手邊的裝置進行實測,分別有 Android 5.0 及 iOS 12 的機型:
  • alert() 與 confirm() 都能正常執行
  • window open、close 問題不變
看起來 webView 的進展並非一成不變,隨著 Android 與 iOS 的更新,JS 的支援程度也會越來越高。window open 的問題最後再敘,目前 alert 與 confirm 能用的話,就可提醒使用者改用正規瀏覽器開啟網頁,手機 APP 使用 webView 的問題要完全解決至少是看得到希望的2. 測試 ES6、ES7 支援度 會想要徹底解決 webView 的問題,其實主要原因來自 JS 的非同步問題。自從 ES7 的 async/await 徹底解決了非同步問題的 callback 地獄,同時讓 JS 的架構井然有序,沒有再回去使用舊語法的道理。 然而 ES7 別說可能較舊的網頁瀏覽器不支援,webView 就更別提。所以如果 JS 不能辨識手機 webView 的話,就不可能放心讓自己的網頁全面啟用 async/await。那麼 webView 的技術目前究竟支援到 ES 哪個版本,有必要實地測試一下。 這個討論串「Can I detect async/await available in browser?」提供了以下測試方法: try { eval('async () => {}'); } 這行語法包含了 async 以及箭頭函數,我們來看看結果如何。 3. Android、iOS 相容性 以下是我的手機內建瀏覽器(非網頁瀏覽器)實測結果:
  • Android 5 webView:出現了箭頭函數的錯誤訊息,代表 ES6 的支援就有問題了,更不可能支援 ES7
  • iOS 12 webView:出現了 SyntaxError 錯誤訊息,代表支援 ES6 箭頭函數、不支援 async,所以 ES7 一定不行
android 5 相對於 iOS12 是比較舊的版本,不過代表市場上還會有這樣的機型。這也可看出不同機型的內建瀏覽器對 JS 的支援度並非都一樣,而隨著時間進展 webView 將來是有可能追上 JS 主流技術的。 這個測試結果算是給我不少信心,目前當然需要解決 webView 的問題,但很可能隨著手機的進步,將來有一天 webView 的問題就微不足道了。 4. window open、close 問題 雖然提供了 webView 的鼓舞訊息,但接下來要潑點冷水,來談談 window open、close 問題。先說結論,這很有可能是無解的狀況。 webView 的先天設計有其限制,功能只是單純用來開啟網頁,並沒有像一般網頁瀏覽器有分頁(tab)的功能,這導致所謂的 window.open() 並不會另開視窗,而只是在原網頁開啟另一個網址。 看到這裡讀者應該就懂了,不會另開視窗的話,那麼不同頁面間要切換只能執行上一頁、下一頁,自然 window.opener 的對象就跟網頁瀏覽器不一樣了,而 window open、close 所有相關問題也都是同樣原理。 所以如果開發者的網頁一定要使用 window.opener 相關功能,就絕對不能在 webView 執行,就算將來有一天 webView 的 JS 相容性已經跟網頁瀏覽器一模一樣也不行,這算是 "硬體上的限制"!

二、JS 處理 webView 的構思

前一篇文章我認為沒有一勞永逸的解法,所以只提供了解決問題的概念,瞭解如何判斷各種內建瀏覽器的語法。而這段時間以來,網路上陸續出現了各種有助於解決 webView 的拼圖,大致整理一下其他的關鍵技術,最後會提出我的整合構想: 1. Chrome 網址協定 如果手機有裝 Chrome 瀏覽器的話,可以使用 Chrome 提供的網址協定,參考這篇「手機網頁跳出WebView強制使用Chrome開啟」。 Android 使用範例: googlechrome://navigate?url=www.wfublog.com 2021.4.24 補充:留言 #1 提到以上 Android 範例會失效,查到這個討論串「URL Scheme to open Chrome/Firefox (Android OS) from Facebook post」原來有人發現 FB app 在 Android 不能使用 "googlechrome://" 這個協議。但我自己測試沒問題,也許是 Android 版本太低,而比較新的 Android 版本(例如留言者使用的 Android 8)就不支援。那麼可參考該討論串提供的解法,改用以下協議試試: intent:https://www.wfublog.com#Intent;end iOS 使用範例: googlechrome://www.wfublog.com 2. Line 開啟外部瀏覽器 手機使用 Line 開啟連結網址時,官方留了一個後門可以用外部瀏覽器開啟,只要在網址後面加上參數 ?openExternalBrowser=1,例如: https://www.wfublog.com?openExternalBrowser=1 3. 偵測是否為行動裝置 根據「讓 Line 按鈕只在手機+行動裝置顯示」→「三、判斷行動裝置」,以下為判斷語法: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) 4. 測試 async 支援度 因為 webView 對 JS 支援度不夠,那麼可以偵測瀏覽器是否支援較新的 JS 規範,例如測試瀏覽器是否支援 async 語法,前面有範例語法。 當然,這並非嚴謹的測試方法:
  • 因為不支援 async 的瀏覽器不一定只有 webView,也包含了舊瀏覽器與瀏覽器舊版本
  • 但這仍不失為可行的檢驗方法,並竟我的網頁就是需要支援 async 才能正常顯示
  • 所以任何不支援 async 的瀏覽器,我都需要提醒訪客改用最新的主流瀏覽器版本,例如 Chrome
  • 但如果開發者的網頁並沒有使用 async/await 語法,則不一定要測試 async,只要測試夠用的 JS 規範即可,例如前面提過可測試箭頭函數
  • 如果將來 webView 支援了 async,而網頁又有想排除 webView 的必要需求時,必須改為偵測更新的 JS 規範
4. 整合構思 整合前述的所有概念後,以下為我構思的 JS 偵測 webView 概念流程:
  • 偵測瀏覽器 JS 是否支援 async,若支援則不處理
  • 瀏覽器不支援 async 時,偵測是否為行動裝置
    • 如果不是行動裝置,則 alert 提醒使用最新 chrome 瀏覽器
  • 如果是行動裝置時,偵測是否為 Line 內建瀏覽器
    • 如果是 Line 內建瀏覽器,直接用外部瀏覽器開啟網頁
  • 如果不是 Line 內建瀏覽器時
    • 偵測是否為 Android
      • 如果是 Chrome 則 alert 提醒更新為最新版本
      • 不是 Chrome 則 confirm 請訪客同意用 Chrome 開啟網頁,或是請自行改用外部瀏覽器開啟
    • 偵測是否為 iOS
      • 如果是 Chrome 則 alert 提醒更新為最新版本
      • 不是 Chrome 則 confirm 請訪客同意用 Chrome 開啟網頁,或是請自行改用外部瀏覽器開啟

三、範例程式碼

根據前面提出的構想,以下為範例程式碼,請自行修改相關字串: (function() { var chrome_warning_text = "建議使用最新版 chrome 瀏覽器才能正常執行本網頁", chrome_protocol_text = "如您已安裝 Chrome,請同意用 Chrome 開啟網站,才能確保執行正常。或是請取消後自行改用外部瀏覽器開啟本頁面網址", userAgent = navigator.userAgent, thisHref = location.href, thisUrl = location.hostname + location.pathname, isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent), isLine = userAgent.indexOf("Line") > -1, isAndroid = userAgent.indexOf("Android") > -1, isIOS = /iPhone|iPad|iPod/i.test(userAgent), isChrome = /Chrome/.test(userAgent) && /Google Inc/.test(navigator.vendor); try { // 測試是否支援 async eval("async () => {}"); } catch { // 測試是否為行動裝置 if (isMobile) { // 偵測是否為 Line 內建瀏覽器 if (isLine) { // Line 內建瀏覽器 直接用外部瀏覽器開啟網頁 location.href = thisHref.indexOf("?") > 0 ? thisHref + "&openExternalBrowser=1" : thisHref + "?openExternalBrowser=1"; } else { // 偵測是否為 Android if (isAndroid) { // 偵測是否為 Chrome if (isChrome) { // 建議使用最新版 chrome alert(chrome_warning_text); } else { // 不是 Chrome 則請訪客同意用 Chrome 開啟網頁 if (confirm(chrome_protocol_text)){ location.href = "googlechrome://navigate?url=" + thisUrl; } } return; } // 偵測是否為 iOS if (isIOS) { // 偵測是否為 Chrome if (isChrome) { // 建議使用最新版 chrome alert(chrome_warning_text); } else { // 不是 Chrome 則請訪客同意用 Chrome 開啟網頁 if (confirm(chrome_protocol_text)){ location.href = "googlechrome://" + thisUrl; } } return; } // 其他作業系統建議使用最新版 chrome alert(chrome_warning_text); } } else { // 非行動裝置建議使用最新版 chrome alert(chrome_warning_text); } } })();

四、Babel

除了以上本篇提供的思路,跨瀏覽器、跨裝置讓 JS 相容還有一套知名的工具「Babel」 ,可以將 ES6 以上的 JS 新規格語法轉換回 ES5 語法,讓較低階的瀏覽器也能執行。 1. 線上轉換工具 這是 Babel 提供的線上轉換工具: 不過我把 async/await 語法丟進去後,並沒發現這個工具能夠轉換為相容的 ES5 語法,也許這個線上工具並非最新 Babel 版本。 2. 安裝 Babel 從這篇「以 async/await 為例,說明 babel 插件怎麼搭」看來,Babel 有辦法可以處理 async,也許要找到對的版本。 不過 Windows 下使用看來要花一些功夫,此參考資料提供給開發者來研究了。 3. 感想 那麼 Babel 是否能做為 webView 相容性的解答?也許有可能,但需要持續關注 Babel 的開發狀況、版本差異性、以及 webView 的進展,感覺上是滿累的,就看前端開發者如何抉擇了。

五、如何解決 window.opener

前面提過 window.opener 會是 webView 的無解難題,本篇提供的任何方案都無法完全消除隱憂。不過畢竟 window.opener 使用機率低,所以這裡單獨另開一個章節說明解法,解決思路大致是這樣:
  • 前面提過 webView 無法另開視窗,只能開在同一視窗
  • 那麼可以在新開的頁面檢測 window.opener 相關資訊
  • 如果是正常網頁瀏覽器,window.opener 的網址會是原頁面的網址
  • 但在 webView 之下 window.opener 的網址就不會是原頁面的網址
  • 那麼新開的頁面當檢測到異常後,就可以 alert 提醒訊息
  • 也可按本篇範例程式碼的流程引導訪客使用 Chrome 瀏覽器

六、總結

  • 本篇的內容嚴格來說是「async 語法跨瀏覽器、跨裝置的解決方案」,而 webView 的問題只能說是順道一併解決。
  • 不過內容全是圍繞內建瀏覽器產生的問題,所以也不算偏離主題。
  • 本篇的範例程式碼算是有時效性,將來 JS 的發展若出現另一個突破性的階段,那麼 webView 的問題就要再改成「XXX 語法跨瀏覽器、跨裝置的解決方案」了
  • 即便如此,到時只要修改範例程式碼 try{...} 這裡的內容就好,那麼本篇程式碼的泛用性還是不錯的
更多 Javascript 技巧相關文章:
0 0
如這篇文章對你有幫助,歡迎「分享」到 FB、「追蹤」粉絲團、「訂閱」最新文章

4 則留言:

  1. 用「Chrome 網址協定」這個方法在安卓貌似失效了,我遇到的問題跟這個人一樣
    https://stackoverflow.com/questions/57912296/not-allowed-to-load-local-resource-trying-opening-googlechrome-navigateurl-xx
    這篇文發在2019年9月,比大大的文還早,換句話說這個方法可能還是有效的,只是在某些情況下導致了Not allowed to load local resource的錯誤,但是我怎麼樣都想不透是哪邊有問題,請教一下大大怎麼看?
    p.s.我有問題的手機也是ASUS(Android 8.0.0)

    回覆刪除
    回覆
    1. 關於你的問題可參考 https://stackoverflow.com/questions/58229510/url-scheme-to-open-chrome-firefox-android-os-from-facebook-post → 這篇提到 FB app 在 Android 「Chrome 網址協定」近期失效,所以你需要釐清一下是否你的狀況也是如此

      這篇有提供 solution: window.location = 'intent:https://example.com#Intent;end';

      刪除
  2. 文章裡的方法可以讓IOS裝置在FB裡去跳轉Chrome瀏覽器
    但有沒有辦法讓IOS去跳轉到原有的safari瀏覽器呢?

    回覆刪除
    回覆
    1. chrome 能提供這個功能代表 chrome 提供了這樣的通訊協定
      如果找不到 safari 的這個功能 可能 safari 根本就沒有提供通訊協定
      所以搜尋資料時 需要朝 safari 有沒有提供自身的通訊協定著手

      這些國外討論串供參考:
      https://stackoverflow.com/questions/45378919/how-open-link-in-safari-mobile-app-from-webview
      https://stackoverflow.com/questions/51170092/forcefully-open-link-in-safari-with-ios-11

      刪除

張貼留言注意事項:

◎ 勾選「通知我」可收到後續回覆的mail!
◎ 請在相關文章留言,與文章無關的主題可至「Blogger 社團」提問。
◎ 請避免使用 Safari 瀏覽器,否則無法登入 Google 帳號留言(只能匿名留言)!
◎ 提問若無法提供足夠的資訊供判斷,可能會被無視。建議先參考這篇「Blogger 提問技巧及注意事項」。
◎ CSS 相關問題非免費諮詢,建議使用「Chrome 開發人員工具」尋找答案。
◎ 手機版相關問題請參考「Blogger 行動版範本的特質」→「三、行動版範本不一定能執行網頁版工具」;或參考「Blogger 行動版範本修改技巧 」,或本站 Blogger 行動版標籤相關文章。
◎ 非官方範本問題、或貴站為商業網站,請參考「Blogger 免費諮詢 + 付費諮詢
◎ 若是使用官方 RWD 範本,請參考「Blogger 推出全新自適應 RWD 官方範本及佈景主題」→ 不建議對範本進行修改!
◎ 若留言要輸入語法,"<"、">"這兩個符號請用其他符號代替,否則語法會消失!
◎ 為了過濾垃圾留言,所有留言不會即時發佈,請稍待片刻。
◎ 本站「已關閉自刪留言功能」。

TOP