[TOC]
前言
隨著線上推廣力度加大,不可避免的需要通過網(wǎng)頁的形式向用戶推廣,進(jìn)行APP的引流以及下載,這就使用了深度鏈接技術(shù)
介紹
深度鏈接(Deeplinking)是通過網(wǎng)頁鏈接直接啟動原生應(yīng)用的方法。確切地說是通過映射預(yù)定義行為到唯一的鏈接上,讓用戶無縫跳轉(zhuǎn)到相關(guān)內(nèi)容頁面。
官方技術(shù)介紹
<<--->> 中文翻譯版本
官方推薦的使用深度鏈接的方法是通過定義URI(統(tǒng)一資源定位符),唯一標(biāo)識一個APP的具體行為;
例如:eganprojest://needstartactivity
其對應(yīng)的定義是:scheme://host
或者是網(wǎng)絡(luò)地址的寫法:
例如:https://www.eganproject.com/needstartactivity
起對應(yīng)的定義是:scheme://host//pathPrefix
實際上,只要按照約定好即可。
使用
按照官方文檔:
- 在需要的頁面注冊位置進(jìn)行配置:
<activity
android:name=".Task2Activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!-- 接受以"testproject://task2”開頭的 URIs -->
<data android:scheme="testproject"
android:host="task2" />
<!-- 接受以"https://www.eganproject.com/aaaa”開頭的 URIs -->
<data android:scheme="https"
android:host="www.eganproject.com"
android:pathPrefix="aaaa" />
</intent-filter>
</activity>
對上面的屬性進(jìn)行意義闡釋:
- 為頁面的添加
intent-filter -
<action android:name="android.intent.action.VIEW" />-- 指定ACTION_VIEW的操作,使得Google搜索可以觸及intent filter -
<category android:name="android.intent.category.BROWSABLE"/>--BROWSABLE category指定該頁面可以響應(yīng)瀏覽器,如果不添加,APP無法響應(yīng)深度鏈接 -
<category android:name="android.intent.category.DEFAULT"/>--DEFAULT category是可選的,但建議添加。沒有這個category,activity只能夠使用app組件名稱以顯示(explicit)intent啟動。 -
<data android:scheme="testproject"/>--data標(biāo)簽可以一到多個,不同屬性指定對于深度鏈接的不同解析-
android:scheme必須包含的標(biāo)簽,其一般指定所對應(yīng)的啟動的App -
android:host具體的動作/應(yīng)用的唯一標(biāo)識 -
android:pathPrefix頁面
-
數(shù)據(jù)的解析
如果我們一個頁面可能支持了多個 <data> 標(biāo)簽,那我們?nèi)绾螀^(qū)分是哪個URI啟動的呢?Android已將uri的信息直接保存到啟動Activity的Intent中,可以方便的通過下面的方法直接獲取
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// 獲得啟動頁面的 Intent.
Intent intent = getIntent();
// 獲得啟動頁面的 Uri 信息.
Uri data = intent.getData();
}
Google約束
遵守下面這些慣例來提高用戶體驗:
深度鏈接應(yīng)直接為用戶打開內(nèi)容,不需要任何提示,插播式廣告頁和登錄頁面。要確保用戶能看到app的內(nèi)容,即使之前從沒打開過這個應(yīng)用。當(dāng)用戶從啟動器打開app時,可以在操作結(jié)束后給出提示。這個準(zhǔn)則也同樣適用于網(wǎng)站的first click free體驗。
遵循Navigation with Back and Up中的設(shè)計指導(dǎo),來使你的app能夠滿足用戶通過深度鏈接進(jìn)入app后,向后導(dǎo)航的需求。
但是我們實際開發(fā)中可能并不能或者是并不容易達(dá)到這個要求,比如打開的App目的頁面需要App的啟動信息或者登錄信息,或者產(chǎn)品的需求等;
所以下面說一下實際開發(fā)中的使用;
具體使用
前面的準(zhǔn)備工作,也就是知識的儲備是必須的;
只是需要對一些配置信息、以及數(shù)據(jù)的解析進(jìn)行簡單的改動;
- 所需要啟動的目的頁面不需要配置,只需要在
App的LaunchActivity(啟動頁面)進(jìn)行配置
<activity
android:name=".LaunchActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!-- 接受以"testproject://xxx”開頭的 URIs -->
<data android:scheme="testproject"/>
</intent-filter>
</activity>
我們只需要配置action、DEFAULT category、BROWSABLE category以及data中的scheme屬性
這樣的話我們 App 會響應(yīng)所有以 testproject:// 開頭的URI,并且是在APP的啟動頁面,這樣,可以保證我們APP流程的完整性(PS:比如啟動流程)
在 Launch 頁面,我們可以通過 getData() 獲取到具體的URI信息,并且對數(shù)據(jù)進(jìn)行解析存儲(key-value),在特點的頁面,比如 APP的首頁,再次對所存儲的數(shù)據(jù)進(jìn)行解析、區(qū)分,進(jìn)行不同業(yè)務(wù)的處理這同時也保證了業(yè)務(wù)完成后返回能夠回到 APP 的首頁,而不是直接退出 APP
完全具體實現(xiàn)
頁面配置
<!-- 啟動頁面 -->
<activity android:name=".LaunchActivity">
<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:host="www.egan.testproject"
android:scheme="testproject" />
</intent-filter>
</activity>
<!-- 主頁面 -->
<activity android:name=".MainActivity" />
<!-- 目標(biāo)頁面 -->
<activity android:name=".Task2Activity" />
public class DeepLinks {
// 每一個短鏈接中有一個參數(shù)的key是 tag,用了唯一區(qū)別該短鏈
// PS:也可以通過 path 來區(qū)分,一樣的,這里用的第一種
private static final String TAG_KEY = "tag";
private final Map<String, String> dataMap = new HashMap<>();
private DeepLinks() {
}
public static DeepLinks getInstance() {
return SingletonHolder.deepLinks;
}
private static final class SingletonHolder {
static final DeepLinks deepLinks = new DeepLinks();
}
/**
* 深度鏈接的數(shù)據(jù)解析.
*
* @param intent 攜帶鏈接數(shù)據(jù)的 intent.
*/
void parseData(Intent intent) {
final Uri data = intent.getData();
if (null != data) {
// todo...
Log.d("YSK", "DeepLinks:parseData >>> " + data);
Set<String> parameterNames = data.getQueryParameterNames();
Observable.fromIterable(parameterNames).subscribe(new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
// 將數(shù)據(jù)進(jìn)行數(shù)據(jù)的存儲.
dataMap.put(s, data.getQueryParameter(s));
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Log.d("YSK", "DeepLinks:accept >>> " + throwable.getMessage());
}
});
}
/**
* 數(shù)據(jù)處理.
*/
void processData() {
// 可補(bǔ)充,比如增加App的數(shù)據(jù)有效性校驗.
// 獲取目標(biāo)活動數(shù)據(jù) key
if (dataMap.containsKey(TAG_KEY)) {
// 打印數(shù)據(jù)
logMap(dataMap);
String key = dataMap.get(TAG_KEY);
// 根據(jù)具體業(yè)務(wù)區(qū)分處理。
switch (key) {
case "Task2":
break;
case "Task3":
break;
default:
break;
}
// 存儲的深度鏈接信息處理后,清空,防止后續(xù)深度鏈接數(shù)據(jù)的準(zhǔn)確性.
dataMap.clear();
}
}
/**
* 打印 map 信息.
*/
private void logMap(final Map<String, String> map) {
Observable.fromIterable(map.keySet()).subscribe(new Consumer<String>() {
@Override
public void accept(String o) throws Exception {
Log.d("YSK", "DeepLinks:accept >>> " + o + " : " + map.get(o));
}
});
}
/**
* 啟動頁面
*/
public class LaunchActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_launch);
// 深度鏈接啟動的數(shù)據(jù)解析.
DeepLinks.getInstance().parseData(getIntent());
// 延時3s發(fā)送事件,模擬啟動頁面的廣告時間.
Observable.timer(3, TimeUnit.SECONDS).subscribe(new Consumer<Long>() {
@Override
public void accept(Long aLong) throws Exception {
startActivity(new Intent(LaunchActivity.this, MainActivity.class));
finish();
}
});
}
}
/**
* 主頁面.
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 深度鏈接啟動的信息處理.
DeepLinks.getInstance().processData();
}
}
adb命令測試方法
google官方提供了通過ADB命令直接測試深度鏈接效果的命令(不用再費力的專門寫一個html了)
adb shell am start
-W -a android.intent.action.VIEW
-d <URI-定義的URI> <PACKAGE-需要測試的應(yīng)用包名>
比如我需要測試我的Demo,可以直接通過命令
adb shell am start
-W -a android.intent.action.VIEW
-d "testproject://www.egan.testproject/aler?tag=Task2&p1=1" com.egan.testproject
補(bǔ)充
深度鏈接一般我們可能都需要傳遞數(shù)據(jù),比如我需要兩個參數(shù),這種情況下的測試,使用官方提供的adb命令無法完全獲得兩個參數(shù),比如我執(zhí)行的是 testproject://www.egan.testproject/aler?tag=Task2&p1=1 ,但是通過 getIntent().getData() 獲取到的 uri 是 testproject://www.egan.testproject/aler?tag=Task2 ,只能獲取到第一個參數(shù);
所以請編寫一個 html 自行測試;
- 業(yè)務(wù)區(qū)分的常用方式
testproject://www.egan.testproject/business_1
testproject://www.egan.testproject/business_2
testproject://www.egan.testproject/alert?tag=business_1
testproject://www.egan.testproject/alert?tag=business_2
- 攜帶參數(shù)的常用方式:
testproject://www.egan.testproject/alert?param1=param1¶m2=param2
testproject://www.egan.testproject/alert?data={'param1':'param1', 'param2':'param2'}