Android AppLinks 接入

Geedio發表於2019-03-04

tags: AppLinks


以下內容分兩部分。
第一部分譯自官網文件,主要為App Links的接入指南;第二部分為筆者的接入實踐,總結了接入過程遇到的問題,以及對App Links的使用上的思考。
以上內容,僅供個人學習、記錄、參考使用,如有紕漏,還請留言指正。

[TOC]


驗證 Android App Links

官網文件:Verify Android App Links

Android App Links是一種特殊的Deep Links,它使Android系統能夠直接通過網站地址開啟應用程式對應的內容頁面,而不需要使用者選擇使用哪個應用來處理網站地址。

要新增Android App Links到應用中,需要在應用裡定義通過Http(s)地址開啟應用的intent filter,並驗證你確實擁有該應用和該網站。如果系統成功驗證到你擁有該網站,那麼系統會直接把URL對應的intent路由到你的應用。

為了驗證你對應用和網站的所有權,以下兩個步驟是必須的:

  1. 在AndroidManifest裡要求系統自動進行App Links的所有權驗證。這個配置會告訴Android系統去驗證你的應用是否屬於在intent filter內指定的URL域名。
  2. 在以下連結地址裡,放置一個數字資產連結的Json檔案,宣告你的網址和應用之間的關係:
    https://domain.name/.well-known/assetlinks.json複製程式碼

Deep Links 是一種允許使用者進入應用某個特定Activity的intent filter。點選這類連結時,系統可能會彈出一個選擇列表,讓使用者在一堆能夠處理這類連結的應用裡(包括你的)選擇一個來處理該連結。圖一展示了這樣一種情況:使用者點選了一個地圖相關的連結,系統彈出一個選擇列表,讓使用者選擇是要使用地圖應用來處理,還是使用Chrome瀏覽器來處理。

App Links 是一種基於你的網站地址且驗證通過的Deep Links。因此,點選一個這樣的連結會直接開啟你的應用(如果已經安裝),系統將不會彈出選擇列表。當然,後續使用者可以更改配好設定,來指定由哪個應用程式處理這類連結。

下面這個列表描述更多差異:

Deep Links App Links
Intent URL Scheme https, http,或者自定義 需為http或https
Intent Action 任意Action 需為android.intent.action.VIEW
Intent Category 任意Category 需為android.intent.category.BROWSABLEandroid.intent.category.DEFAULT
連結驗證 不需要 需要在網站上放置一個數字資產連結,並能夠通過HTTPS訪問
使用者體驗 可能會彈出一個選擇列表給使用者選擇用哪個應用處理連線 沒有彈框,系統直接開啟你的應用處理網站連線
相容性 所有Android版本 Android 6.0及以上

為了讓系統為你的應用進行連結驗證,需要在AndroidManifest中,在任意一個網站連結intent filter裡(包含了android.intent.action.VIEW的Intent Action和android.intent.category.BROWSABLE的Intent Category),設定android:autoVerify="true"。如下面的片段所示:

<activity ...>

    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="http" android:host="www.example.com" />
        <data android:scheme="https" />
    </intent-filter>

</activity>複製程式碼

android:autoVerify="true"出現在你任意一個intent filter裡,在Android 6.0及以上的系統上安裝應用的時候,會觸發系統對APP裡和URL有關的每一個域名的驗證。驗證過程設計以下步驟:

  1. 系統會檢查所有包含以下特徵的intent filter:Action為 android.intent.action.VIEW、Category為android.intent.category.BROWSABLEandroid.intent.category.DEFAULT、Data scheme為httphttps
  2. 對於在上述intent filter裡找到的每一個唯一的域名,Android系統會到對應的域名下查詢數字資產檔案,地址是:https://域名/.well-known/assetlinks.json

只有當系統為AndroidManifest裡找到的每一個域名找到對應的數字資產檔案,系統才會把你的應用設定為特定連結的預設處理器。

在intent filter中的data元素中定義的每一個域名,系統都需要能夠驗證它的數字資產宣告檔案,即每一個域名都需要放置一個對應的數字資產宣告檔案。如果驗證過程中有任何一個域名驗證失敗了,那麼系統不會把應用設為應用內宣告的任何一種連結模式的預設處理器。系統會用原來處理Deep Links的方式處理這些連結。

舉例來說,一個定義了下面的intent filter的應用,在驗證過程中,由於assetlinks.json檔案在https://www.example.com/.well-known/assetlinks.jsonhttps://www.example.net/.well-known/assetlinks.json下都沒找到。

<application>

  <activity android:name=”MainActivity”>
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="http" android:host="www.example.com" />
      <data android:scheme="https" />
    </intent-filter>
  </activity>
  <activity android:name=”SecondActivity”>
    <intent-filter>
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" android:host="www.example.net" />
    </intent-filter>
  </activity>

</application>複製程式碼

注意,同一個intent filter內所有的<data>元素會合並在一起,以解釋他們所有組合屬性的變數。例如:上述第一個intent filter僅僅定義了一個HTTPS Scheme。但這個Scheme和其他的<data>元素結合在一起,因此這個intent filter既支援http://www.example.com,也支援https://www.example.com。同樣的,為了定義不同的URI的Scheme和域名的組合,你需要建立不同的intent filter。

數字資產連結協議把你intent filter裡的子域名看做唯一的、獨立的域名。因此,如果你的intent filter列出一個域名及其多個子域名,你需要在每個子域名上釋出一個有效的assetlinks.json檔案。舉例來說,下面的intent filter包含了www.example.commobile.example.com作為期望處理的intent的URL的主機名。因此,有效的assetlinks.json必須同時釋出在https://www.example.com/.well-known/assetlinks.jsonhttps://mobile.example.com/.well-known/assetlinks.json

<application>
  <activity android:name=”MainActivity”>
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" android:host="www.example.com" />
      <data android:scheme="https" android:host="mobile.example.com" />
    </intent-filter>
  </activity>
</application>複製程式碼

或者,如果你用萬用字元定義了你要處理的主機名(如*.example.com),那麼你需要把你的assetlinks.json檔案釋出在根主機名(根域名)下(example.com)。比如,一個以定義瞭如下intent filter的應用,如果assetlinks.json釋出在https://example.com/.well- known/assetlinks.json,它的任一子域名(如foo.example.com)將會通過驗證。

<application>
  <activity android:name=”MainActivity”>
    <intent-filter android:autoVerify="true">
      <action android:name="android.intent.action.VIEW" />
      <category android:name="android.intent.category.DEFAULT" />
      <category android:name="android.intent.category.BROWSABLE" />
      <data android:scheme="https" android:host="*.example.com" />
    </intent-filter>
  </activity>
</application>複製程式碼

宣告網站和應用之間的關係

數字資產證明檔案必須釋出在你的網站上,以證明你的應用是和網站關聯在一起的,並且用於驗證應用內的URL連結模式。這個JSON檔案用下面幾個欄位來標識關聯的應用:

  • package_name:在build.gradle裡定義的application ID
  • sha256_cert_fingerprints:應用簽名的SHA256指紋資訊。你可以用下面的命令,通過Java keytool來生成指紋資訊:
    $ keytool -list -v -keystore my-release-key.keystore
    這個欄位支援多個指紋資訊,可以用來支援不同的應用版本,如開發版本和釋出版本。

下面這個示例assetlinks.json授予連結開啟許可權給com.example應用:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]複製程式碼

讓網站和不同應用關聯起來

網站可以在同一個assetlinks.json檔案裡宣告和不同的app的關係。下面這個檔案列出了兩個宣告,這兩個宣告宣告瞭網站和兩個應用之間的關聯,這個檔案位於https://www.example.com/.well-known/assetlinks.json

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.puppies.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
  },
  {
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.monkeys.app",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]複製程式碼

不同應用或許會處理同一個域名下的不同資源連結。比如:app1宣告一個intent filter用於處理https://example.com/articles,app2宣告一個intent filter用於處理https://example.com/videos

注意:關聯同一個域名的不同應用可能會使用相同或不同的簽名檔案。

關聯一個應用到不同網站

不同的網站可以在它們的assetlinks.json檔案裡宣告關聯同一個應用。下述檔案展示瞭如何關聯example.com和example.net到app1。第一個檔案展示了關聯app1到example.com:

www.example.com/.well-known…

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.mycompany.app1",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]複製程式碼

下一個展示了關聯app1到example.net。兩者的差異只有放置的地方不同。

www.example.net/.well-known…

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.mycompany.app1",
    "sha256_cert_fingerprints":
    ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"]
  }
}]複製程式碼

釋出JSON驗證檔案

你必須把你的JSON驗證檔案釋出在下面的位置:

https://你的域名/.well-known/assetlinks.json複製程式碼

需要確保以下幾點:

  • assetlinks.json檔案的content-type必須為application/json
  • 不管你的應用的intent filter是否定義https作為data的scheme,assetlinks.json必須能通過HTTPS連結訪問
  • assetlinks.json必須能不經過任何重定向被訪問到(即沒有301、302跳轉),同時可以被爬蟲訪問到(你的robot.txt必須允許抓取/.well-known/assetlinks.json)
  • 如果你的應用支援多種域名,你需要把assetlinks.json釋出在這幾個域名的伺服器上。
  • 不要在你的AndroidManifest檔案裡釋出無法公開訪問的開發/測試URL(比如那些只能通過VPN進行訪問的URL)。一種可行的做法是配置構建型別,來為開發構建生成不同的清單。

當實現APP Links特性時,你需要測試連結功能是否可用,以保證系統能夠將你的APP和網站關聯起來,並如預期地處理URL請求。

要測試一個已存在的宣告檔案,你可以使用宣告列表生成器和測試器工具

確認需要進行驗證的域名列表

進行測試時,你需要確認你的應用裡關聯的需要系統進行驗證的域名列表。確保列表裡的域名對應的intent filter包含以下屬性和元素:

  • android:scheme屬性值為httphttps
  • android:host屬性為一個域名URL模式
  • 包含android.intent.action.VIEWCategory
  • 包含android.intent.category.BROWSABLECategory

同樣的,確保列表裡的每一個域名及相應的子域名下有數字資產證明連結的JSON檔案存在。

確認數字資產證明檔案

對每個域名,使用數字資產連結API來確認數字資產證明檔案被正確地定義和釋出在網際網路上:

https://digitalassetlinks.googleapis.com/v1/statements:list?
   source.web.site=https://你的域名:可選的埠&
   relation=delegate_permission/common.handle_all_urls複製程式碼

測試URL Intent

當你確認了你宣告的網站列表和你的應用之間的關聯,同時也確認了釋出的JSON檔案是有效的,將應用安裝到裝置上。等待約20s,以便系統發起的非同步驗證任務執行完畢。用下面這個命令來檢查系統是否驗證了你的應用並設定了正確的連結處理策略:

adb shell am start -a android.intent.action.VIEW 
    -c android.intent.category.BROWSABLE 
    -d "http://你的域名:可選的埠"複製程式碼

檢查連結策略

作為你的測試的一部分,你可以當前系統的連結處理設定。用下面的命令來獲取裝置上所有應用的連結處理策略:

adb shell dumpsys package domain-preferred-apps複製程式碼

下面這個命令也是同樣的作用:

adb shell dumpsys package d複製程式碼

注意:為了讓系統能完成驗證過程,確保你在安裝應用之後等待了至少20秒

命令返回了裝置上每一個使用者配置的列表,以如下格式的頭部資訊開頭:

App linkages for user 0:複製程式碼

在頭部資訊後面的輸出以如下的格式輸出連結處理配置資訊:

Package: com.android.vending
Domains: play.google.com market.android.com
Status: always : 200000002複製程式碼

這個列表表示了哪個應用和哪個網站之間的關聯:

  • Package:標識應用的包名
  • Domains:列出應用所處理的所有網站域名列表,以空格作為分隔符
  • Status:表示應用當前的連結處理策略。通過了驗證的應用,且它的Manifest檔案裡包含android:autoVerify="true",它的狀態為always。跟在狀態之後的十六進位制數字和系統的應用連結配置記錄有關。這個數值和驗證是否成功無關。

注意:If a user changes the app link settings for an app before verification is complete, you may see a false positive for a successful verification, even though verification has failed.This verification failure, however, does not matter if the user explicitly enabled the app to open supported links without asking. This is because user preferences take precedence over programmatic verification (or lack of it). As a result, the link goes directly to your app, without showing a dialog, just as if verification had succeeded.

測試示例

為了App Links能驗證成功,系統必須能夠驗證你定義在intent filter裡的所有網站,當然這些定義需要符合App Links的標準。下面這是示例演示了一個定義了幾個App Links的Manifest配置。

<application>

    <activity android:name=”MainActivity”>
        <intent-filter android:autoVerify="true">
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="www.example.com" />
            <data android:scheme="https" android:host="mobile.example.com" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="www.example2.com" />
        </intent-filter>
    </activity>

    <activity android:name=”SecondActivity”>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="https" android:host="account.example.com" />
        </intent-filter>
    </activity>

      <activity android:name=”ThirdActivity”>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:scheme="https" android:host="map.example.com" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="market" android:host="example.com" />
        </intent-filter>
      </activity>

</application>複製程式碼

根據上述配置,系統會嘗試進行驗證的域名有如下幾個:

www.example.com
mobile.example.com
www.example2.com
account.example.com複製程式碼

系統不會嘗試進行驗證的域名有:

map.example.com (沒有設定 android.intent.category.BROWSABLE)
market://example.com (不是“http”或“https” scheme)複製程式碼

官網文件正文完

以上便是官網上有關AppLinks的接入指導。但在筆者接入過程中,仍然碰到了一些問題。下文將記錄筆者遇到的問題和解決的方法。


AppLink 接入實踐總結

驗證

AppLinks的接入驗證,依照上文,可分為兩步進行:

  1. 驗證網站上是否將assetlink.json
  2. 第一步驗證通過後,將應用安裝到裝置上,進行驗證

第一步的驗證可使用上文提及的宣告列表生成器和測試器工具。筆者在驗證過程中發現,此工具確實能有效幫助我們生成,但對於驗證環節,錯誤資訊的提示並不怎麼友好。

為了獲得具體的出錯資訊,需要我們右鍵開啟Chrome瀏覽的檢查功能,選中Network標籤頁,然後再觸發一次驗證請求。此時,可以在Network標籤頁上看到剛剛發出去的驗證請求。檢視這個請求的返回結果,在errorMessage裡可以看到完整的錯誤資訊。

進行第二步驗證,依照上文,需要在應用安裝後,等待至少20秒的時間,以便系統驗證完畢。但是,上文只告訴我們如何獲得應用是否是特定域名的預設處理器的配置資訊,系統的這個驗證過程是否發起,是否執行完畢,驗證結果如何,我們是無從得知的。

筆者在接入過程中,觀察系統(Android 7.1.2)日誌輸出,發現SingleHostAsyncVerifier這個tag會輸出驗證相關的log,如下:

09-05 14:10:08.122 1386-26740/? I/SingleHostAsyncVerifier: Verification result: checking for a statement with source a <
                                                             a: "https://kg.qq.com"
                                                           >
                                                           , relation delegate_permission/common.handle_all_urls, and target b <
                                                             a: "com.tencent.karaoke"
                                                             b <
                                                               a: "02:38:85:6C:4B:EF:4A:5D:E1:81:E1:CB:07:B5:EF:02:B2:EC:4E:69:AC:C3:7F:CD:FC:08:8D:40:C7:6B:9C:68"
                                                             >
                                                           >
                                                            --> true.複製程式碼

上述日誌輸出表示成功驗證了這個AppLinks。可以參考本方法,觀察系統log來確定是否驗證通過。另一種方法是以應用包名作為過濾器來過濾日誌,也能發現一些蛛絲馬跡。

AppLinks是Android M之後系統支援的特性,因此也只有使用Android M及以上的系統的使用者能體驗到這個特性。

但要使這個特性生效,是否需要我們應用的目標SDK版本(targetSdkVersion)也提到Android M+呢?答案是否定的。因此,即時應用還未適配Android M以上的動態許可權申請,也可以接入這個特性。

另外,AppLinks本質上是一種特殊的DeepLinks,也就是說,在不支援的系統上,它會跟DeepLinks一樣,作為一個可處理某一類請求的處理器,顯示在系統的彈出列表裡。

要驗證AppLinks,可以使用上文提到的adb命令:

adb shell am start -a android.intent.action.VIEW 
    -c android.intent.category.BROWSABLE 
    -d "http://你的域名:可選的埠"複製程式碼

來傳送一個構造好的intent給系統。這種方式適合開發者進行自測。

如果要給他人體驗這個效果,那麼可以嘗試,在記事本里輸入可處理的連結,然後在預覽模式下,點選該連結,觸發AppLinks生效的場景。

另外,筆者嘗試在Android手機上的Chrome瀏覽器、獵豹瀏覽器、UC瀏覽器裡直接輸入網址來驗證,均無法觸發系統的AppLinks特性。推測瀏覽器內發起的跳轉,會被瀏覽器直接攔截處理了,因此無法進入系統的分發處理Intent流程,故沒觸發AppLinks特性。

系統的預設處理配置訊息

另外,筆者在除錯過程中發現,即便AppLinks特性已經生效了,但通過:

adb shell dumpsys package domain-preferred-apps複製程式碼

adb shell dumpsys package d複製程式碼

這兩個命令得到的列表裡,筆者的應用顯示的仍然不是always的狀態。這在筆者除錯的過程中,給筆者造成了很大的困擾,導致筆者以為AppLinks的驗證過程未通過,導致這個情況。

實際驗證過程推薦使用檢視系統日誌、手動構造傳送intent的方式進行驗證。

快速整合方法

Android Studio 2.3開始,在Tools裡整合了App Links Assistant,這個圖形化工具能幫助我們完成上述提及的所有步驟。瞭解了上述細節後,可以使用這個工具,快速整合。

小結

總的來說,App Links賦予了應用直接處理其相關網站連線的能力,使得應用能更方便地觸達使用者,提供優於Web的體驗。同時,通過assetlink這種方式,來確保網站所有者及其對應應用的相關性,避免外部應用惡意攔截處理網站連線的情況。

但在實際使用中發現,這種方式只能用於系統處理URL的intent的情況,若URL本身沒有拋給系統處理,而是直接在應用內開啟了,那就無法直接拉起應用。

這也是App Links和iOS的Universal Link不一樣的地方。App Links是在URL被處理前,轉發給應用處理,而Universal Link則是先開啟對應的網頁,然後再直接跳轉到應用。

由於大部分應用,如微博、微信、第三方瀏覽器(包括Chrome),都不會將URL拋給系統處理,因此App Links生效的情況就很有限了,比如只能從記事本應用、簡訊應用這些進行跳轉。總體來說,實屬雞肋。

相關文章