admin管理员组

文章数量:1613742

Android自定义URL使用Scheme方式唤起Activity或App

一、浏览器打开本地App

常规模式下,浏览器打开一个url要么是http要不就是https,那么浏览器是如何识别url指向我们的app呢?额!巧了,有这么一个scheme协议,它的url有scheme、host、host、path、query等部分组成。浏览器可以通过这种协议识别我们的请求,导向我们的应用。我们只需要在我们应用配置scheme。

<activity
            android:name=".transit.ProxyActivity"
            android:launchMode="singleTask"
            android:screenOrientation="portrait">
            <intent-filter>
                <data
                    android:host="proxy"
                    android:path="/proxy.html"
                    android:scheme="lechuangkeji" />
                <action android:name="android.intent.action.VIEW"></action>

                <category android:name="android.intent.category.DEFAULT"></category>
                <category android:name="android.intent.category.BROWSABLE"></category>
            </intent-filter>
        </activity>

浏览器需要配置和我们一样的scheme协议便可跳转
scheme: 协议名称(由开发人员自定义)(必要,其他都是可选)
host: 域名
port:端口
path: 页面路径
query: 请求参数

二、我们如何获取浏览器传递的信息

Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
 Uri data = intent.getData();
 String id = data.getQueryParameter("id");
}

这样便可以获取浏览器传递过来的信息。
如果需要跳转不同界面就可以根据传递的信息来判断跳转

三、我们如何优雅的从浏览器打开本地应用

正如标题所说的,我们不止要跳转到我们的应用,还需要优雅的跳到我们的应用。为什么这么说呢?请看下面三个问题:

  1. 假如我们从浏览器打开了应用,按下HOME键,然后又从桌面点击了应用图标,这时候会发生什么?
  2. 假如应用之前已经打开了某个页面,然后我们又从浏览器重新打开了应用,这时候按返回键,我们还能回到之前打开的页面么?
  3. 我们通过浏览器打开页面一般都会是二级甚至三级页面,如果之前没有打开过应用,那么直接按返回键就会退出应用,这似乎用户体验不太友好,怎么解决?

我们通过从浏览器打开知乎的来举例:

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

Log.d("Activity start onCreate", getIntent().getAction());
Log.d("Activity start onResume", getIntent().getAction());

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

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

篇幅所限,就不介绍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的类型做页面跳转即可。

Intent intent = getIntent();
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
 Intent intent1 = new Intent();
 intent1.setClass(this, MainActivity.class);
 intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 startActivity(intent1);
 finish();
} else if (Intent.ACTION_MAIN.equals(intent.getAction())) {
 Intent intent1 = new Intent();
 intent1.setClass(this, MainActivity.class);
 startActivity(intent1);
 finish();
}

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

if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
 int activitySize = AppManager.getAppManager().getSize();
 if (activitySize > 1) {
 jumpFromBrowser(intent.getData());
 } else {
 intent.setClass(this, MainActivity.class);
 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 startActivity(intent);
 }
 finish();
 return;
}

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

  • 通过设置android:allowTaskReparenting=”true”属性将其他任务栈中存在的Activity在应用启动时移动到自己的任务栈中,实现按HOME键启动应用能回到浏览器唤起的页面。
  • 通过Splash页面做过渡,通过New_Task的方式启动浏览器唤起的页面,使得在浏览器中按返回键能接着本地应用已打开的页面。
  • 通过判断Activity的数量决定是否是直接唤起页面还是先唤起主界面再打开需要打开的界面使得按返回键不至于直接从二级或者三级界面退出应用,提高用户体验。

本文标签: 优雅浏览器