赞
踩
H5唤醒App有多种方式,但一般前端为了同时兼容Android和iOS会采用URL Scheme的形式,下面就重点介绍这种方式。 一个URL Scheme是由以下部分组成的:
[scheme]://[host]:[port][path]?[参数]
说明以及举例
scheme
是和前端商量好的,一般可以用公司的英文名称,比如淘宝就是taobao
,这里举例定义为abc
host
是主机,可以写公司域名,可以多级,这里举例定义为abc.com
port
是端口,随便定义,这里举例定义为8088
path
是路径,随便定义,这里举例定义为/router
参数
是前端带给App的参数,这里举例定义为token=123&data={这是一个json字符串}
那么拼出来的URL Scheme就是:
abc://abc.com:8088/router?token=123&data={这是一个json字符串}
在项目中需要根据以上的协议定义一个可以由外部打开的Activity,在清单文件中注册如下:
<!--H5唤起App的中转页面--> <activity android:name=".activity.H5WakeUpNativeActivity" android:exported="true" android:screenOrientation="portrait" tools:ignore="AppLinkUrlError"> <intent-filter> <data android:host="abc.com" //这里的参数和前端定义的保持一致 android:path="/router" //这里的参数和前端定义的保持一致 android:port="8088" //这里的参数和前端定义的保持一致 android:scheme="abc" /> //这里的参数和前端定义的保持一致 <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <action android:name="android.intent.action.VIEW" /> </intent-filter> </activity>
有了这个Activity之后就可以考虑后续的操作了。
唤醒App可能存在以下几个问题:
1、App可能启动了,也可能没启动,没启动的话需要先启动App,启动App后才能跳转,跳转的数据需要存下来。
2、App如果已经在后台了,需要判断用户是否已经登录,如果跳转的目标页面需要登录,那需要先登录后才能跳转到目标界面,跳转的数据需要存下来。
3、想要跳转的目标Activity很多,那么就需要找到一个统一负责跳转和维护的地方。
如果使用数据透传以及ARouter广播通知continue(postCard)的方式实在太麻烦了,可以试试别的思路。
因为一般情况下MainActivity存在App就在,所以将跳转和维护都放在MainActivity中。
我们开发的App一般区分二种情况,一是进MainActivity之前需要登录,二是进MainActivity之前不需要登录,碰到具体操作的时候才需要登录。
下面分别来看。
1、项目使用ARouter实现跳转,不然组件化的项目拿不到其他模块的Activity的类和对象。
2、项目有维护全局可调用的ViewModel对象,可以看我之前写的一篇文章《Android如何设计一个全局可调用的ViewModel对象?》 本文采用的是博客中的方式二。 (建议点过去看一下,不然有可能看不懂下面的内容)。
3、和前端约定传过来的JSON数据的格式,以下面解析后的结果为例:
/** * H5唤醒App时带过来的数据 * 原始Json数据:{"path":"AC001","data":{"id":13786,"user_id":56129},"code":200,"msg":""} */ data class H5ParamsBean( val path: String = "", //跳转的路径标识 val code: Int = 0, val msg: String = "", var data: DataBean? = null, ) data class DataBean( var id: Int = 0, var user_id: Int = 0 )
其中path
字段跳转到哪里和前端同事商量好,比如:
//从 h5 页面经过路由activity跳转至修改密码界面
const val H5_ROUTER_UPDATE_PASSWORD_ACTIVITY = "AC001"
//从 h5 页面经过路由activity跳转至忘记密码界面
const val H5_ROUTER_FORGOT_PASSWORD_ACTIVITY = "AC002"
//从 h5 页面经过路由activity跳转至注销账号界面
const val H5_ROUTER_CANCEL_ACCOUNT_ACTIVITY = "AC003"
//从 h5 页面经过路由activity跳转至意见反馈界面
const val H5_ROUTER_FEED_BACK_ACTIVITY = "AC004"
上面的数据根据自己的需求改成自己实际需要的。这里列出来是为了方便说明。
下面进入正题。
先从需要登录后才能进入MainActivity的App说起,再讲不需要登录就能进MainActivity的App。
当拿到前端传递过来的数据后,将数据设置给全局可调用的ViewModel
对象EventViewModel
中的SharedFlow--->h5WakeUpMutableSharedFlow
,然后在MainActivity
中订阅这个SharedFlow
,记住SharedFlow
的replay
的值要设置为1,因为App没启动时,是先设置值后订阅的,所以数据重放(replay)
要设置为1。
先看看H5唤醒原生Activity的代码:
/** * H5跳转到原生页面 * 具体传参如下: * abc://abc.com:8088/router?token="123"&data={这是一个json字符串} */ class H5WakeUpNativeActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //唤醒App wakeUpApp() } private fun wakeUpApp() { //判断MainActivity是否在栈中 isMainActivityRunning().also { isRunning -> val paramsBean = getParamsBean() if (isRunning) { //MainActivity在栈中 //eventViewModel就是全局可调用的viewmodel的对象,下同 //设置唤醒时携带的值 eventViewModel.setH5WakeUpValue(paramsBean) } else { //MainActivity不在栈中 eventViewModel.setH5WakeUpValue(paramsBean) //唤醒App ARouter.getInstance().build(ARouterPath.StartActivity).navigation() } } finish() } //获取H5传递过来的值并解析成Bean类 private fun getParamsBean(): H5ParamsBean? { val uri = intent.data if (isRecognizable(uri)) { uri?.getQueryParameter("data")?.apply { if (isNotEmpty()) { try { return GsonUtils.fromJson(this, H5ParamsBean::class.java) } catch (exception: JsonSyntaxException) { LogUtils.d("H5WakeUpNativeActivity JsonSyntaxException") } } } } return null } //是不是需要这个类解析的scheme private fun isRecognizable(uri: Uri?): Boolean { return if (uri != null) { val url: String = uri.toString() return url.startsWith("abc://abc.com:8088/router?") } else { false } } /** * 判断MainActivity是否在运行 */ private fun isMainActivityRunning(): Boolean { //使用的是AndroidUtilCode库的Api表示感谢 ActivityUtils.getActivityList().apply { if (isNotEmpty()) { forEach { if (it::class.java.simpleName == "MainActivity") { return true } } } } return false } }
这里的逻辑比较简单:
1、拿H5传过来的data
的JsonString
字符串解析成Bean
类。
2、将数据设置给SharedFlow--->h5WakeUpMutableSharedFlow
。
3、如果MainActivity
不在调用栈中,就启动App
。
上面大部分代码都是解析数据,不管采用什么唤醒方式,基本都是要写的。
下面重点看EventViewModel
的代码:
class EventViewModel : ViewModel() { //与viewModel生命周期关联的协程 private val coroutineScope: CoroutineScope get() = viewModelScope /** * H5唤醒App的跳转的Flow */ private val h5WakeUpMutableSharedFlow: MutableSharedFlow<H5ParamsBean> = MutableSharedFlow(replay = 1) <--------注意这里 /** * 设置H5唤醒携带的值 */ fun setH5WakeUpValue(bean: H5ParamsBean?) { bean?.apply { h5WakeUpMutableSharedFlow.tryEmit(bean) } } /** * 开始订阅,获取H5唤醒携带的值 */ fun getH5WakeUpValue(method: (bean: H5ParamsBean) -> Unit) { coroutineScope.launch(Dispatchers.IO) { h5WakeUpMutableSharedFlow.collect { method.invoke(it) } } } }
在MainActivity中订阅
eventViewModel.getH5WakeUpValue {
//TODO 跳转到具体Activity的业务代码
}
以上方式的优点在于:
1、简洁,除了解析数据,实际的代码就一点点。
2、让MainActivity
订阅解决了是否登录的问题,因为登录才能进入MainActivity。
3、使用SharedFlow
的replay=1
轻松解决了App
启动传值和登录传值的问题。因为设置数据在前,MainActivity
订阅在后,进入MainActivity
还是能拿到数据。
不需要登录就能进入MainActivity会稍微麻烦一点,因为需要监听登录的状态,但是在EventViewModel
中也能轻松的解决。创建一个StateFlow
监听登录的状态,然后让它与我们上面定义的SharedFlow--->h5WakeUpMutableSharedFlow
联动,当用户登录成功后将未处理完的跳转继续跳转就好了,代码如下:
class EventViewModel : ViewModel() { private var mH5ParamsBean: H5ParamsBean? = null //与viewModel生命周期关联的协程 private val coroutineScope: CoroutineScope get() = viewModelScope /** * 全局的登录状态 */ private val loginMutableStateFlow: MutableStateFlow<LoginState> = MutableStateFlow(LoginState.NOT_LOGGED_IN) //默认未登录 val loginStateFlow: StateFlow<LoginState> = loginMutableStateFlow /** * H5唤醒App的跳转 */ private val h5WakeUpMutableSharedFlow: MutableSharedFlow<H5ParamsBean> = MutableSharedFlow(replay = 1) /** * 设置登录的状态。登录/退出登录都需要设置。LoginState自定义登录的枚举类。 */ fun setLoginState(value: LoginState) { loginMutableStateFlow.value = value } /** * 设置H5唤醒携带的值 */ fun setH5WakeUpValue(bean: H5ParamsBean?) { bean?.apply { h5WakeUpMutableSharedFlow.tryEmit(bean) } } /** * 获取H5唤醒携带的值 */ fun getH5WakeUpValue(method: (bean: H5ParamsBean) -> Unit) { coroutineScope.apply { launch(Dispatchers.IO) { //H5唤醒原生页面的事件回调 h5WakeUpMutableSharedFlow.collect { when (it.path) { "AC006" -> { //需要登录才能跳转的页面 AC006是举例 if (loginStateFlow.value == LoginState.LOGGED_IN) { //已经登录 method.invoke(it) } else { //未登录 //将值存起来 mH5ParamsBean = it //跳转到登录页面 ARouter.getInstance().build(ARouterPath.LoginActivity).navigation() } } else -> { //不需要登录就能跳转的页面 method.invoke(it) } } } } launch(Dispatchers.IO) { //登录状态的事件回调 loginStateFlow.collect { state -> when (state) { LoginState.LOGGED_IN -> { //用户已登录 //用户已登录,继续之前的跳转动作 mH5ParamsBean?.apply(method).also { //跳转后参数设置为null mH5ParamsBean = null } } LoginState.NOT_LOGGED_IN -> { //用户未登录 } } } } } } }
当用户在登录页面登录成功后,修改登录的StateFlow
的状态值,继续完成未完成的跳转事件,这样整个事件就贯穿起来了。也不需要在ARouter拦截器中使用广播那么繁琐了。
扩展:
① 如果用户取消了登录,需要清除跳转怎么办?监听登录的StateFlow
中增加用户取消登录的状态,适时回调,将数据置为null即可。
② 如果H5需要一次唤醒多个页面跳转,怎么办?那更是Flow的强项了,将数据逐个分发,逐个跳转即可。
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。