前言
之前我們又是看源碼又是研究動畫,今天分享一個比較簡單的技術點:DeepLink。
DeepLink,深度鏈接技術,主要應用場景是通過Web頁面直接調用Android原生app,並且把需要的參數通過Uri的形式,直接傳遞給app,節省用戶的註冊成本。簡單的介紹DeepLink概念之後,我們看一個實際的例子:
朋友通過京東分享給我一個購物鏈接:
於是我通過微信打開了這條鏈接:
在微信中打開這個網址鏈接,提示我打開京東app,如果我點擊了允許,就會打開我手機中的京東app,並且跳轉到這個商品的詳情頁:
通過這種形式,我可以快速的找到需要查看的商品,並且完成購買相關的操作。是不是非常方便,這就是DeepLink。
正文
這麼流弊的DeepLink是不是非常的難?其實DeepLink的基本實現是簡單到不可思議,他的核心思想實際上是Android的隱式啓動。我們平時的隱式啓動主要是通過Action和Category配合啓動指定類型的Activity:
<activity
android:name=".SecondActivity"
android:exported="true">
<intent-filter>
<action android:name="com.lzp.deeplinkdemo.SECOND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
val intent = Intent("com.lzp.deeplinkdemo.SECOND")
intent.addCategory(Intent.CATEGORY_DEFAULT)
startActivity(intent)
除了action和category,還有一種隱式啓動的用法是配置data:
<data
android:scheme="xxxx"
android:host="xxxx"
android:port="xxxx"
android:path="xxxx"
android:pathPattern="xxxx"
android:pathPrefix="xxxx"
android:mimeType="xxxx"/>
scheme:協議類型,我們可以自定義,一般是項目或公司縮寫,String
host:域名地址,String
port:端口,int。
path:訪問的路徑,String
pathPrefix:訪問的路徑的前綴,String
pathPattern:訪問路徑的匹配格式,相對於path和pathPrefix更爲靈活,String
mimeType:資源類型,例如常見的:video/*, image/png, text/plain。
通過這幾個配置項,我們發現data實際上爲當前的頁面綁定了一個Uri地址,這樣就可以通過Uri直接打開這個Activity。
複習一下Uri的結構:
<scheme> :// <host> : <port> / [ <path> | <pathPrefix> | <pathPattern> ]
示例:https://zhidao.baidu.com/question/2012197558423339788.html
scheme和host不可缺省,否則配置無效;path,pathPrefix,pathPattern一般指定一個就可以了,pathPattern與host不可同時使用;mimeType可以不設置,如果設置了,跳轉的時候必須加上mimeType,否則不能匹配到Activity。
現在我們修改SecondActivity的intent-filer:
<activity
android:name=".SecondActivity"
android:exported="true">
<intent-filter>
<action android:name="com.lzp.deeplinkdemo.SECOND" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<intent-filter>
<data
android:scheme="lzp"
android:host="demo"
android:port="8888"
android:path="/second"
android:pathPattern="/second"
android:pathPrefix="/second"
android:mimeType="text/plain"/>
</intent-filter>
</activity>
打開SecondActivity:
val intent = Intent()
intent.setDataAndType(Uri.parse("lzp://demo:8888/second"), "text/plain")
startActivity(intent)
現在在App中已經可以打開頁面了,那麼用web能不能正常打開呢?首先配置MainActivity的intent-filter:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<data
android:scheme="lzp"
android:host="demo"
android:path="/main"/>
</intent-filter>
</activity>
Web需要打開url鏈接,所以我們不需要配置mimeType,
手寫一個簡單的Html頁面:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title></title>
<meta name="Generator" content="Cocoa HTML Writer">
<meta name="CocoaVersion" content="1561.4">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; line-height: 17.0px; font: 12.0px 'Songti SC'; color: #000000; -webkit-text-stroke: #000000; min-height: 17.0px}
span.s1 {font-kerning: none}
</style>
</head>
<body>
<a href="lzp://demo/main">打開main</a>
</html>
Html頁面添加了一個鏈接,點擊打開lzp://demo/main這個地址。把html導入到手機中,用瀏覽器打開,點擊“打開app”,毫無反應!!!
沒錯,如果只是配置了data,Web還是沒辦法通過url地址打開我們的Activity,那怎麼解決這個問題呢?
/**
* Activities that can be safely invoked from a browser must support this
* category. For example, if the user is viewing a web page or an e-mail
* and clicks on a link in the text, the Intent generated execute that
* link will require the BROWSABLE category, so that only activities
* supporting this category will be considered as possible actions. By
* supporting this category, you are promising that there is nothing
* damaging (without user intervention) that can happen by invoking any
* matching Intent.
*/
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
我們還需要配置:
<category android:name="android.intent.category.BROWSABLE" />
從官方的註釋上寫明:需要瀏覽器打開Activity,需要設置這個分類。例如郵件,只有設置了這個分類的Activity纔會考慮被打開。加上這個配置後,再次點擊看看有沒有效果。
如果你真的親自嘗試了,你會發現還是沒有效果。這個時候我們需要回顧一下action和category的用法:
首先需要嘗試匹配action,action匹配成功了之後,纔會繼續匹配設置的category,所以單獨匹配category是沒有任何效果的。
因爲我們要打開的僅僅是一個頁面,所以我們設置:
/**
* Activity Action: Display the data to the user. This is the most common
* action performed on data -- it is the generic action you can use on
* a piece of data to get the most reasonable thing to occur. For example,
* when used on a contacts entry it will view the entry; when used on a
* mailto: URI it will bring up a compose window filled with the information
* supplied by the URI; when used with a tel: URI it will invoke the
* dialer.
* <p>Input: {@link #getData} is URI from which to retrieve data.
* <p>Output: nothing.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_VIEW = "android.intent.action.VIEW";
官方的註釋說明ACTION_VIEW表示展示數據的頁面,系統默認的Action就是ACTION_VIEW。添加上ACTION_VIEW,再次點擊打開app。
還是不行,但是跟之前不同的是,這次出現了啓動app的提示窗口,但是app卻閃退了,看一下崩潰日誌:
09-06 14:35:15.459 1216-3270/? W/IntentResolver: resolveIntent failed: found match, but none with CATEGORY_DEFAULT
09-06 14:35:15.473 26708-26708/? E/AKAD: thread:Thread[main,5,main]
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW cat=[android.intent.category.BROWSABLE] dat=lzp://demo/main?id=111 flg=0x10000000 (has extras) }
at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:1805)
at android.app.Activity.startActivityIfNeeded(Activity.java:4420)
at android.app.Activity.startActivityIfNeeded(Activity.java:4367)
at org.chromium.chrome.browser.externalnav.ExternalNavigationDelegateImpl$3.onClick(ExternalNavigationDelegateImpl.java:239)
at com.qihoo.browser.dialog.CustomDialog$3.onClick(CustomDialog.java:274)
at android.view.View.performClick(View.java:5267)
at android.view.View$PerformClick.run(View.java:21249)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635)
日誌上寫的很明確,雖然找到了匹配的頁面,但是沒有設置CATEGORY_DEFAULT。看來Web通過url來打開鏈接,必須要求設置CATEGORY_DEFAULT,添加上後,看一下我們完整的xml配置:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<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="lzp"
android:host="demo"
android:path="/main"/>
</intent-filter>
</activity>
最後看一下效果:
那麼如何在通過url給app傳遞參數呢?要實現這個也很簡單,首先我們知道要想給url添加參數,直接在url後拼接key=value就可以了,例如:
其中wd=android就是我們要添加的參數,現在假設我們需要爲Activity傳遞一個參數id,我們就可以修改uri爲:
客戶端接收key爲id的參數的方法:
if (intent != null && intent.data != null) {
Log.e("lzp", intent.data.getQueryParameter("id"))
}
如果只是接收參數的話,客戶端不需要進行任何修改,但是這裏有一種情況,如果我們Activity必須傳遞id,如果不傳遞id不允許跳轉怎麼辦呢?我們有兩種辦法解決這個問題:
1、在剛纔的if語句增加else判斷,當參數爲空的時候,進行finish操作。
2、通過pathPattern,通過通配符設置必須有參數。
我們看一下第二種的實現方式:
<data
android:pathPattern="lzp://demo/main?id=*"
android:scheme="lzp" />
之前已經說過,pathPattern不能和host同時使用,所以我們只能刪除host,pathPattern匹配的是整個Uri,這樣我們還可以指定多個參數。但是AndroidManifest.xml會報錯,我們忽略就可以了
總結
其實DeepLink的實現原理就是這麼簡單,只是我們對於隱式啓動理解的不夠。是不是也想給自己的App加上DeepLink呢?趕快嘗試一下吧~