最近项目中需要实现在WebView中打开的H5页面可以打开其他应用的功能,用本篇文章记录一下该功能的实现方案。
手机自带的浏览器可以通过两种协议来实现打开其他应用,分别为:
- Scheme协议(Android Deep Links 基于此协议)。
- 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。在上面的例子中https://www.minigame.vip、https://minigame.vip、jump://minigame.vip、jump://www.minigame.vip 都能打开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)
实现效果如下:
可以看到,弹出了一个选择框让用户选择要打开的App,这个效果不是很理想。
在官方文档中有提到,在Android M(6.0)以上,如果验证了你是应用和网站的拥有者,那么就不会弹出这个选择框,而是直接进入你的应用。 如果要验证应用和网站的所有权,需要:
- 在Mainfest中开启自动验证:
<intent-filter android:autoVerify="true">
...
</intent-filter>
2. 在网站上提供assetlinks.json:
https://domain.name/.well-known/assetlinks.json
assetlink.json文件可以通过AndroidStudio中的App Links Assistant生成,具体步骤参考官方文档。
自定义的Scheme
实现效果如下:
可以看到直接打开了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()
}
}
实现效果如下:
存在的问题与自定义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
}
}
实现效果如下:
总结
上述的几种方案都能满足WebView中打开的H5页面可以打开其他应用的功能,可以按需选用。