深度鏈接(Deeplinking)

[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

實際上,只要按照約定好即可。

使用

按照官方文檔:

  1. 在需要的頁面注冊位置進(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的信息直接保存到啟動ActivityIntent中,可以方便的通過下面的方法直接獲取

@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)行簡單的改動;

  • 所需要啟動的目的頁面不需要配置,只需要在AppLaunchActivity(啟動頁面)進(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&param2=param2
testproject://www.egan.testproject/alert?data={'param1':'param1', 'param2':'param2'}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容