DeepLink的使用及任务栈问题

本文详细介绍了Android中使用DeepLink技术提高用户日活量的实践,包括DeepLink的分类、使用场景、实现原理以及延迟深度链接的概念。通过配置scheme、host和intent-filter,实现从网页直接启动App并跳转到指定页面。同时,文章还探讨了任务栈和回退栈在DeepLink场景下的作用,提出了解决浏览器启动App时创建新Activity问题的解决方案,即使用BrowserIntentActivity进行过渡处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言
项目求人前段时间有个需求,希望提高应用的用户日活量,在用户使用Google或者Chrome浏览器检索“求人”相关关键字后,点击检索的网页,如果网页链接的前缀是项目的官网(https://www.xxxx.xx.com/…),那么需要启动App, 并在合适的地方解析出获取到的链接,跳转到指定的检索内容页面。
项目采用了谷歌官方提供的API-- DeepLink,也就是深度链接(https://developer.android.com/training/app-links/deep-linking)来实现。
DeepLink介绍
Deeplink又叫深度链接,是一项减少运营难度的技术,它在手机上的应用场景十分广泛。比如
朋友在微信上分享一条资讯, 我点击打开就能跳转到资讯来源的app,并在app指定页面进行打开。浏览网页点击微博相关的网页、点击知乎相关的网页等等,都会弹出以下类型的提示框,提示用户打开相关app进行访问。(当然你需要已经安装了与之相关的app应用)
提示弹框的样式在不同浏览器(或应用)中有所不同,UI效果由其本身决定。
请添加图片描述 在这里插入图片描述
DeepLink的分类

  1. Deeplink: 深度链接,指已安装相应App的情况下,把特定的参数通过url 的形式传递给
    App,从而直接打开指定的内部页面,实现从链接直达App内部页面的跳转。
  2. Deferred Deeplink:延迟深度链接,主要增加了一个是否已安装相应App的判断,用户点
    击链接时,如果未安装App,则引导用户前往应用市场,下载完对应App后,首次打开该
    App时自动跳转进入指定的内部页面。

DeepLink使用与否的区别
在这里插入图片描述
在整个流程中,Deeplink 起到的作用是显而易见的,一方面,随着操作步骤的减少,用户体
验也就随之提高;另一方面,用户点击H5链接后-键拉起App,不会再因为找不到相应页面而产生流失,App的分享页面、推广活动、投放广告的价值和转化率都将大大提高。

DeepLink实现原理
deepLink本质上是通过web网页调用Andriod原生App,然后将参数通过URL的形式传递给App实现拉起,App所注册的活动即为拉起的页面,通过获取的URL按照一定逻辑解析成跳转到指定页面所需的数据。
在这里插入图片描述

  • scheme
    scheme是用于标识具体跳转app,可以避免歧义对话框的出现(具体样式可参考上方Google浏览器提示框),唯一性的scheme大多用于 app A 启动 app B(比如微信分享的资讯打开相关的app)。而如果是点击检索网页实现跳转app,scheme自然是 http或者https。(允许网页和app皆可解析访问)
    在这里插入图片描述
  • host
    为了进一步细化能够启动Activity的链接,需host指明URL的具体类型。当然,如果希望Activity能够接收更多链接,可以注册多个标签,对应不同的链接。
    如果多个链接的URL相似,前缀一致,只是路径有所区别,可按照实际需求分情况处理:
    eg: https://www.fenrir-inc.com.cn/aaaa/bbbb/?ccc=sfsifhihloh
    https://www.fenrir-inc.com.cn/ddd/
    https://www.fenrir-inc.comcn/eee/J12526/fff/
    1. 统一在一个Activity中处理,只需要注册scheme,以及host,根据网页跳转的app后获取的URL进行分类讨论。
<activity
        android:name="com.example.android.SplashActivity" >
        <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.fenrir-inc.com.cn" />
        </intent-filter>
</activity>
  1. 在多个Activity中处理,通过使用 android:path 属性或其 pathPattern 或 pathPrefix 变体区分系统应针对不同 URI 路径打开哪个 Activity。
    path、pathPattern、pathPrefix的区别:
  • path 用来匹配完整路径的,只有特定链接才能启动Activity.
  • pathPrefix 用来匹配路径的开头部分,只需指明host之后的部分前缀路径。
  • pathPattern 用来表达式匹配整个路径,就是下面这些匹配符号与转义。
    匹配符号:
    1. “*” 用来匹配0次或者更多次,比如:"a *“就可以包含"a”,“aa”,"aaa"等
    2. “.” 用来匹配任意字符,比如:".“就可以包含"a”,“b”,"c"等
    3. 两种符号组合,". *"就能够批撇任意字符任意次数,比如 ". *html"就包含了“abcchtml”,"pdf.html"等
    4. 当然也要注意转义符号
      在XML文件中,“\字符”是转义的,比如“\n”换行,所以如果想规定处理的链接是以 【“fenrir-inc.com.cn*happy”】结尾,比如这种链接【https://www.fenrir-inc.com.cn/…fenrir.com.cn *happy】(当然没有链接这么怪的)
      那么应该写成这样:
 <intent-filter>
    <action android:name="android.intent.action.VIEW"></action>
    <category android:name="android.intent.category.DEFAULT"></category>
    <data android:scheme="https" android:host="www.fenrir-inc.com.cn" 
    android:pathPattern=".*fenrir-inc\\.com\\.cn\\*happy"></data>
</intent-filter>

对于使用哪种方式处理,应根据项目实际需求决定。求人这边我用的就是第一种,省事些,需要跳转的链接scheme、host都一致的,拿到链接后对其分类讨论就可以了。

在Activity获取链接

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_browser);
        
        handleIntent(getIntent);
    }

@Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        
        handleIntent(intent);
    }

private void handleIntent(Intent intent){
        if(intent.getData != null && intent.getAction.equals(Intent.ACTION_VIEW)){
            String strUri = intent.getData.toString;
        }
    }

服务器端配置

服务器端必须在指定路径配置好 assetlink.json,这个json文件采用Digital Asset Links协议让网站和app建立关联绑定。

  1. 自动生成assetlink.json的方法:https://developers.google.com/digital-asset-links/tools/generator
    在这里插入图片描述
    生成的文件大致如下:
[{
      "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"]
      }
    }]

参数说明:

  • relation 关系声明,用于描述目标程序与网站关系
delegate_permission/common.handle_all_urls 向目标授权处理所有url
delegate_permission/common.get_login_creds 向目标授权登录凭证(一般用于google smart lock)
  • target 用于描述需要建立关系的目标
  • namespace 应用标志,可为【web】或者【android_app】
  • package_name 就是Android App包名
  • sha256_cert_fingerprints: Android App key store 证书SHA265指纹

文件放置位置(引用施老师调查结果)

  • 将assetlinks.json文件放在网站根目录的/.well-known/ 中
  • 保证https://hostname/.well-known/assetlinks.json和http://hostname/.well-known/assetlinks.json能正常访问,并且不需要任何代理,以及没有重定向
  • robot.txt中允许爬虫抓取/.well-known/assetlinks.json文件

以上便是关于DeepLink基本配置的内容,接下来就是实际应用。
在实际应用前,我想先谈谈任务栈切换、回退栈的问题,当然这些都是基础性的知识,不过我一开始做的时候确实是没有具体去搞明白,所以走了些许弯路。

任务栈、回退栈

  1. 手机开机启动后, Home(Launcher)所在的Activity在回退栈的栈底。
    在这里插入图片描述
  2. 从Launcher上的图标点击进入一个应用A(Activity)时,默认在启动整个Activity的Intent的flag里面加入了FLAG_ACTIVITY_NEW_TASK标记。
    (这个标记的作用是:首先会查找是否存在和被启动的Activity具有相同的亲和性的任务栈,如果有,刚直接把这个栈整体移动到前台,并保持栈中的状态不变,即栈中的activity顺序不变,如果没有,则新建一个栈来存放被启动的activity)。
    也就是说从launcher启动的Activity默认会在一个新的Task里面。
    比如我们启动了一个应用,ABC三个Activity是同一个应用(ABC没有设置亲和性,默认都是跟随启动它的那个activity的亲和性的),都归为Task2。
    在这里插入图片描述
  3. 如果在C中启动浏览器那么就会另起一个Task。
    请添加图片描述
  4. 此时按下Home键,那么Task1就会到回退栈的栈顶.
    在这里插入图片描述
  5. 点击应用A图标(Task2), 由于回退栈中已经存在这个应用的任务栈,所以会复用这个任务栈,并保持栈内的活动顺序不变,也就是点击应用A看到的就是C活动的UI界面。
    在这里插入图片描述
  6. 此时,如果应用A(Task2)的A活动在IntentFilter注册了 www.xxxx.xx链接启动,这时,切换浏览器,点击检索此链接的网页,选择App打开方式,那么就能够看到启动了应用A(Task2)并显示的是A活动,
    【假设应用A中所有的活动启动模式是Standard】
    任务栈变化过程为:
  7. 为何要提及任务栈呢?
    因为当时做使用DeepLink跳转到SplashActivity时,总是会有以下现象。
    (1)有一个应用只有一个活动MainActivity【standard模式】, 如果先点击应用图标Launcher MainActivity后, 使用浏览器打开这个活动,那么就会新建一个新的活动,每用浏览器打开一次就会启动一个新的MainActivity,而此时点击图标pp却不会启动新的Activity,而是启动栈顶的活动。
    在这里插入图片描述

(2)可如果,一开始应用并没有启动过,直接使用浏览器打开,启动MainActivity后,我们重复这个操作【使用浏览器打开MainActivity】,浏览器就不会启动新的MainActivity,而是从第二次操作开始,改为直接启动这个应用的任务栈顶部Activity(当然还是MainActivity)。
有意思的是,如果你此时选择点击app图标启动app,那么每点击一次就会创建一个新的MainActivity.
在这里插入图片描述请添加图片描述
这个就是我不懂的地方
???????????

网上有很多方法,比如将要跳转的活动设置为singleTask等等,这类方法大多不符合实际需求。
为了避免【app通过Launcher启动在后台,使用浏览器跳转到app却仍然会启动新的SplashActivity】的出现,我做了一个较为优解的方法:

1. 新建一个BrowserIntentActivity,专门用于处理浏览器启动app情况

2.每次浏览器启动app时,都会启动这个活动,这个活动是透明的,如果app在后台,那么直接finish,即视觉上“打开了任务栈栈顶的活动”,如果app未启动,则finish,并跳转到SplashActivity,

3.在这个BrowserIntentActivity接收到 链接Uri, 并将其作为MainApplication的一个变量存放.

public class BrowserIntentActivity extends BaseActivity {

    @Inject
    BrowserIntentPresenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_browser);
        val mainApp = MainApplication.getInstance(this);
        presenter.setMainAppUri(getIntent(), mainApp);
        
        finish();
        createIntent();
    }

    // Start SplashActivity or the Activity at the top of the task stack
    private void createIntent() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setClass(this, SplashActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
        startActivity(intent);
    }

4. 在需要跳转的活动中,拿到Uri变量,将其分类,解析成目标跳转界面所需的参数,并进行跳转。

HomeFragment.java

 @Override
    public void onResume() {
        super.onResume();
        handleMainAppIntent();
    }

 // Get Uri from MainApplication
    private void handleMainAppIntent() {
        val mainApp = MainApplication.getInstance(getContext());

        String strUri = mainApp.getStrUri();
        if (!strUri.equals("")) {

            if (strUri.contains("/search/")) {
                startFragment(, ,);
            } else if (strUri.contains("/place/")) {
                startFragment(, ,);
            } else if (strUri.contains("/genre/")) {
               startFragment(, ,);
            } else if (strUri.contains("/style/")) {
                startFragment(, ,);
            } else if (strUri.contains("/rail/")) {
                startFragment(, ,);
            } else if (strUri.contains("/form")) {
                startFragment(, ,);
            } else if (strUri.contains("/job/J") && !strUri.contains("/tel/")) {
                startFragment(, ,);
            } else if (strUri.contains("/job/J") && strUri.contains("/tel/")) {
                sstartFragment(, ,);
            } else {
                // Do nothing
            }
        }   
        
        mainApp.setStrUri("");
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值