iOS app與瀏覽器深度連結 DeeperLink

折騰範兒_味精發表於2019-02-27

遷移老文章到掘金

Update 2016.11.23

更新一個最新的方案,老文章裡面介紹的2個方案都不如這個易操作,唯一的問題是就看iOS10的覆蓋率了,覆蓋率不夠之前下文的裝置指紋方案可以與這個方案互補

  • 只支援iOS10以上
  • 通過剪下板
  • 跨越瀏覽器app與宿主app,傳遞資料

clipboard.js

一個1w Star的js庫

因為iOS10系統給js開放了API可以操作剪下板,因此直接使用這個庫,可以很方便的在任何App的webview(包含safari,微信,qq,百度框等)將資料存入剪下板

然後很方便的在其他任何app中,讀取出剪貼簿的資料用於互通

這個方案在準確度上,成功率上,適用範圍上全面優於下文討論的裝置指紋SafariViewController方案

忘掉SafariViewController方案吧,這個方案現在非常的坑

原因是,SFViewController的方案,Openurl事件會以各種方式被系統吞吃掉事件,duplicate等等,都會導致失敗率非常的高

Deeper Link 簡介

本文主要介紹,app跨域訪問app外部的瀏覽器的資料的方案,包括外部safari,或者QQ,微信,手百等外部app內的瀏覽器。

主要使用場景就是:

使用者在別的wap網頁上,產生了使用者行為,使用者資料,但是還沒下載app,當使用者下載app後,打算直接在app內延續之前在wap上的行為和資料的時候,就需要運用到跨越瀏覽器與app鴻溝的,互通方案。

簡單舉個例子就是:

使用者在微信瀏覽器裡,訪問某個頁面,感興趣並且登陸了,然後引導下載了app,等使用者下載完app後第一次開啟,希望能自動就完成登陸,甚至同步下來一些剛才使用者在wap頁面上操作的資料

如果能發生跨瀏覽器與app的互通,除了這個case之外,還可以有更多地自由發揮,設計出更加舒暢的使用者體驗

這就是 Deeper Link

想要實現這樣目前看有2個方案,各自都有弊端,都不是完美的,本文會詳細說明這兩個方案

  • 裝置指紋唯一識別方案
  • iOSSafariCookie互通方案

裝置指紋唯一識別方案

如果使用者在wap頁面,能通過某種方式識別到唯一的裝置標識,當使用者離開去下載app,下載完成第一次開啟app的時候,app能識別到一樣的裝置標識,那麼就可以判斷第一次開啟app的使用者,就是剛才瀏覽wap網頁的使用者,這樣服務就可以把剛才wap上操作的資料結果,通過網路下發給app,從而讓app實現,還原剛才wap的操作場景

方案流程

  • 使用者在wap網頁上產生了行為,產生了使用者個人資料
  • wap網頁收集了一種能夠唯一標識裝置的資訊,並且傳送給了伺服器
  • app安裝完畢後第一次執行,也去通過app嘗試收集唯一標識裝置的資訊,並且發給伺服器
  • 伺服器經過對比,發現app的唯一標識與wap網頁發上來的唯一標識能夠匹配
  • 伺服器判斷,是同一個人操作,於是下發使用者個人資料

縱觀整個流程發現,一切的核心,一切的關鍵,就是那個唯一標示

選取唯一標識

這個唯一標識要具備苛刻的條件,想找到其實很不容易

  • 選擇當做唯一標識的內容,必須能讓app獲取的到
  • 選擇當做唯一標識的內容,必須也能讓wap獲取的到
  • 選擇當做唯一標識的內容,還必須有能力區分出不同的裝置,如果選的唯一標識好幾個裝置取出來的都一樣,那麼就亂套了

那麼我們看看遵循這幾個條件,我們能選擇啥?

  • UDID,MAC地址啥的,別說wap了,app都不可能取到了
  • JS有好幾套,通過網頁渲染canvas的方案獲取螢幕”指紋”,但這玩意app不可能拿到完全一致的東西,二者對不上,就沒任何意義
  • IDFA,IDFV,這玩意app是能取到了,但是wap拿不到啊

上面說的幾個都是相對來說,如果能雙方都拿到,是可以比較精準的進行裝置唯一標識的,但問題是,我們拿不到。。怎麼辦?

看看下面幾個資料

  • 裝置螢幕尺寸(iOS裝置如此的統一,一共就那麼幾個螢幕尺寸,重複的還不一堆一堆的)
  • 裝置作業系統(iOS系統碎片化如此的低,大部分幾乎都升到較高階的系統版本,重複的依然一堆一堆)
  • 裝置IP(IP這玩意會變啊,離開WIFI進入3G,經常變,並且IP這玩意在同一WIFI下也重複的一堆一堆的啊)
  • 訪問時間(時間這玩意更沒譜了,你們的使用者量越大,某一個確定的時間段內,發生第一次安裝,重複的就越多)
  • 還有更多類似的資料

發現沒有,上面的資料最大的特點就是,有一定的描述裝置體徵的資訊,但是如果只靠這一個描述資訊,那結果就是重複的太多太多,根本沒法確定一個唯一性。

但是,如果我們把這麼些描述資訊做成一個合集,同一時間內滿足所有的條件,那麼這個裝置重複的概率一下就縮減了太多太多。

舉例說明

舉個例子,到app安裝完畢第一次開啟的時候,所有訪問過wap的裝置資訊,把他們的資訊全都收集起來,找到同樣的螢幕尺寸,同樣的作業系統版本,同樣的IP地址,訪問時間相差不超過10min(暫定)的裝置,在如此多得限定條件下,我們近乎可以認定為,是具有唯一性的裝置,是同一個人

可以看到這裡面眾多的資訊一起去過濾,比較強的過濾條件就是IP,但因為IP存在頻繁變化,所以追加了時間條件,IP也可能因為WIFI路由器的原因導致,IP也存在重複和誤傷,這時候,又輔助了簡單的裝置資訊進行二次過濾。

這樣我們就找到了一個並不完美的唯一標識,有了這個唯一標識,就可以實現我們的跨瀏覽器和app的互通。

其實友盟的SDK就是這麼做的

友盟 SDK文件

友盟通過這個方法,知道了使用者是從哪個網頁看到的app下載的廣告,然後發生的去appstore下載並執行的行為,從而有效的能核算廣告的收益

a.通過對應用appstore URL進行封裝,獲取分渠道點選使用者的相關資訊,包括:時間、IP、裝置型別、作業系統版本;

b.通過在應用中整合程式碼,獲取初次開啟應用的使用者資訊,包括:時間、IP、裝置型別、作業系統版本;

c.實時對比不同渠道點選使用者和應用啟用使用者資訊,區分不同渠道帶來的啟用使用者;

d.此統計方式不用媒介提供統計資料,實時自動對比,會存在一定誤差,但可以基本衡量各渠道間及不同時期的渠道啟用轉化資料。

方案弊端

他有什麼弊端嗎?弊端還是挺明顯的,因為他是不完美的唯一標識,所以就存在著誤傷。

什麼是誤傷?使用者A瀏覽了WAP介面,使用者B恰巧用同一螢幕,同一作業系統版本,同一網段出口IP,在既定時間內,B使用者下載並執行了APP,這樣我們這套方案,會把B識別成A,等到A真的下載完APP後再來執行,資料可能已經失效了

這種誤傷是概率存在的,在現有的限定條件下,隨著app的使用者體量越來越大,這種誤傷將會越來越明顯。

iOSSafariCookie互通方案

方案簡介

接下來介紹另外一種方案,iOSSafariCookie互通,這種方案藉助的是iOS9系統新出的一個系統APISFSafariViewController,這個API是專為Safari設計的。所以這套方案有他的特點

  • 優點:精準,不會誤傷。
  • 缺點:只能通過safari,不能借助QQ,微信,手百等第三方app的瀏覽器

感謝SafariAutoLoginTest這個demo專案提供的思路

SafariAutoLoginTest Github地址

方案思路

詳細說一下思路,如果我們能在使用者訪問wap頁面的時候,通過網頁,網手機裡寫入一些使用者的行為和資料,比如使用者名稱,然後在app執行的時候去讀取這個資訊,那麼就自然能建立起,wap頁面訪問,和app下載安裝後第一次執行,二者之間的聯絡。但是想要做到這一點,談何容易。

大家都知道,iOS是有沙盒的,不同app之間,幾乎不可能跨越沙盒屏障來訪問資料,wap在瀏覽器裡可以寫資料進入cookie,儲存在手機上,這沒問題,但是app所在的cookie,和剛才的外部瀏覽器所在的cookie,分屬不同沙盒,完全就不是同一份cookie。我們在wap上寫cookie寫進的是safari的cookie,我們開啟自己app讀cookie讀得是自己app的cookie。

有什麼方法可以跨越沙盒傳遞資料?URL Scheme沒錯,通過OpenUrl的方式。如果我在wap頁面訪問,wap頁面發出來一個已經和我們的app約定好的URL Scheme跳轉,那麼就可以,喚起我們的app,並且伴隨著url,傳遞來資料。

如果使用者手機裡安裝了我們的app,使用者先去瀏覽wap頁面,wap頁面觸發了url跳轉,自動喚起了已經安裝的app,並且伴隨著url傳遞來了資料,一氣呵成,沒錯使用者很自然的從wap上的操作行為,延續到了app上。

問題來了,如果使用者沒裝app怎麼辦?難道讓使用者先瀏覽Wap站,產生了行為資料,被引導下載app,下載完後,重新回到wap站,重新再由wap站發起url跳轉?這體驗簡直渣到爆,簡直無法忍受

SFSafariViewController

iOS9以後,蘋果推出了SFSafariViewController這個全新的類,這個類的API允許在app內開啟一個safari瀏覽器,而不是一個app內部的webview。

這個app內safari和外面系統的safari是同一個,共享同一個沙盒,可以操作同一個cookie

剛才我們設想的操作流程,使用者體驗很差的流程

  • 使用者瀏覽wap站
  • 使用者引導下載app
  • 使用者回到wap站,跳轉app
  • wap通過openurl喚醒app傳遞資料

經過app內safari的處理,我們可以採用一些鬼點子,順著這個舊思路,把使用者體驗極差的第三部,第四部,給隱藏了,讓使用者無感知的靜默完成,這樣方案就完美了

  • 使用者通過safari瀏覽wap站,wap站寫使用者行為資料進入cookie
  • 使用者通過引導下載app,執行app
  • 第一次執行app,app內靜默的開啟一個純透明safari(讓使用者感覺不出來)
  • 純透明的safari訪問一個專門用來靜默取cookie得頁面
  • 純透明的safari訪問的取cookie的頁面,取到了正確的cookie資料,
  • 純透明的safari將資料通過openurl,靜默的回傳給app
  • app拿到瀏覽器資料後,銷燬無用的純透明safari

流程上看起來很複雜,但結果就是,使用者用系統safari,瀏覽了wap站,下載了app,app開啟後就自動能恢復到他瀏覽wap站的個人資訊了(或者其他資料)

VKSafariDomainBridge使用

這個工具已經不推薦使用了!!

原因是,SFViewController的方案在iOS10以後,Openurl事件會以各種方式被系統吞吃掉事件,duplicate事件,等等,因為有了更好的替換方案,所以

所以非常非常不推薦繼續使用這個SafariAutoLogin方案了

上面的流程,如果用程式碼進行開發還算挺麻煩的,所以我封裝了一個工具,來輔助進行這一串靜默讓使用者無感知的操作

VKSafariDomainBridge Github地址

按理說整個流程應該分為2部分

  • wap頁面功能:
    – 使用者瀏覽wap頁的存cookie (wap地址1,使用者訪問用的)
    – 隱藏safari瀏覽的讀cookie頁面(wap地址2,靜默程式碼訪問用的)
    – 隱藏safari跳轉openurl功能
  • app內功能:
    – 開啟隱藏safari
    – 收聽openurl的回撥,處理資料
    – 關閉隱藏safari

鑑於實在是不會h5相關的開發,所以我封裝的工具就只包含app內的功能

複製程式碼

//初始化 VKSafariDomainBridge
NSURL *url = [NSURL URLWithString:@”wap地址2,靜默程式碼訪問用的url”];
NSString *key = @”xxkey”
[VKSafariDomainBridge VKSetupSafariBridgeUrl:url AndKey:key];

>url的地址就是`wap地址2,靜默程式碼訪問用的`
key作為協議識別關鍵字,隱藏safari發起的跳轉,通過這個key識別,才會走入VKSafariDomainBridge的處理邏輯,如果是其他正規渠道的openurl跳轉,key不匹配,便直接走正常邏輯,不會進行VKSafariDomainBridge處理

>想要獲取wap使用者資料的時候

>```objectivec
[[VKSafariDomainBridge VKSingleton]VKGetSafariInfo:^(BOOL success, NSString *info) {
        NSLog(@"%@ status = %@",info,@(success));
}];
複製程式碼

通過回撥,如果成功success會返回YES,並且整個跳轉含有資料的url會被轉成string,通過block返回,如果失敗,則會返回NO

程式碼分析

這個功能需要通過appdelegate的openurl回撥來實現,既然是封裝工具,就要做到無侵入性,寫成category形式,只要使用者匯入工程,便可以一行程式碼不需要寫,自動生效。

application:openURL:options:這個方法,如果開發者沒有在工程中用到,我會自動新增,保證了openurl回撥可以正常工作。

如果開發者已經在工程中使用,已經有很多使用者自己的openurl協議要處理了,那麼我的category會生成一個新方法,交換掉老的系統函式(MethodSwizzling),先判斷url協議裡是否含有上面提到的專屬Key,含有則走我的處理邏輯,如果不含有,呼叫老函式,保證原專案功能無異常。

SEL origSelector = @selector(application:openURL:options:);
SEL newSelector = @selector(vkApplication:openURL:options:);
    
Method origMethod = class_getInstanceMethod(class,origSelector);
    
if (!origMethod) {
    SEL emptySelector = @selector(vkEmptyApplication:openURL:options:);
    Method emptyMethod = class_getInstanceMethod(class,emptySelector);
    IMP emptyImp = method_getImplementation(emptyMethod);
    class_addMethod(self, origSelector, emptyImp,
                    method_getTypeEncoding(emptyMethod));
}
    
origMethod = class_getInstanceMethod(class,origSelector);
Method newMethod = class_getInstanceMethod(class,newSelector);
if (origMethod && newMethod) {
    method_exchangeImplementations(origMethod, newMethod);
}
複製程式碼

接下來就是開啟一個透明safari,等待來自網頁的openurl跳轉。製作透明safari的方法就是new出來後,alpha改為0,直接present。

-(void)VKGetSafariInfo:(VKSafariReturn)rtBlock
{
    if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 9.0) {
        if (rtBlock) {
            self.rtblock = rtBlock;
            
            SFSafariViewController *safari = [[SFSafariViewController alloc]initWithURL:self.safariUrl];
            safari.delegate = self;
            safari.modalPresentationStyle = UIModalPresentationOverCurrentContext;
            safari.view.alpha = 0.0f;
            self.safari = safari;
            
            UIViewController *currentVC = [self getCurrentVC];
            self.currentVC = currentVC;
            [currentVC presentViewController:safari animated:NO completion:nil];
        }
    }else
    {
        if (rtBlock) {
            rtBlock(NO,nil);
        }
    }
    
}
複製程式碼

當透明safari載入完畢後,略微延遲後直接銷燬safari,如果在延遲期間,openurl返回則判斷,取cookie資料成功,回撥成功,如果超時,就判斷取cookie資料失敗,回撥失敗。

此處是SFSafariViewController的delegate回撥

-(void)safariViewController:(SFSafariViewController *)controller didCompleteInitialLoad:(BOOL)didLoadSuccessfully{
    __weak typeof(self) weakself = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.timeOut * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakself.currentVC dismissViewControllerAnimated:NO completion:^{
            weakself.safari = nil;
            weakself.currentVC = nil;
        }];
        [weakself VKTimeOut];
    });
}
複製程式碼

openUrl的邏輯就不細說明了。

相關文章