Android正确使用Scheme协议打开App,兼容浏览器scheme的二次跳转


URL Scheme

URL Scheme是一种页面内跳转协议,通过定义自己的URL Scheme协议,可以实现

  1. 从一个APP中打开另外一个APP指定的页面
  2. 从H5页面中跳转到APP指定的页面(实际上就是从一个浏览器中的一个页面跳转到APP指定页面)。

URL Scheme协议格式:
一个完整的完整的URL Scheme协议格式由scheme、host、port、path和query组成,其结构如下所示:

<scheme>://<host>:<port>/<path>?<query>

如下就是一个自定义的URL
openapp://hhong:80/product?productId=10000007
openapp: 即Scheme 该Scheme协议名称(必填)
hhong: 即Host,代表Scheme作用于哪个地址域(必填)
80: 即port,代表端口号
product:即path,代表打开的页面
param: 即query,代表传递的参数

传递参数的方法跟web端一样,通过问号?分隔,参数名和值之间使用等号=连接,多个参数之间使用&拼接。

Scheme使用

既然我们使用scheme来做打开app并跳转的逻辑,那这个scheme应该声明在哪里比较合适呢?如果你的应用在启动页(splash)或者在主界面(main)初始化了一些必要的设置,比如必要的token信息检查交易或者一些其它校验等,没有这些信息会造成崩溃的,这个时候我们就需要在启动页来声明这个scheme了,获取scheme信息保存起来,然后在主界面做处理逻辑,如跳转到其它界面等。当然你也可以声明scheme在其它地方,具体得需要看怎么实现业务比较方便。

scheme跳转
scheme跳转
APP1
APP2启动页保存参数
浏览器
APP2主页
APP2具体页面

如配置SplashActivity完整的打开链接为openapp://hhong:80/product?param=100,需要在AndroidManifest.xml配置(SplashActivity启动模式为默认standard模式)

        <activity android:name=".SplashActivity">

            <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="hhong"
                    android:path="/product"
                    android:port="80"
                    android:scheme="openapp" />
            </intent-filter>
        </activity>

定义好scheme相关的参数后,现在我们需要用scheme调起目标app,主要有两种方式

  1. APP中打开另一个APP指定的页面
String url = "openapp://hhong:80/product?productId=10000007";
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
//intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String intentUri = intent.toUri(Intent.URI_INTENT_SCHEME);
Log.e(TAG, "action是:" + intentUri);
startActivity(intent);

这种方式的跳转比较简单,也是最不容易出错的。

  1. 浏览器中的一个页面跳转到APP指定页面

这种方式的跳转可能同样的代码会导致不同的结果,因为涉及到浏览器,而不同的手机厂商不能保证所有的浏览器内核是一样的,进而可能对scheme的处理也是不一样。
前提:项目用的框架是蚂蚁的MPASS,这个项目可能和正常工程项目不同,因为框架对项目的启动方式做了封装,有兴趣的同学也可以去看下这个框架。
问题:app未启动时,由微信分享出来的链接然后在华为浏览器打开这个链接跳转到指定app时(非桌面图标启动app),第一次是能正常跳转到指定页面,但是app切到后台时,再在华为浏览器打开链接发现竟然不做跳转了,只是将应用切回前台页面,但在小米手机上同样的操作,好像又是正常的,这个把我们搞得一脸懵逼,我们猜测是否和浏览器内核有关,于是我们在Android 自带WebView做scheme的跳转,发现也是正常的。

<div>
    <a href="openapp://hhong:80/product?productId=10000007"></a>
</div>

还有就是浏览器上的scheme跳转,Android这边怎么处理的呢?scheme启动Activity其实用的就是Intent,细心的你肯定发现了第一种方式:APP中打开另一个APP指定的页面的跳转,log打印了一个intentUri:

   String intentUri = intent.toUri(Intent.URI_INTENT_SCHEME);
   Log.e(TAG, "action是:" + intentUri);

打印的结果:

E/MainActivity: action是:intent://hhong:80/product?productId=10000007#Intent;scheme=openapp;end

如果我们在方式1中把intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)加上,打印的结果:

E/MainActivity: action是:intent://hhong:80/product?productId=10000007#Intent;scheme=openapp;launchFlags=0x10000000;end

相比之下,多了个launchFlags字段值,这个值在二次跳转起着重要作用(SplashActivity启动模默认standard),Android FLAG标识和启动模式关系可以看下这两篇文章Android Intent FLAG详解,包括其他的标记的一些解释Android之点击Home键后再次打开导致APP重启问题,我们把项目SplashActivity的启动模式改为singleTask就完美的实现了我们的需求,此时mpass工程点击桌面图标并不会重新走开屏页SplashActivity。
其实华为推送通知跳转到指定页面时,在控制台上配置的就是这个intent,有兴趣的同学可以去看下华为推送官方文档,看下他们如何定义intent的,所以对于Android来说上面的href标签也可以写成这样:

<div>
    <a href="intent://hhong:80/product?productId=10000007#Intent;scheme=openapp;end"></a>
</div>

这两种方式是一样的效果,但是我们从华为浏览器scheme跳转打开app拦截到的intent是携带了FLAG,intent还有些其它的信息,如页面appfilterctrl、当前由哪个应用跳转的application_id。下面是我用正常工程测试浏览器scheme启动跳转app拦截到的intent(测试机是华为手机,可能不同的手机有不同的效果):

华为浏览器
intent://hhong:80/product?productId=10000007#Intent;
scheme=openapp;
category=android.intent.category.BROWSABLE;
launchFlags=0x17000000;
launchHwFlags=0x400;
component=com.example.aapp/.SplashActivity;end

UC浏览器
intent://hhong:80/product?productId=10000007#Intent;
scheme=openapp;
launchFlags=0x13000000;
launchHwFlags=0x400;
component=com.example.aapp/.SplashActivity;end

Android 自带的WebView
intent://hhong:80/product?productId=10000007#Intent;
scheme=openapp;
category=android.intent.category.BROWSABLE;
launchFlags=0x3000000;
launchHwFlags=0x400;
component=com.example.aapp/.SplashActivity;end

桌面图标启动
intent:#Intent;
action=android.intent.action.MAIN;
category=android.intent.category.LAUNCHER;
launchFlags=0x10200000;
component=com.example.aapp/.SplashActivity;sourceBounds=792%201686%201044%202001;end

SplashActivity的启动模式都是默认的standard,最终发现UC浏览器启动app后能正常跳转到页面,但是我再次从浏览器scheme启动app时,不会再跳转指定页面,这一点和桌面启动效果类似,点击桌面图标启动app后,再次点击只会把app切到前台。从上面可以看出主要的区别在于launchFlags,如果将SplashActivity启动模式改为singleTask、singleTop、singleInstance又会是怎样呢?

结论:测试正常工程发现如果SplashActivity的启动模式为standard,scheme二次能否跳转指定页面(能否重新创建SplashActivity)取决于浏览器内核,因为它所携带的launchFlags是不同的;如果SplashActivity的启动模式为singleTask、singleTop、singleInstance,scheme二次都能跳转指定页面,和浏览器内核(携带的launchFlags)无关,但是每次点击桌面图标时都会重新打开app,这个时候我们需要在启动页SplashActivity添加一段代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        if ((getIntent().getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != 0) {
//            finish();
//            return;
//        }
        // 避免从桌面启动程序后,会重新实例化入口类的activity
        if (!this.isTaskRoot()) {
            Intent intent = getIntent();
            if (intent != null) {
                String action = intent.getAction();
                if (intent.hasCategory(Intent.CATEGORY_LAUNCHER) && Intent.ACTION_MAIN.equals(action)) {
                    finish();
                    return;
                }
            }
        }
        setContentView(R.layout.activity_splash);
    }

这样就保证了在正常工程下,兼容各种浏览器scheme的二次跳转能正常跳转到指定页面,并且每次点击桌面图标又不会重新打开app了!

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐