一、H5怎么唤醒App

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说起,再讲不需求登录就能进MainActivity的App。

当拿到前端传递过来的数据后,将数据设置给大局可调用的ViewModel目标EventViewModel中的SharedFlow--->h5WakeUpMutableSharedFlow,然后在MainActivity中订阅这个SharedFlow,记住SharedFlowreplay的值要设置为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传过来的dataJsonString字符串解析成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、运用SharedFlowreplay=1轻松处理了App发动传值和登录传值的问题。由于设置数据在前,MainActivity订阅在后,进入MainActivity仍是能拿到数据。

五、不需求登录就能进入MainActivity的App

不需求登录就能进入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的强项了,将数据逐一分发,逐一跳转即可。

六、总结

上面便是最近做项目一个需求的一点思路,记录一下。如有过错或缺乏欢迎纠正。谢谢!