1. 程式人生 > >Universal Link 前端部署採坑記

Universal Link 前端部署採坑記

轉載自: http://awhisper.github.io/2017/09/02/universallink/

號外!號外!微信已經封禁Universal Link,已經不能實現跳出微信開啟App

具體細節可以看

但Univeral Link 還是可以學習學習看看的~畢竟幾遍在Safari下還有個好處,可以幹掉schema跳轉的那個難看的錯誤彈框

– 以上來自2018.1.8修改

前言:

文章會適當說一些如何開發iOS上的universal link,但類似的文章太多了一艘一大堆,每篇都介紹的挺清楚,因此也不是重點

本文更加會側重從前端的角度,將整個universal link 部署應用到wap app中的一些策略和一些問題解決辦法

其實整個Universal Link沒啥難的,真正上線過Universal link的人這些應該都趟過一遍了,本文主要是我們team去應用Universal link的時候一些文件沉澱和記錄

Schema VS Universal Link

Deeplink相關的技術,在wap中喚起app其實應用最最廣泛的並不是Universal Link,而是直接Schema跳轉

1location.href = 'schema://xxxx'

並且一般各大APP都會給自己做一套路由體系,這樣其實可以直接在schema頭後面對接路由體系,做到一行schema定位開啟任意App內功能介面(我就不詳細扯路由的事了)

123456- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { if ([[url absoluteString] hasPrefix:@"schema://"]) { [[WKDispatcher sharedInstance] operationObjectFromRouteURL:[url absoluteString]];//路由 return
YES; }}

如果單純為了實現deeplink – 在WAP上開啟App,並且傳遞來資料資訊,定位App內的具體邏輯,那麼Schema就夠了,其實沒必要上Universal Link,Schema相當於是很特殊的Url,他是schema://xxx這種樣子,如果安裝了APP才能支撐跳轉這種Schema Url,如果沒安裝APP就沒任何效果,而Universal Link則是把普通url,長http://xxx.xxx.xxx/xxx這樣的Normal Url,如果安裝了App,就能像Schema一樣傳遞給App,延續App內邏輯,如果沒裝App,則還會繼續在瀏覽器裡跳轉這個Normal Url

Schema的弊端

Schema無法判斷是否安裝App

一定會有這樣的產品需求的:

  • 如果已經安裝App,則開啟App
  • 如果沒有安裝App,則前往下載App

瀏覽器實際上是沒有能力判斷手機裡是否安裝了某個App的,所以聰明的程式設計師們選擇了討巧的方法

12345678910111213141516171819try { var appSchema = 'schema://xxxx'; if ($.os.ios) { location.href = openNALocation; //location.href 開啟schema } else { $('body').append('<iframe src="' + appSchema + '" style="display:none"></iframe>'); //iFrame 開啟 schema } }catch (e) {}//延遲1000秒setTimeout(function () { if ($.os.ios) { location.href = `https://itunes.apple.com/us/app/idxxxxxxx?mt=8`; } else { location.href = `https://xxx.xxx.xxx/xxx/xxx.apk`;//直接apk下載link } },1000
  • 首先發起跳轉Schema
    • 如果沒安裝App,會開啟App失敗,沒效果
    • 如果安裝App,會成功開啟App
  • 延遲1000ms
    • 如果沒安裝App,Schema開啟失敗,等1000秒後會自動跳轉
    • 如果安裝App,App會開啟,當前網頁會被暫停,這延遲程式碼不會執行

聰明的人會發現,這樣有個風險,如果使用者開啟APP成功後,又手動切回瀏覽器,那麼延遲1000ms的程式碼依然會執行,安卓會跳出下載apk包得提示,iOS會又再度跳到Appstore,但這個瑕疵也不是太大的問題,所以這種做法被普遍採用,運用在各種安裝就跳轉,不安裝就下載的使用者場景。

安卓這麼用挺好,iOS有個討厭的彈框

如果使用者沒有安裝App,那麼他一定會經歷2個事情

  • schema開啟app,但是失敗
  • 延遲後,跳轉下載App

在第一個環節,安卓上schema開啟失敗,沒有任何反映,也沒有任何提示,一切順利,但是iOS就不同了。

schema會彈個可惡的跳轉失敗的框
1504334347900

然後再延遲後彈跳轉AppStore的框
1504334376521

Schema被很多App禁止,比如微信手百

Schema被廣泛使用,從瀏覽器中喚起開啟專門的App,但這並不被很多App認可,比如微信手機百度,他們本身除了瀏覽網頁以外有其他的使用場景,所以站在微信/手百的角度,並不希望使用者為了看一些分享和內容就跳出微信/手百的App,於是這些客戶端攔截了Schema,使得所有Schema都無法生效。

於是不得已,廣大開發者只好針對,微信/手百,等特殊UA資訊,展現出蒙層,引導使用者用系統/外部瀏覽器開啟

Universal Link 解決 Schema的弊端

開篇就說了,如果你單純為了能讓wap開啟App,Schema就能做到了,Universal Link的意義則是把普通url,也賦予了能開啟App的能力,而不必編寫專門的Schema Url去喚起App

Schema 的2個弊端確實能通過Universal Link解決

不同於Schema,在沒裝App的時候,Universal Link他也是一個合法的url連結,瀏覽器可以正常跳轉,因此不會出現在iOS上討人厭的框

Universal Link目前還沒有基於iOS的UI/WKWebView的應用進行攔截,所以目前看還是能突破微信/手百的封鎖。(以後,不好說啊~)

Universal Link 開發

類似的話題,隨便搜搜Universal Link能搜到一大堆,我這裡就略微多囉嗦兩句,一般各大教程裡會反覆說的,我儘量一帶而過,多說點我遇到的坑

配置apple-app-association

究竟哪些的url會被識別為Universal Link,全看這個apple-app-association檔案
Apple Document UniversalLinks.html

  • 你的域名必須支援Https
  • 域名根目錄下放這個檔案apple-app-association,不帶任何字尾
  • 檔案為json儲存為文字即可
  • json按著官網要求填寫即可

怎麼寫json其實沒啥可教的,滿世界的文章都教你咋寫了,我們看個例子,點下面的連結,你的瀏覽器就會自動把知乎的apple-app-association的json file給down下來

劃重點

有心人可能看到,知乎的Universal Link配置的是 oia.zhihu.com 這個域名,並且對這個域名下比如/answers /questions /people 等urlpath進行了識別,也就是說,知乎的universal link,只有當你訪問 https://oia.zhihu.com/questions/xxxx,在移動端會觸發Universal Link,而知乎正經的Urlhttps//www.zhihu.com/questions/xxx是不會觸發Universal Link的,知乎為什麼製作,為什麼不把他的主域名配置Universal Link,這是由於Universal Link的一個大坑所致

PS.

apple-app-association 你可以看完全了知乎的json file,會發現裡面也不止是 universal link

蘋果的一些其他功能都和apple-app-association有關,都需要配置這個檔案,增加更多json欄位資訊

比如Hand off 還有一些跨Web&App的分享

測試是否正確

蘋果官方提供了一個網站來測試你配置的域名apple-app-association是否正常work

這個網站有點SB,就是你用他測試不通過,其實Universal Link也可能不生效的,比如我把知乎的oia.zhihu.com輸入進去,他就沒感應到,認為沒有。我搜索的時候,發現也有人發現了這個問題,反正可以當個參考

配置iOS App工程

  • 開發者中心證書開啟Associated Domains
  • 工程配置Associated Domains
  • 將你apple-app-association所在域名配置進去
  • 給你的工程像Schema的OpenUrl一樣,編寫App被喚醒後的處理邏輯

12345678#pragma mark Universal Links- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler { if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { NSURL *webUrl = userActivity.webpageURL; [self handleUniversalLink:webUrl]; // 轉化為App路由 } return YES;}

恩比較千篇一律,我不多說了

Universal Link的基本運作流程

  • APP第一次啟動 or APP更新版本後第一次啟動
  • APP向工程裡配置的域名發起Get請求拉取apple-app-association Json File
  • APP將apple-app-association註冊給系統

  • 由任意webview發起跳轉的url,如果命中了apple-app-association註冊過的通用連結

  • 開啟App,觸發Universal Link delegate
  • 沒命中,webview繼續跳轉url

在你進行apple-app-association 以及 App工程的配置之後,整個Universal Link的運作流程完全由系統控制了

Universal Link 採坑

整個Universal Link其實真要只是開發完成,完全寫不了幾行程式碼,就差不多搞定了,不過還真是踩了幾個坑

跨域

前端開發經常面臨跨域問題,恩Universal Link也有跨域問題,但不一樣的是,Universal Link,必須要求跨域,如果不跨域,就不行,就失效,就不工作。(iOS 9.2之後的改動,蘋果就這麼規定這麼設計的)

這也是上面拿知乎舉例子的時候重點強調的一個問題,知乎為什麼使用oia.zhihu.com做Universal Link?

  • 假如當前網頁的域名是 A
  • 當前網頁發起跳轉的域名是 B
  • 必須要求 B 和 A 是不同域名,才會觸發Universal Link
  • 如果B 和 A 是相同域名,只會繼續在當前WebView裡面進行跳轉,哪怕你的Universal Link一切正常,根本不會開啟App

是不是不太好理解,那直接拿知乎舉例子

知乎的一般網頁URL都是www.zhihu.com域名,你在微信朋友圈看到了知乎的問題分享,如果copy url 你就能看到這樣的連結

微信裡其實是遮蔽Schema的,但是你依然能看到大大的一個按鈕App內開啟,這確實就是通過Universal Link來實現的,但如果知乎把Universal Link 配在了www.zhihu.com域名,那麼即便已經安裝了App,Universal Link也是不會生效的。

一般的公司都會有自己的主域名,比如知乎的www.zhihu.com,在各處分享傳播的時候,也都是直接分享基於主域名的url,但為了解決蘋果強制要求跨域才生效的問題,Universal Link就不能配置在主域名下,於是知乎才會準備一個oia.zhihu.com域名,專為Universal Link使用,不會跟任何主動傳播分享的域名撞車,從而在任何活動WAP頁面裡,都能順利讓Universal Link生效。

簡單一句話

  • 只有當前webview的url域名,與跳轉目標url域名不一致時,Universal Link 才生效

apple-app-association 覆蓋

我們業務機房的叢集是大部門下幾條業務線共用的,有一整套雲服務系統來進行機房叢集的管理,有統一的接入層進行分發。雖然是不同的產品線,不同的服務,但是共享分散式的機房進行運作的。

很多Universal Link的教學文章是這麼寫的

  • 將json命名為 apple-app-association 不要亂改名
  • 將file 上傳到域名所在的伺服器根目錄下

於是我就將我們文庫的apple-app-association,上傳到我準備的wenkuUniversal域名的所在機器的根目錄下。(因為機房都是分散式的,所以其實就是upload的全部門下的很多機器上)

同部門另一個產品線閱讀後來也開始嘗試Universal Link,也要把他們寫好的apple-app-association上傳到他們的yueduUniversal域名所在的機器的根目錄下。

因為都是同樣的檔名,又因為整個事業部機器實際上是共用的,因此就發生了覆蓋。

解決辦法

  • 共用同一個 apple-app-association

因為apple-app-association的具體內容裡有App 的Bundle ID在,因此可以簡單的把2個json file 進行merge,你的App bundle 生效你的link,我的App bundle生效我的link

但其實並不推薦,畢竟雙方都要小心在更新的時候,不能覆蓋對方,並且這樣做也很不合理,apple-app-association也不止為universal link 一個feature工作,當面臨跨app / web share 甚至hand off的時候,共用一個json file 還是有坑

  • 接入層分發不同json file

2個產品線的link域名其實是不一樣的,只不過恰巧這兩個域名最重打到得機器是同一個或者說有重疊,因此產生了覆蓋,完全可以將json檔案儲存成各自的名字,在接入層對域名進行分發

最終App也是通過Get請求去拉取apple-app-association的,只要Get到,並且ssl安全性上符合要求(強制https)就沒問題

隱藏的坑:apple-app-association被覆蓋後如何更新

我們線上已經work的Universal Link功能,突然有一天發現壞了,查了一圈最後查到被閱讀覆蓋了,那就修復唄,修復倒是沒問題,問題在於修復後的universal link,使用者必須重新安裝一次app,才能重新work,這個太坑了啊

所以關鍵是需要掌握apple-app-association的更新時機,反覆重新殺APP重開完全沒用,刪了APP重灌確實有用,但不可能讓使用者這麼去做

這裡解釋了,每次App安裝後的第一次Launch,會拉取apple-app-association,除此之外在Appstore每次App的版本更新後的第一次Launch,也會拉取apple-app-association。

也就是說,一旦不小心因為意外apple-app-association,想要挽回又讓那部分使用者無感,App再發一個版本就好了

Universal Link 會因為使用者行為而失效

Universal Link 觸發後開啟App,這時候App的狀態列右上角會有文字提示來自XXApp,可以點狀態列的文字快速返回原來的AP

如果使用者點了返回微信,就會被蘋果記住,認為使用者並不需要跳出原App開啟新App,因此這個App的Universal Link會被關閉,再也無效。

想要開啟也不是不行,讓使用者重新用safari開啟,universal link的頁面,然後會出現很像蘋果smart bar的東西,那個東西點了後就能開啟(我是看到的,我沒親自操作過)

Universal Link 業務部署

知乎的apple-app-association可以看到裡面有一大堆的WAP的URL,比如/answers /questions /people等,知乎都將它一一對映到App得對應介面裡,問題/回答/人詳情頁。這是因為知乎的WAP站和APP的功能幾乎是一致的。因此知乎的Universal Link的使用方式,可以說是很經典的遵循著蘋果的原始設計初衷通用連結,將wap url,變成通用url,同樣的url,對應著2個跳轉,web跳轉/app跳轉,但是他們是同一個功能。

我們產品線面臨的情況不一樣,我們的產品線文庫,他的WAP和APP功能差異非常大,可以說除了文件閱讀頁/view,WAP與APP都有這個功能,其他的功能WAP是WAP的,APP是APP的,形態和場景都有明顯差異。除了/view這個功能,我們可以按著通用連結的設計,將APP閱讀頁跳轉,與WAP閱讀頁跳轉進行統一。其他時候Universal Link對於我們業務來說就是一個更強大的Schema(突破舊Schema侷限的=),他只需要跳轉到APP,他沒有合法的WAP Url可以讓瀏覽器在沒有安裝App的情況下繼續跳轉。

我們的Universal Link 業務部署

我們的Universal Link就像知乎一樣,沒有選擇我們的主域名,而是選了一個完全沒在WAP上有任何頁面和流量的域名,我們的apple-app-association是這麼寫的

12345{ "appID": "xxxxxx.xxx.xxx.xxxxx", "paths":[ "/view/*", "/_iosuniversallink/*"]},

我們的AppDelegate中具體handleUniversalLink的邏輯是這麼寫的

1234567891011121314151617181920- (BOOL)handleUniversalLink:(NSURL *)url { NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:YES]; NSString *host = components.host; if ([host isEqualToString:@"xxx.xxx.xxx"]) { //host判斷,雖然沒啥意義 if (pathComponents.count >= 3) { //地址匹配+頁面跳轉 NSString *router; if ([pathComponents[1] isEqualToString:@"view"]) { router = @"xxx";//生成開啟APP閱讀頁的專屬Router } else if ([pathComponents[1] isEqualToString:@"_iosuniversallink"]) { router = @"xxx";//解析出APP能識別的任意路由, } if (router && router.length > 0) { [[WKDispatcher sharedInstance] operationObjectFromRouteURL:router];//無論是閱讀頁路由還是任意路由,發起跳轉 } return YES; } } return NO;}

可以看出來我只打開了這個域名下https://xxx.xxx.xxx/view/* 和 https://xxx.xxx.xxx/_iosuniversallink/*2個Universal Link Path.對沒錯,不像知乎那麼多。

  • /view/* 後面的*直接是閱讀頁ID,用於快速生成閱讀頁路由,發起跳轉
  • /_iosuniversallink/* 後面的*其實應該填寫的是我們App已經設計好的路由字串,識別出路由字串後,發起跳轉

其實可以看出來/_iosuniversallink是完全包含/view的,因為APP閱讀頁天然也是包含在我們的路由規則內的,只要這裡有路由策略,擴充套件力已經足夠支援任意APP頁面了,因此apple-app-association只配置了2個,但是如果有計劃外的特殊case,大不了更新一下,也沒多大事。

為了統一WAP&APP,為了通用連結的效果

我剛才提到,我們選擇的Universal Link的域名其實是一個沒有實際頁面的域名,也就是說https://xxx.xxx.xxx/view/*這個url,如果沒安裝APP因此觸發WebView繼續跳轉原地址,會直接404。處理很簡單,重定向一下

1234router.use('/view', function (req, res, next) { var path = req.path; res.redirect('https://wk.baidu.com/view' + path + '?st=1#1');});

整個效果就是

  • 跳轉https://xxx.xxx.xxx/view/*
    • 已安裝App
      • 開啟App 觸發handleUniversalLink
      • 走到/view/分支,拼接閱讀頁路由
      • 跳轉
    • 未安裝App
      • WebView原地跳轉https://xxx.xxx.xxx/view/*
      • 命中伺服器的重定向邏輯
      • 重定向到https://wk.baidu.com/view/*
      • 開啟我們的WAP閱讀頁

這就是為啥明明/_iosuniversallink是完全包含/view能力的,但還是要把/view/單獨處理的原因,為的是實現WAP與APP的統一設計,為了通用連結這個初衷

不為統一WAP&APP 只拿來當強化版Schema使用

同樣的道理,https://xxx.xxx.xxx/_iosuniversallink/*這個url,也沒有實際的頁面,如果不進行重定向,也會直接返回404,因此看一眼重定向的程式碼

1234router.use('/_iosuniversallink', function (req, res, next) { var redirecturl = 'https://wk.baidu.com/topic/naiosappstore'; res.redirect(redirecturl);});

解釋一下https://wk.baidu.com/topic/naiosappstore就是我們為iOS下載App準備的專門的WAP單頁面,這個頁面開啟後會自動延遲500ms,發起跳轉appstore

整個效果就是

  • 跳轉https://xxx.xxx.xxx/_iosuniversallink/*
    • 已安裝App
      • 開啟App 觸發handleUniversalLink
      • 走到/_iosuniversallink/分支,拼接出任意App內的介面路由
      • 跳轉介面
    • 未安裝App
      • WebView原地跳轉https://xxx.xxx.xxx/_iosuniversallink/*
      • 命中伺服器的重定向邏輯
      • 重定向到https://wk.baidu.com/topic/naiosappstore
      • naiosappstore頁面會延遲跳轉AppStore
      • 開啟AppStore下載

這個設計看起來就是完美解決了PM得需求

  • 如果已安裝App,跳轉對應介面
  • 如果沒安裝App,跳轉App下載介面

解決了舊Schema模式下的弊端問題:

  • Schema無法判斷是否安裝App,只能採用setTimeout的Trick方式
  • Schema的Trick方式會有一個醜陋的錯誤跳轉彈框
  • Schema無法在微信/手百等App內,開啟我們自己的App

簡單的說,這樣設計的初衷就是,我不為了通用連結這一目的來使用Universal Link,來統一WAP&APP的URL跳轉,我就為了把Universal Link當做加強版Schema來使用