阅读 213

Android WebView 支持H5打开其他App

最近项目中需要实现在WebView中打开的H5页面可以打开其他应用的功能,用本篇文章记录一下该功能的实现方案。

手机自带的浏览器可以通过两种协议来实现打开其他应用,分别为:

  1. Scheme协议(Android Deep Links 基于此协议)。

  2. Intent协议。

1.通过Scheme协议实现

Scheme协议的格式为:

[scheme]://[host]/[path]?[query]复制代码

如果你的App中的某个页面需要响应Scheme协议,则需要在Mainfest中添加如下代码:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.minigame.sdktest">

    <application>

        <activity
            android:name="com.minigame.sdktest.ui.HomeActivity"
            android:exported="true">

            <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" />

                //要响应隐式Intent必须提供此类别
                <category android:name="android.intent.category.DEFAULT" />
                //要从浏览器中打开应用必须提供此类别
                <category android:name="android.intent.category.BROWSABLE" />
                
                //代表解析为Activity的URL格式
                <data
                    android:host="www.minigame.vip"
                    android:scheme="https" />
                <data
                    android:host="minigame.vip"
                    android:scheme="jump" />
            </intent-filter>
        </activity>
    </application>
</manifest>复制代码

注意:如果在一个intent-filter里面包含了多个data,匹配的规则会涵盖所有data的组合。如果需要匹配唯一的网址,一个intent-filter只能包含一个data。在上面的例子中www.minigame.vip、https://minigame.v… 都能打开MainActivity。

WebView支持Scheme协议

手机自带的浏览器支持Scheme协议,但是WebView需要自己实现,代码如下:

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView?, request:WebResourceRequest?): Boolean {
        val url = request?.url
        val scheme = url?.scheme
        val host = url?.host
        when (scheme) {
            "https" -> {
                //仅过滤某些host进行判断是否跳转,也可不过滤
                if ("www.minigame.vip" == host) {
                    gotoOtherAppBySchemeProtocol(url)
                }
            }
            "jump" -> {
                gotoOtherAppBySchemeProtocol(url)
            }
            else -> {}
        }
        return super.shouldOverrideUrlLoading(view, request)
    }
}

private fun gotoOtherAppBySchemeProtocol(url: Uri) {
//   isActivityExits方法存在限制
//    val intent = Intent(Intent.ACTION_VIEW, url)
//    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
//    if (isActivityExits(intent)) {
//        startActivity(intent)
//    }

    try {
        val intent = Intent(Intent.ACTION_VIEW, url)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
        startActivity(intent)
    } catch (e: ActivityNotFoundException) {
        //通过直接处理抛出的ActivityNotFound异常来确保程序不会崩溃
        e.printStackTrace()
    }
}

/**
 * intent对应的Activity是否存在
 *
 * 在Android R(11) 以上,根据官方文档(https://developer.android.com/training/package-visibility)
 * 需要通过两种方式来确保有返回值
 * 1.可以在清单文件<queries>标签中添加包名
 * 2.申请QUERY_ALL_PACKAGES权限(文档中描述google对该权限的审核较为严格,可能会导致上架失败)
 *
 * @param intent intent
 */
private fun isActivityExits(intent: Intent): Boolean {
    val resolveActivity = intent.resolveActivity(packageManager)
    return resolveActivity != null
}复制代码

默认格式的Scheme(Https、Http)

实现效果如下:originalScheme.gif

可以看到,弹出了一个选择框让用户选择要打开的App,这个效果不是很理想。

在官方文档中有提到,在Android M(6.0)以上,如果验证了你是应用和网站的拥有者,那么就不会弹出这个选择框,而是直接进入你的应用。 如果要验证应用和网站的所有权,需要:

  1. 在Mainfest中开启自动验证:

<intent-filter  android:autoVerify="true">
    ...
</intent-filter>复制代码
  1. 在网站上提供assetlinks.json:

https://domain.name/.well-known/assetlinks.json复制代码

assetlink.json文件可以通过AndroidStudio中的App Links Assistant生成,具体步骤参考官方文档。

自定义的Scheme

实现效果如下:diyScheme.gif

可以看到直接打开了App。WebView显示无法打开网页的问题,可以根据业务需求来处理,比如停留在上一个页面或者加载一个新的网址。

2.通过Intent协议实现

Intent协议的格式为:

intent://[host]##Intent;package=[String];action=[String];category=[String];component=[String];scheme=[String];S.browser_fallback_url=[String];end;复制代码

可以只填需要的参数,比如:

intent://www.minigame.vip##Intent;package=com.minigame.sdktest;scheme=https;S.browser_fallback_url=https://www.minigame.vip;end;复制代码

App响应Intent协议所需的配置与Scheme协议相同。

WebView支持Intent协议

手机自带的浏览器支持Intent协议,但是WebView需要自己实现,代码如下:

webView.webViewClient = object : WebViewClient() {
    override fun shouldOverrideUrlLoading(view: WebView?, request:WebResourceRequest?): Boolean {
        val url = request?.url
        val scheme = url?.scheme
        if("intent" == scheme){
            gotoOtherAppByIntentProtocol(url)
        }
        return super.shouldOverrideUrlLoading(view, request)
    }
}

private fun gotoOtherAppByIntentProtocol(url: Uri) {
    val stringUrl = url.toString()
    val fallbackUrl: String = if (stringUrl.contains("S.browser_fallback_url")) {
        stringUrl.substring(stringUrl.indexOf("S.browser_fallback_url"), stringUrl.indexOf(";end"))
    } else {
        ""
    }

    try {
        val intent = Intent.parseUri(url.toString(), Intent.URI_INTENT_SCHEME)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
        startActivity(intent)
    } catch (e: URISyntaxException) {
        e.printStackTrace()
    } catch (e: ActivityNotFoundException) {
        //通过直接处理抛出的ActivityNotFound异常来确保程序不会崩溃
        e.printStackTrace()
    }
}复制代码

实现效果如下:intentProtocol.gif

存在的问题与自定义Scheme一样。

3.通过包名直接打开应用

除了通过协议来打开应用以外,还可以通过JS交互的方式,把要打开的App的包名传递给Android端,通过包名打开应用,代码如下:

//设置是否开启JavaScript
webView.settings.javaScriptEnabled = true
//允许js弹出窗口
webView.settings.javaScriptCanOpenWindowsAutomatically = true

webView.addJavascriptInterface(object : JsInterface {
    @JavascriptInterface
    override fun openApp(packageName: String?) {
        gotoOtherAppByPackageName(packageName)
    }
}, "JsInterface")

fun gotoOtherAppByPackageName(packageName: String?) {
    if (!packageName.isNullOrEmpty()) {
        if (checkInstallStatus(this, packageName)) {
            val intent = packageManager.getLaunchIntentForPackage(packageName)
            if (intent != null) {
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                startActivity(intent)
            }
        }
    }
}

/**
 * 根据包名判断应用是否安装了
 */
fun checkInstallStatus(context: Context, packageName: String): Boolean {
    return try {
        val packageInfo = context.packageManager.getPackageInfo(packageName, 0)
        packageInfo != null
    } catch (e: NameNotFoundException) {
        e.printStackTrace()
        false
    }
}复制代码

实现效果如下:js.gif

总结

上述的几种方案都能满足WebView中打开的H5页面可以打开其他应用的功能,可以按需选用。


作者:ChenYhong
链接:https://juejin.cn/post/7047005498712784909


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐