Android Deep Link 深度鏈接吐葱,看看你在第幾層弟跑?

你的支持對我意義重大孟辑!

?? Hi扑浸,我是旭銳燕偶。本文已收錄到 GitHub · Android-NoteBook 中指么。這里有 Android 進階成長路線筆記 & 博客,有志同道合的朋友巫财,歡迎跟著我一起成長哩陕。(聯(lián)系方式在 GitHub)

1. 背景

  • 你一定遇到過這個場景悍及,你在微信上瀏覽一個朋友分享的淘寶商品心赶,但是你想要在淘寶 App 上打開(畢竟原生 App 才能提供更完整和流暢的體驗)。此時椭符,你需要打開淘寶 App销钝,然后再通過搜索功能一步步找到剛才的商品曙搬。然后鸽嫂,然后就沒有然后了据某。
  • 跳轉過程的操作路徑太長了癣籽,用戶體驗很一般筷狼,用戶很容易在這個過程中流失匠童。那么汤求,有沒有可能實現(xiàn)瀏覽器點擊鏈接直達商品頁的流暢用戶體驗呢严拒?這就是 App 深度鏈接的要做的事了裤唠。

簡單來說种蘸,App 深度鏈接(Deep Link)是一項基礎的 App 優(yōu)化方法劈彪,通過技術手段縮短了用戶操作路徑沧奴,從而優(yōu)化了產(chǎn)品服務的用戶體驗长窄,最終幫助實現(xiàn)了轉化率提升挠日、用戶增長等業(yè)務目標。


2. 應用場景

一鍵跳轉是深度鏈接比較重要的使用場景,但它的能力不僅于此只冻,主要包括以下幾種:

  • 一鍵跳轉: 在用戶已安裝 App 的情況下喜德,從瀏覽器或 QQ垮媒、微信等社交平臺睡雇,一鍵拉起 App 并直達落地頁它抱;
  • 傳參安裝(也叫延遲深度鏈接): 在用戶未安裝 App 的情況下秕豫,引導用戶到應用市場下載安裝應用,并在應用首次啟動后自動直達落地頁抗愁;

這兩個場景分別對應用戶已安裝 App 和未安裝 App 的兩種情況馁蒂,在此基礎上呵晚, 還可以衍生出其他一些業(yè)務化的場景:

  • 分享閉環(huán)(也叫場景還原): 用戶將 App 內容分享到微信等社交平臺,其他用戶通過分享鏈接打開或安裝后打開 App沫屡,自動直達分享內容饵隙,實現(xiàn)流量閉環(huán);
  • 無碼邀請: 用戶通過二維碼 / 鏈接等形式邀請新用戶安裝金矛,新用戶下載安裝后可以識別出邀請來源,免除填寫邀請碼勺届,對用戶更友好(通常是在鏈接中拼接來源業(yè)務標識驶俊,例如 code=[內容頁類型]_[內容 ID]_[App標識]_[用戶標識]);
  • 渠道追蹤: 用戶通過 Web 下載引導頁安裝 App 后免姿,首次啟動時 App 識別并統(tǒng)計下載渠道饼酿,實現(xiàn)渠道效果歸因。

3. 數(shù)據(jù)流轉流程

在深度鏈接的工作流程需要 Wap 端胚膊、客戶端和服務端協(xié)同配合故俐,整體的數(shù)據(jù)流轉示意圖如下:

  • Wap 端: 判斷設備是否安裝指定 App,已安裝則直接拉起 App 并傳遞深度鏈接參數(shù)紊婉,未安裝則引導用戶重定向到應用市場安裝 App药版,并將深度鏈接參數(shù)暫存到服務器;
  • 服務端: 主要是為了兼容設備未安裝 App 的場景喻犁,可以理解為是前端和客戶端之間的通訊橋梁槽片。前端會將設備唯一標識和深度鏈接參數(shù)的映射關系臨時存儲在服務器,將來一段時間內肢础,客戶端可以憑借設備唯一標識從服務端讀取參數(shù)还栓;
  • 客戶端: 根據(jù)前端直傳的深度鏈接直達落地頁,或者在首次啟動時嘗試從服務端讀取暫存的參數(shù)乔妈,再直達落地頁蝙云。

4. 一鍵跳轉實現(xiàn)原理

在用戶已安裝 App 的情況氓皱,可以通過標準的協(xié)議實現(xiàn)一鍵拉起 App 并傳遞深度鏈接參數(shù)路召,目前主要有以下三種協(xié)議:

Deep Link 描述 適用系統(tǒng)
Scheme 協(xié)議 所有系統(tǒng)支持的 App 相互調用的協(xié)議,并且可以傳遞參數(shù) 所有系統(tǒng)
App Links Google 在 Android M 提出的深度鏈接實現(xiàn)波材,我還沒發(fā)現(xiàn)它比 Scheme 的優(yōu)勢在哪里股淡。如果你們項目用了,請告訴我為什么 Android M(6)+
Universal links Apple 在 iOS 9(WWDC 2015)推出的通用鏈接的 deep link 特性 iOS 9 +

這里我們主要介紹 Android 端的實現(xiàn)廷区,主要分為以下幾個步驟:

  • 配置 AndroidManifest.xml: 在 AndroidManifest.xml 中定義接收參數(shù)的 Activity唯灵,并配置 IntentFilter 篩選期望接收的參數(shù)。例如:

    <activity 
        android:name=".app.ProxyActivity"
        android:exported="true"
        android:launchMode="singleTask"
        android:theme="@style/SplashTheme">
        <intent-filter>
            <action android:name="android.intent.action.VIEW"/>
    
            <category android:name="android.intent.category.DEFAULT"/>
            <category android:name="android.intent.category.BROWSABLE"/>
    
            <!-- 注意:path 必須有 / 前綴 -->
            <data 
                android:scheme="xiaopeng"
                android:path="goodsId" />
        </intent-filter>
    </activity>
    

    這里需要注意下幾個細節(jié):

    • android:exported: 從 Android 12 開始隙轻,所有支持隱式啟動的組件必須顯式設置 android:exported 屬性埠帕,因此這里必須設置 android:exported="true"垢揩;
    • SplashTheme 主題: Scheme 協(xié)議是支持冷啟動拉起 App 的,為了保證與點擊 Launcher 冷啟動的體驗相同(如 windowBackground 占位圖)敛瓷,需要用到 SplashActivity 的主題叁巨。
  • 解析 Intent: 通過 Scheme 拉起的 Activity,在其 Intent 中會包含 Web 端傳遞過來的深度鏈接參數(shù)呐籽。參數(shù)實體是一個 URI 格式的字符串锋勺,獲取到這個 URI 后,App 就可以根據(jù)自定義協(xié)議來拉起落地頁狡蝶。例如:

    class ProxyActivity : BaseActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            if (null == savedInstanceState) {
                dispatchIntent(intent)
            }
            finish()
        }
    
        override fun onNewIntent(intent: Intent?) {
            super.onNewIntent(intent)
    
            dispatchIntent(intent)
            finish()
        }
    
        private fun dispatchIntent(intent: Intent?) {
            if (null == intent) {
                return
            }
            val uri : Uri = intent.data ?: return
            // 根據(jù)自定義協(xié)議解析 Uri
        }
    }
    
  • 延遲直達: 嚴格來說庶橱,每次啟動 ProxyActivity 就立刻拉起落地頁并不是一個可靠的方式,因為有時候在拉起落地頁前有一些無法跳過的初始化頁面贪惹。比如用戶之前清除過 App 數(shù)據(jù)苏章,或者 App 隱私政策更新,這個時候一定需要用戶先同意隱私政策奏瞬,再拉起落地頁布近。又比如需要用戶先進入啟動廣告,或進行一些必要的設置頁(選擇個性標簽丝格、選擇城市等)才允許進入落地頁撑瞧。經(jīng)過分析,可以歸納出這些場景都是在 App 冷啟動的時候發(fā)生的显蝌,所以我們只要區(qū)分下冷啟動和熱啟動進入 ProxyActivity 的情況即可预伺。偽代碼示例:

    class ProxyActivity : BaseActivity() {
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            if (null == savedInstanceState) {
                dispatchIntent(intent)
            }
            finish()
        }
    
        override fun onNewIntent(intent: Intent?) {
            super.onNewIntent(intent)
    
            dispatchIntent(intent)
            finish()
        }
    
        private fun dispatchIntent(intent: Intent?) {
            if (null == intent) {
                return
            }
            val uri : Uri = intent.data ?: return
            if (SchemeHelper.getRunningActivityCount() == 1) {
                // 1. 冷啟動
                // 1.1 將 Uri 臨時存儲到全局靜態(tài)域
                SchemeHelper.setPendingSchemeUri(intent.data)
                // 1.2 轉而啟動 SplashActivity,走正常點 Launcher 的啟動流程
                startActivity(Intent(this, SplashActivity::class.java))
            } else {
                // 2. 熱啟動曼尊,直接打開落地頁
                SchemeHelper.handleDeepLink(this, uri)
            }
        }
    }
    
    /**
     * 主頁面
     */
    class MainActivity : BaseActivity() {
        // 啟動初始化邏輯走完后調用:
        SchemeHelper.handleDeepLink(this)
    }
    
    - 1酬诀、SchemeHelper.getRunningActivityCount():registerActivityLifecycleCallbacks 回調,在 Activity onCreate 和 onDestroy 時記維護 runningActivityCount 
    - 2骆撇、SchemeHelper.setPendingSchemeUri():    將 Uri 臨時存儲到全局靜態(tài)域
    - 3瞒御、SchemeHelper.handleDeepLink():         根據(jù)自定義協(xié)議解析 Uri
    
  • 統(tǒng)一路由入口: 除了剛才提到了冷啟動和熱啟動的兩個場景,實踐中的 App 跳轉或路由行為可遠不止這兩種神郊,例如:

    • App 原生跳轉
    • App 網(wǎng)頁容器跳轉 App 原生頁
    • Scheme 協(xié)議喚醒 App 的跳轉(如前所述肴裙,分為冷啟動和熱啟動)
    • Push 消息息喚醒 App 跳轉(分為端外推送和端內推送)

    那么,這么多入口的路由行為如果不統(tǒng)一起來涌乳,對于后序的維護工作會劣化蜻懦。所以這塊需要把 SchemeHelper 中的協(xié)議解析部分統(tǒng)一封裝為全局可用的路由分發(fā)器 RootUrlDispatcher ,實現(xiàn)收口夕晓。

  • adb 測試: 如果開發(fā)階段自測時需要依賴 Web 端給我們提供一個網(wǎng)頁來拉起 App宛乃,測試效率就太低了。我們可以使用 adb 命令來自測:

    命令模板:
    adb shell am start
        -W -a android.intent.action.VIEW
        -d <URI-定義的URI> <PACKAGE-需要測試的應用包名>
    示例:
    adb shell am start
        -W -a android.intent.action.VIEW
        -d "xiaopeng://www.myapp.com/goods/?goodsId=123456" com.xiaopeng.app
    

5. 自定義 Scheme 協(xié)議設計

自定義 Scheme 協(xié)議本質上就是定義一套標識 App 行為的規(guī)則,實踐中采用的 URI(Uniform Resource Identifier征炼,統(tǒng)一資源標識符) 方案析既,下圖是 URI 的通用格式:

實踐中的設計過程多少會帶點 Restful API 的風格。Restful 本身是接口命名的一種規(guī)范谆奥,用 URI 標識一種資源渡贾,再用 HTTP 方法來定義對資源的操作。比如定義 /goods/{goodsId} 是商品的路徑雄右,那么對于商品這個資源的操作可以分為以下幾種:

  • 獲取商品信息: GET /goods/123456
  • 修改商品信息: POST/PATCH /goods/123456
  • 刪除商品: DELETE /goods/123456

把 Restful API 這套理論帶到 App 這邊空骚,是不是也適用呢?比如以下行為是不是也可以用 Restful API 的風格表示:

  • 打開 App 商品詳情頁: GET GoodsDetailActivity/123456
  • 修改 / 刪除商品詳情: 經(jīng)過分析擂仍,這個行為在 Scheme 的場景不成立囤屹;
  • 打開 App 商品推薦列表: GET GoodsListActivity
  • 打開 App 商品評價頁: GET GoodsCommentDetailActivity/123456
  • 打開 App 商品評價修改頁: GET GoodsToCommentActivity/123456(是的,即使是修改的行為也用 GET逢渔,這就是 App 相對于 API 的差異性肋坚,因為 GoodsToCommentActivity 本身就帶修改的動作)

既然在 App 端對資源的訪問行為只有 GET,那么就可以省略掉 GET 這個元素肃廓。再考慮到鏈接需要跨平臺智厌,還有多參數(shù)等因素,鏈接模板需要再進一步改進盲赊。一般推薦采用這種格式的 URI:scheme://host/path?query铣鹏。 例如,鏈接 xiaopeng://www.myapp.com/goods/?goodsId=123456&size=1 打開商品詳情頁哀蘑,并且選擇 size=1 的規(guī)格诚卸。

部分 參數(shù) 描述
scheme xiaopeng:// 業(yè)務獨有的領域,一個 App 可以支持多個 Scheme
host www.myapp.com/ 某一個子域名
path goods/ 頁面路徑绘迁,可以多級別
query ?goodsId=123456&size=1 頁面參數(shù)合溺,可以多參數(shù)

這里需要注意下幾個細節(jié):

  • 登錄引導: 我們定義了 needLogin 這個參數(shù)呢,因為實踐中發(fā)現(xiàn)用戶的賬單詳情頁這一類落地頁是一定要求用戶登錄的缀台。所以我們在拉起落地頁之前增加了一個登錄引導棠赛,在登錄成功后再進入落地頁。例如膛腐,鏈接 xiaopeng://www.myapp.com/goods/?goodsId=123456&size=1?needLogin=1 表示打開 App睛约,先要求用戶登錄后再打開商品詳情頁,并且選擇 size=1 的規(guī)格依疼;
  • H5 跳轉: 有一些活動頁是需要通過網(wǎng)頁容器來承載的痰腮,因此我們希望打開 App 后喚起 MyWebViewActivity 網(wǎng)頁容器來顯示。對于這樣的場景我們可以直接使用 http 或 https 作為 Scheme律罢,App 將這類鏈接直接轉交給 MyWebViewActivity 去呈現(xiàn);
  • 數(shù)據(jù)加密: 為了提高安全性,URI 中的 path?query 的部分可以使用加密算法误辑,scheme://host 的部分需要用于匹配沧踏,并且不帶有風險數(shù)據(jù),可以不加密巾钉。

6. 總結

在 PC 端翘狱,瀏覽器是用戶流量的主要入口,但在移動端砰苍,用戶的流量(使用時間)被分散到大大小小的 APP 上潦匈,而不再是瀏覽器。用戶感興趣的內容分散在各個 APP 里赚导,當用戶想在 APP 上找到某個感興趣的頁面時茬缩,深度鏈接(Deeplink)是一個可以從任何地方將用戶帶到應用內容頁的簡單方式。你用起來了嗎吼旧?

參考資料

你的點贊對我意義重大凰锡!希望大家可以一起討論技術,找到志同道合的朋友圈暗,我們下次見掂为!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市员串,隨后出現(xiàn)的幾起案子勇哗,更是在濱河造成了極大的恐慌,老刑警劉巖寸齐,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件智绸,死亡現(xiàn)場離奇詭異,居然都是意外死亡访忿,警方通過查閱死者的電腦和手機瞧栗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來海铆,“玉大人迹恐,你說我怎么就攤上這事∥哉澹” “怎么了殴边?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長珍语。 經(jīng)常有香客問我锤岸,道長,這世上最難降的妖魔是什么板乙? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任是偷,我火速辦了婚禮拳氢,結果婚禮上,老公的妹妹穿的比我還像新娘蛋铆。我一直安慰自己馋评,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布刺啦。 她就那樣靜靜地躺著留特,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玛瘸。 梳的紋絲不亂的頭發(fā)上蜕青,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音糊渊,去河邊找鬼右核。 笑死热幔,一個胖子當著我的面吹牛澈驼,可吹牛的內容都是我干的。 我是一名探鬼主播茵乱,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼芒篷,長吁一口氣:“原來是場噩夢啊……” “哼搜变!你這毒婦竟也來了?” 一聲冷哼從身側響起针炉,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤挠他,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后篡帕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體殖侵,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年镰烧,在試婚紗的時候發(fā)現(xiàn)自己被綠了拢军。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡怔鳖,死狀恐怖茉唉,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情结执,我是刑警寧澤度陆,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站献幔,受9級特大地震影響懂傀,放射性物質發(fā)生泄漏。R本人自食惡果不足惜蜡感,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一蹬蚁、第九天 我趴在偏房一處隱蔽的房頂上張望恃泪。 院中可真熱鬧,春花似錦缚忧、人聲如沸悟泵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒙具,卻和暖如春球榆,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背禁筏。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工持钉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人篱昔。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓每强,卻偏偏與公主長得像,于是被迫代替她去往敵國和親州刽。 傳聞我的和親對象是個殘疾皇子空执,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內容