博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何优雅地从浏览器打开本地应用deeplink
阅读量:6554 次
发布时间:2019-06-24

本文共 4765 字,大约阅读时间需要 15 分钟。

hot3.png

相信Android开发者都会知道,在应用内跳转页面,我们肯定会用到 Intent。使用 Intent 跳转页面有显式跳转和隐式跳转两种方式。

Intent intent = new Intent();intent.setClass(this, MainActivity.class);startActivity(intent);

典型的显式跳转就如同上面的代码一样,如果需要携带数据的话,我们也有两种方式。一方面我们可以调用 Intent 的 putExtra 系列方法,通过键值对的方式把数据传递过去。Intent 支持传递的数据包括基本类型数据、字符串、序列化以及各自的数组,有意思的是 Intent 支持 Parcelable数组 但不支持 Serializable数组。然后在跳转的页面通过相应的 get方法 把数据拿出来即可。

另一方面,我们也可以用 Intent 的 setData方法 将统一资源标识符Uri传递过去,然后在接收端解析 Uri 来拿出数据。对于一个 Uri,主要由以下几部分组成:

scheme://host:port/path?query

相应地,我们便能从 Uri 中把各个部分的信息拿出来。

而隐式跳转则是通过 Intent 的 action 来实现。典型的便是我们在桌面点击了应用的图标,然后应用启动进入我们的第一个页面。相信大家都知道对于应用自动的第一个页面,我们在 AndroidManifest.xml 文件中对 Activity 注册时,会有这么一段代码。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

实际上应用从点击图标到启动第一个页面的过程相当复杂,如果大家有兴趣的话可以去看老罗的博客:

Android应用程序启动过程源代码分析

http://blog.csdn.net/luoshengyang/article/details/6689748

从源代码的分析来看,桌面图标保存了相应应用的启动 intent,该 intent 保存了 AndroidManifest.xml 的信息。因此我们自定义隐式启动页面时,只需要在 AndroidManifest.xml 中相应的 Activity 配置相应的 action 信息,然后调用的时候设置相应的 action 便可启动相应 Activity。

比如我们之前开发应用时,想要调用系统拨打电话应用,我们并不知道拨打应用 Activity 的具体信息,所以只有通过隐式方式来调用。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

嗯,有了这些基础知识,我们便可以来实现浏览器打开本地App了。

 

浏览器打开本地APP

常规下,浏览器打开一个 url 要么是 http 要么是 https,那么,我们要怎么让浏览器能识别到一个 url 是指向我们的应用的呢?那我们来看看 Url 吧,仔细看看一个 url 链接其实就是一个 Uri,有scheme,有host,有query等等。scheme 实际上相当于一种协议,比如 http,系统识别到这个协议便会交由网络部分去处理这个请求。

那么系统能否识别自定义的 scheme 呢?答案是肯定的。我们可以在自己的应用内自定义一种 scheme,系统安装我们的应用后便会在某个地方进行 scheme 注册,这样当有相应的 scheme 请求的时候,便能将请求导向到我们的应用。有意思的是如果在多个应用中配置了相同的 scheme 的话,从浏览器请求时便会提示用户选择某一个应用来打开。

那么,我们来看看基本做法怎么做吧。

  • 配置要打开的页面,比如我们在应用的启动页面配置相应的scheme

090531_xXND_1771562.gif

  • 配置浏览器访问的url

myapp://?id=1

为了简单,就没有配置 host 信息,读者可以自己将 host 加上即可。这样,当浏览器访问这个 url 的时候便会打开我们的App。那么怎么拿传递的数据呢,不要方,看代码。

090531_ea1p_1771562.gif

之所以要作一个判断,是为了区别该界面是否是由浏览器打开的,然后我们便可以通过 Uri 的 getQueryParameter 来获取传递的信息了,当然,你还可以拿到 Uri 的其他信息,这里就不说明了。

至此,通过浏览器打开本地App功能算是完成了。相信读者看到这里也会说,这么简单,我分分钟便能搞定。没错,做到这个地步,相信只要有点Android开发知识的同学都能分分钟搞定,那么我在这里提几个问题:

  • 假如我们从浏览器打开了应用,按下HOME键,然后又从桌面点击了应用图标,这时候会发生什么?

  • 假如应用之前已经打开了某个页面,然后我们又从浏览器重新打开了应用,这时候按返回键,我们还能回到之前打开的页面么?

  • 我们通过浏览器打开页面一般都会是二级甚至三级页面,如果之前没有打开过应用,那么直接按返回键就会退出应用,这似乎用户体验不太友好,怎么解决?

如果你能把这三个问题解决了,那么从浏览器打开本地应用就算掌握了。哈哈,没那么简单吧,要解答上面三个问题,必须熟练掌握 Activity 的启动方式并灵活应用,下面我来介绍我的方案吧。

 

仿知乎浏览器打开本地应用

其实我上面抛出的三个问题就是从知乎的体验来提出的,通过浏览器打开知乎的某个问题的回答页面,假如之前知乎是打开的,则按返回键能直接回到之前的页面,如果之前知乎没有打开过,则按返回键会回到主页面。当通过浏览器打开页面后,按HOME键之后再重新点击图标,知乎会直接打开到浏览器打开的那个页面。所以知乎是完美解决了以上三个问题的。那么,我们来看如果不做任何处理,会是什么效果。

由于手机界面截图太大了,就不贴图了,我们从打印日志来看界面的启动情况吧。分别在 onCreate方法 和 onResume方法 中放入日志打印代码。

090531_E4N0_1771562.gif

首先完全退出应用,然后通过浏览器调用 url 唤起界面,然后按下HOME键,点击图标重新进入,我们可以得到如下所示的日志

090531_8NZ9_1771562.gif

可以看出点击图标后会重新创建应用,之前在浏览器打开的界面找不到了。同样对于第二个问题,如果我们不做任何处理,浏览器唤起的页面按返回键是回不到之前打开的界面的。那么,这是为什么呢?

篇幅所限,就不介绍 Activity 的四种启动方式了。首先我们来了解一下任务栈这个概念吧。默认情况下,如果没有对 Activity 设置 TaskAffinity属性,一个应用的所有 Activity 都是运行在同一个任务栈的,任务栈的名称为应用的 PackageName。如果从应用A启动应用B的某个 Activity C,则 C 会运行在 A 的任务栈中。说到这里,相信大家应该明白为啥了吧。

从桌面启动的应用运行在应用本身的任务栈中,而从浏览器打开的界面则运行在浏览器的任务栈中,两个任务栈是分开的,所以在情景一,会重新创建出新的任务栈来打开应用,而在情景二中,由于浏览器的后台任务栈是桌面,在浏览器的任务栈中按返回键当然不能回到本地应用的任务栈咯而是回到桌面。

那么,知道了问题的原因,怎么来解决这个问题呢?由于从桌面点击应用会创建自己的应用栈,那么如果我们可以把浏览器任务栈中的界面移动到应用本身的任务栈中,则不就解决第一个问题了么。那么怎么将 Activity 从其他任务栈中移到自己的任务栈中呢?方法很简单,只需要在相应的 Activity 中配置 allowTaskReparenting属性 为 true 即可。

android:allowTaskReparenting="true"

设置了该属性的 Activity 在应用真正启动时,会将在其他任务栈中移动到自己的任务栈中来,由于移动过来的界面处与栈顶,所以会直接显示之前在浏览器中打开的界面,是不是很厉害。

对于场景二,应用自己打开的 Activity 在自己的任务栈中,由于我们没办法把已启动的 Activity 移动到浏览器的任务栈中,所以只有另辟蹊径。我们知道浏览器唤起的那个界面如果不做任何处置,则会在浏览器的任务栈中新建 Activity,那么我们是不是可以指定唤起的 Activity 的打开方式为 SingleTask 呢,因为对于 SingleTask 类型启动的界面来说,如果在本任务栈中不存在对应的 Activity 的话,会在新的任务栈中新建 Activity。所以当浏览器唤起界面时,由于浏览器任务栈中没有对应 Activity,所以会在本地应用所在任务栈去创建 Activity,这样就链接到了本地应用的任务栈,并且将本来处于在后台任务栈的应用任务栈移动到了前台任务栈。

由于 Android 的返回机制是只有在某个任务栈为空时,才会退到上一个任务栈,所以按返回键的效果便是在本地应用栈中退出相应的 Activity,直到任务栈为空时,再把浏览器所在的任务栈移动到前台。当然有个小问题是如果我们在配置文件中声名 Activity 为 SingleTask 启动方式,如果该任务栈中存在相应的 Activity,则会把该 Activity 之上的所有 Activity 清除掉。而通过给 Intent 配置 New_Task 的 flag 方式默认不会清除 Activity 之上的其他 Activity,除非添加了 Clear_Top 的 flag。由于我们不能控制浏览器设置的 Intent,所以没法添加 New_Task 的 flag,所以直接在配置文件中设置 SingleTask 并行不通。

考虑到应用一般都会有启动页,并且启动后会自动 finish,那么我们可以利用该页面做做文章。我们通过浏览器打开启动页,然后在启动页面通过 New_Task 的方式去启动主页面,再逐级打开二级、三级页面。这样既不会清楚已经打开的 Activity,又可以实现任务栈的移动。法很简单,只需要在启动页面的 onCreate方法 中根据 Intent 的类型做页面跳转即可。

090531_Wzq8_1771562.gif

这样便解决了第二个问题,在浏览器中按返回键能回到之前打开的页面。对于第三个问题,就更简单了,我们只需要有个 AppManager类 来管理已经打开的界面,然后在启动页面判断应用是否有打开过的页面,如果有,则直接跳转到需要唤起的界面,如果之前没有打开界面,则先跳转到主界面,再跳转到需要唤起的界面,这样按返回键还能回到主界面不至于直接退出应用。这里值得注意的是跳转到主界面不要新建Intent,直接沿用获得的Intent重新设置跳转信息即可。思路比较简单,就不再赘述了。

090531_3t3o_1771562.gif

这样三个问题都解决了,我们来简单回味一下整个优化过程:

  • 通过设置 android:allowTaskReparenting=”true” 属性将其他任务栈中存在的 Activity 在应用启动时移动到自己的任务栈中,实现按HOME键启动应用能回到浏览器唤起的页面。

  • 通过 Splash 页面做过渡,通过 New_Task 的方式启动浏览器唤起的页面,使得在浏览器中按返回键能接着本地应用已打开的页面。

  • 通过判断 Activity 的数量决定是否是直接唤起页面还是先唤起主界面再打开需要打开的界面使得按返回键不至于直接从二级或者三级界面退出应用,提高用户体验。

文字写得有点啰嗦,那来一张简单明了的图吧。按照这个设计流程,并且在 AndroidManifest.xml 中将 MainActivity 以及浏览器需要唤起的 Activity 设置 android:allowTaskReparenting=”true” 属性,你也可以实现知乎那样的浏览器唤起应用。

640?wx_fmt=png&wxfrom=5&wx_lazy=1

能看到这里的都是真爱啊,希望大家在Android的道路上越走越远,越走越远,越走越远!

转载于:https://my.oschina.net/JiangTun/blog/916777

你可能感兴趣的文章
centos6.5监控平台nagios搭建与配置
查看>>
活动目录域及工作组环境外部时间源同步
查看>>
ACS USB安装引导制作
查看>>
lync客户端外网登陆提示exchange连接错误
查看>>
计算机应用一级B考试专题整理三
查看>>
bzoj 2762: [JLOI2011]不等式组——树状数组
查看>>
uva 437,巴比伦塔
查看>>
利用FFmpge进行视频解码(从H264视频流到图像)
查看>>
P51、面试题5:从尾到头打印链表
查看>>
有关一些 c++ cmath 库中的函数的精度问题
查看>>
HTML Input Text cursor position control
查看>>
阿里云应用安装 小记
查看>>
The Google File System(前五章,翻译的略乱)
查看>>
RUP 迭代开发计划的两种方法
查看>>
PHP中static关键字
查看>>
python处理xml实例
查看>>
从《一生所爱》到《世间始终你好》
查看>>
UIStackView相关
查看>>
数据库
查看>>
20145240问卷调查
查看>>