遷移老文章到掘金
Update 2016.11.23
更新一個最新的方案,老文章裡面介紹的2個方案都不如這個易操作,唯一的問題是就看iOS10的覆蓋率了,覆蓋率不夠之前下文的裝置指紋方案
可以與這個方案互補
- 只支援iOS10以上
- 通過剪下板
- 跨越瀏覽器app與宿主app,傳遞資料
一個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就是這麼做的
友盟通過這個方法,知道了使用者是從哪個網頁看到的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專案提供的思路
方案思路
詳細說一下思路,如果我們能在使用者訪問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方案了
上面的流程,如果用程式碼進行開發還算挺麻煩的,所以我封裝了一個工具,來輔助進行這一串靜默讓使用者無感知的操作
按理說整個流程應該分為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的邏輯就不細說明了。