直播技术-Android基础框架(个人梳理,侵权必究)
目录
基于核心任务调度机制的挂载点的业务,使用Task提供的数据集
(一)直播间架构
(二)核心任务调度机制
(1)复制从滑动直播间加载流程
(2)核心任务调度机制-代码设计
//直播间流程控制管理器
interface LiveRoomFlowManager {
@UiThread
fun registerTask(task: LiveRoomFlowTask) //注入任务
@UiThread
fun dispatchTask(roomStatus: LiveRoomStatus) // 执行卡点开始事件
fun destroy() //直播间销毁事件
}
//配置优先级管理中心
object LiveRoomFlowConfig {
const val LOW_PRIORITY = 1000L
const val DEFAULT_PRIORITY = 10000L
const val HIGH_PRIORITY = 1000000L
const val HIGHEST_PRIORITY = Long.MAX_VALUE
//单个卡点优先级单独管理
object OnCreate {
const val INIT_PARAMS = HIGHEST_PRIORITY
const val INIT_IJK = HIGHEST_PRIORITY - 1000L
const val INIT_ITRACKER = HIGHEST_PRIORITY - 2000L
const val INIT_ROOM_VIEW = HIGHEST_PRIORITY - 3000L
const val INIT_VIEWMODEL = HIGHEST_PRIORITY - 4000L
const val INIT_PLAYERFRAGMENT = HIGHEST_PRIORITY - 5000L
const val INIT_CONFIGANDPCU = HIGHEST_PRIORITY - 6000L
}
object OnResume {
const val COMMON = HIGH_PRIORITY
const val HEARTBEAT = HIGH_PRIORITY - 1000L
}
object OnP0 {
//viewModel
const val INIT_P1 = HIGH_PRIORITY
const val INIT_PLAYERDATA = HIGH_PRIORITY - 1000L
//View
const val INIT_PLAY = HIGH_PRIORITY - 2000L
}
object OnP1 {
const val PLAYER_VIEWMODEL = HIGH_PRIORITY
const val USER_VIEWMODEL = HIGH_PRIORITY - 1000L
const val SOCKET_VIEWMODEL = HIGH_PRIORITY - 2000L
const val INTERACTION_VIEWMODEL = HIGH_PRIORITY - 3000L
const val GIFT_VIEWMODEL = HIGH_PRIORITY - 4000L
const val BASIC_VIEWMODEL = HIGH_PRIORITY - 5000L
const val SPPLAYER_VIEWMODEL = HIGH_PRIORITY - 6000L
const val OPERATION_VIEWMODEL = HIGH_PRIORITY - 7000L
const val ANIM_VIEWMODEL = HIGH_PRIORITY - 8000L
const val SKIN_VIEWMODEL = HIGH_PRIORITY - 9000L
const val OPERATION_VIEWMODELV3 = HIGH_PRIORITY - 10000L
const val VOICE_VIEWMODEL = HIGH_PRIORITY - 11000L
const val SUPERCHAT_VIEWMODEL = HIGH_PRIORITY - 12000L
const val LPLSP_VIEWMODEL = HIGH_PRIORITY - 13000L
const val INTERACTIONPANEL_VIEWMODEL = HIGH_PRIORITY - 14000L
const val QUESTION_VIEWMODEL = HIGH_PRIORITY - 15000L
//分发 其他Basic数据
const val DISPATCH_BASIC_DATA = HIGH_PRIORITY - 16000L
const val DISPATCH_ROOM_INFO_COMPLETE = HIGH_PRIORITY - 17000L
}
}
//每个卡点可以挂载N个任务
sealed class LiveRoomFlowTask(val task: () -> Unit) {
abstract val tag: String //标签 别名
abstract val rule: LiveRoomFlowRule //规则 默认按优先级排序
abstract val liveRoomStatus: LiveRoomStatus // 挂载点
open val isSticky: Boolean = false //是否支持粘性消息
}
//数据记录相关,记录相关数据 埋点上报
class LiveRoomFlowTrace(val hashCode: Int) : LiveLogger {
override val logTag: String
get() = "LiveRoomFlowTrace"
private val initTs = SystemClock.elapsedRealtime()
private val reportMap = LinkedHashMap<String, LiveRoomStatusAssembly>()
init {
logDebug { "init onCreate ms: $initTs hashCode: $hashCode" }
}
fun recordRoomStatusStart(liveRoomStatus: LiveRoomStatus, startTs: Long) {
val assembly = reportMap[liveRoomStatus.tag]
?: LiveRoomStatusAssembly(ArrayList(), liveRoomStatus).also {
reportMap[liveRoomStatus.tag] = it
}
assembly.startTs = startTs
assembly.tasks.clear()
}
fun recordTask(liveRoomFlowTask: LiveRoomFlowTask, costTs: Long, exception: String? = null) {
val assembly = reportMap[liveRoomFlowTask.liveRoomStatus.tag]
?: LiveRoomStatusAssembly(ArrayList(), liveRoomFlowTask.liveRoomStatus).also {
reportMap[liveRoomFlowTask.liveRoomStatus.tag] = it
}
assembly.tasks.add(LiveRoomTaskCost(liveRoomFlowTask.tag, costTs, exception))
}
fun recordRoomStatusEnd(liveRoomStatus: LiveRoomStatus, endTs: Long) {
val assembly = reportMap[liveRoomStatus.tag]
?: LiveRoomStatusAssembly(ArrayList(), liveRoomStatus).also {
reportMap[liveRoomStatus.tag] = it
}
assembly.endTs = endTs
assembly.totalCostTs = assembly.endTs - assembly.startTs
}
fun logRoomStatus(roomStatus: LiveRoomStatus) {
reportMap[roomStatus.tag]?.let {
logDebug { JSON.toJSONString(it) }
}
}
fun reportMap() {
HandlerThreads.getHandler(HandlerThreads.THREAD_REPORT).post {
LiveReporter.reportTech(EVENT_ID, generateReportInfo(), sampler = { true }, force = false)
}
}
private fun generateReportInfo(): Map<String, String> {
return HashMap<String, String>().apply {
put("room_flow_info", JSON.toJSONString(reportMap))
}
}
companion object {
const val EVENT_ID = "live.room-flow.info.track"
}
}
class LiveRoomStatusAssembly(val tasks: ArrayList, val liveRoomStatus: LiveRoomStatus, var totalCostTs: Long = 0L, var startTs: Long = 0, var endTs: Long = 0)
data class LiveRoomTaskCost(val tag: String, val costTs: Long, val exception: String? = null)
//管理器实现类,实现内部所有功能
class LiveRoomFlowManagerImpl(private val hashCode: Int) : LiveRoomFlowManager, LiveLogger {
override val logTag: String
get() = "LiveRoomFlowManager"
private val mRoomTrace = LiveRoomFlowTrace(hashCode)
private val mTasks = mutableMapOf<LiveRoomStatus, LinkedList<LiveRoomFlowTask>>()
private val mComparator = Comparator<LiveRoomFlowTask> { o1, o2 -> o2.rule.priority.compareTo(o1.rule.priority) }
private var mCurrentRoomStatus: Int = LiveRoomStatus.ON_NONE.priority
private var isDestroyCalled = false
override fun dispatchTask(roomStatus: LiveRoomStatus) {
if (isDestroyCalled) return
mCurrentRoomStatus = mCurrentRoomStatus or roomStatus.priority
mRoomTrace.recordRoomStatusStart(roomStatus, SystemClock.elapsedRealtime())
mTasks[roomStatus]?.forEach {
if (isDestroyCalled) return
executeTask(it)
}
mRoomTrace.recordRoomStatusEnd(roomStatus, SystemClock.elapsedRealtime())
mRoomTrace.logRoomStatus(roomStatus)
}
override fun registerTask(task: LiveRoomFlowTask) {
if (isDestroyCalled) return
val queue = mTasks[task.liveRoomStatus] ?: LinkedList<LiveRoomFlowTask>().also {
mTasks[task.liveRoomStatus] = it
}
if (queue.contains(task)) {
return
}
setupRuleForTask(task, queue)
queue.add(task)
Collections.sort(queue, mComparator)
if (task.isSticky && hasRoomStatus(task.liveRoomStatus)) {
executeTask(task)
}
}
private fun setupRuleForTask(liveRoomFlowTask: LiveRoomFlowTask, queue: LinkedList<LiveRoomFlowTask>) {
val rule = liveRoomFlowTask.rule
if (rule.type == LiveRoomFlowRule.Type.PRIORITY) return
if (rule.type == LiveRoomFlowRule.Type.ALL_TOP) {
liveRoomFlowTask.rule.priority = HIGHEST_PRIORITY
return
}
val targetTask = getIndexByTag(rule.target, queue) ?: return
if (rule.type == LiveRoomFlowRule.Type.ABOVE) {
liveRoomFlowTask.rule.priority = targetTask.rule.priority + 1
}
if (rule.type == LiveRoomFlowRule.Type.BELOW) {
liveRoomFlowTask.rule.priority = targetTask.rule.priority - 1
}
}
private fun getIndexByTag(target: String, queue: LinkedList<LiveRoomFlowTask>): LiveRoomFlowTask? {
queue.forEachIndexed { _, liveRoomTask ->
if (target == liveRoomTask.tag) {
return liveRoomTask
}
}
return null
}
private fun hasRoomStatus(liveRoomStatus: LiveRoomStatus) = liveRoomStatus.priority and mCurrentRoomStatus == liveRoomStatus.priority
private fun executeTask(flowTask: LiveRoomFlowTask) {
val startTs = SystemClock.elapsedRealtime()
try {
flowTask.task()
val endTs = SystemClock.elapsedRealtime()
mRoomTrace.recordTask(flowTask, endTs - startTs)
} catch (e: Exception) {
val endTs = SystemClock.elapsedRealtime()
mRoomTrace.recordTask(flowTask, endTs - startTs, e.message)
}
}
override fun destroy() {
isDestroyCalled = true
mTasks.clear()
mRoomTrace.reportMap()
}
}
enum class LiveRoomStatus(val tag: String, val priority: Int) {
ON_NONE(“NONE”, ON_NONE_VAL),
ON_CREATE(“ON_CREATE”, ON_CREATE_VAL),
ON_RESUME(“ON_RESUME”, ON_RESUME_VAL),
ON_P0(“ON_P0”, ON_P0_VAL),
ON_P1(“ON_P1”, ON_P1_VAL)
}
class LiveRoomFlowRule(val target: String = "", val type: Type = Type.PRIORITY) {
var priority: Long = DEFAULT_PRIORITY
init {
when (type) {
Type.ABOVE, Type.BELOW, Type.PRIORITY -> Unit
Type.ALL_TOP -> priority = HIGHEST_PRIORITY
}
}
enum class Type {
ABOVE, BELOW, ALL_TOP, PRIORITY
}
companion object {
const val ON_NONE_VAL = 0x000
const val ON_CREATE_VAL = 0x001
const val ON_RESUME_VAL = 0x002
const val ON_P0_VAL = 0x004
const val ON_P1_VAL = 0x008
fun generatePriorityRule(priority: Long): LiveRoomFlowRule {
return LiveRoomFlowRule().apply { this.priority = priority }
}
}
}
//直播间流程控制管理器
interface LiveRoomFlowManager {
@UiThread
fun registerTask(task: LiveRoomFlowTask) //注入任务
@UiThread
fun dispatchTask(roomStatus: LiveRoomStatus) // 执行卡点开始事件
fun destroy() //直播间销毁事件
}
//配置优先级管理中心
object LiveRoomFlowConfig {
const val LOW_PRIORITY = 1000L
const val DEFAULT_PRIORITY = 10000L
const val HIGH_PRIORITY = 1000000L
const val HIGHEST_PRIORITY = Long.MAX_VALUE
//单个卡点优先级单独管理
object OnCreate {
const val INIT_PARAMS = HIGHEST_PRIORITY
const val INIT_IJK = HIGHEST_PRIORITY - 1000L
const val INIT_ITRACKER = HIGHEST_PRIORITY - 2000L
const val INIT_ROOM_VIEW = HIGHEST_PRIORITY - 3000L
const val INIT_VIEWMODEL = HIGHEST_PRIORITY - 4000L
const val INIT_PLAYERFRAGMENT = HIGHEST_PRIORITY - 5000L
const val INIT_CONFIGANDPCU = HIGHEST_PRIORITY - 6000L
}
object OnResume {
const val COMMON = HIGH_PRIORITY
const val HEARTBEAT = HIGH_PRIORITY - 1000L
}
object OnP0 {
//viewModel
const val INIT_P1 = HIGH_PRIORITY
const val INIT_PLAYERDATA = HIGH_PRIORITY - 1000L
//View
const val INIT_PLAY = HIGH_PRIORITY - 2000L
}
object OnP1 {
const val PLAYER_VIEWMODEL = HIGH_PRIORITY
const val USER_VIEWMODEL = HIGH_PRIORITY - 1000L
const val SOCKET_VIEWMODEL = HIGH_PRIORITY - 2000L
const val INTERACTION_VIEWMODEL = HIGH_PRIORITY - 3000L
const val GIFT_VIEWMODEL = HIGH_PRIORITY - 4000L
const val BASIC_VIEWMODEL = HIGH_PRIORITY - 5000L
const val SPPLAYER_VIEWMODEL = HIGH_PRIORITY - 6000L
const val OPERATION_VIEWMODEL = HIGH_PRIORITY - 7000L
const val ANIM_VIEWMODEL = HIGH_PRIORITY - 8000L
const val SKIN_VIEWMODEL = HIGH_PRIORITY - 9000L
const val OPERATION_VIEWMODELV3 = HIGH_PRIORITY - 10000L
const val VOICE_VIEWMODEL = HIGH_PRIORITY - 11000L
const val SUPERCHAT_VIEWMODEL = HIGH_PRIORITY - 12000L
const val LPLSP_VIEWMODEL = HIGH_PRIORITY - 13000L
const val INTERACTIONPANEL_VIEWMODEL = HIGH_PRIORITY - 14000L
const val QUESTION_VIEWMODEL = HIGH_PRIORITY - 15000L
//分发 其他Basic数据
const val DISPATCH_BASIC_DATA = HIGH_PRIORITY - 16000L
const val DISPATCH_ROOM_INFO_COMPLETE = HIGH_PRIORITY - 17000L
}
}
//每个卡点可以挂载N个任务
sealed class LiveRoomFlowTask(val task: () -> Unit) {
abstract val tag: String //标签 别名
abstract val rule: LiveRoomFlowRule //规则 默认按优先级排序
abstract val liveRoomStatus: LiveRoomStatus // 挂载点
open val isSticky: Boolean = false //是否支持粘性消息
}
//数据记录相关,记录相关数据 埋点上报
class LiveRoomFlowTrace(val hashCode: Int) : LiveLogger {
override val logTag: String
get() = "LiveRoomFlowTrace"
private val initTs = SystemClock.elapsedRealtime()
private val reportMap = LinkedHashMap<String, LiveRoomStatusAssembly>()
init {
logDebug { "init onCreate ms: $initTs hashCode: $hashCode" }
}
fun recordRoomStatusStart(liveRoomStatus: LiveRoomStatus, startTs: Long) {
val assembly = reportMap[liveRoomStatus.tag]
?: LiveRoomStatusAssembly(ArrayList(), liveRoomStatus).also {
reportMap[liveRoomStatus.tag] = it
}
assembly.startTs = startTs
assembly.tasks.clear()
}
fun recordTask(liveRoomFlowTask: LiveRoomFlowTask, costTs: Long, exception: String? = null) {
val assembly = reportMap[liveRoomFlowTask.liveRoomStatus.tag]
?: LiveRoomStatusAssembly(ArrayList(), liveRoomFlowTask.liveRoomStatus).also {
reportMap[liveRoomFlowTask.liveRoomStatus.tag] = it
}
assembly.tasks.add(LiveRoomTaskCost(liveRoomFlowTask.tag, costTs, exception))
}
fun recordRoomStatusEnd(liveRoomStatus: LiveRoomStatus, endTs: Long) {
val assembly = reportMap[liveRoomStatus.tag]
?: LiveRoomStatusAssembly(ArrayList(), liveRoomStatus).also {
reportMap[liveRoomStatus.tag] = it
}
assembly.endTs = endTs
assembly.totalCostTs = assembly.endTs - assembly.startTs
}
fun logRoomStatus(roomStatus: LiveRoomStatus) {
reportMap[roomStatus.tag]?.let {
logDebug { JSON.toJSONString(it) }
}
}
fun reportMap() {
HandlerThreads.getHandler(HandlerThreads.THREAD_REPORT).post {
LiveReporter.reportTech(EVENT_ID, generateReportInfo(), sampler = { true }, force = false)
}
}
private fun generateReportInfo(): Map<String, String> {
return HashMap<String, String>().apply {
put("room_flow_info", JSON.toJSONString(reportMap))
}
}
companion object {
const val EVENT_ID = "live.room-flow.info.track"
}
}
class LiveRoomStatusAssembly(val tasks: ArrayList, val liveRoomStatus: LiveRoomStatus, var totalCostTs: Long = 0L, var startTs: Long = 0, var endTs: Long = 0)
data class LiveRoomTaskCost(val tag: String, val costTs: Long, val exception: String? = null)
//管理器实现类,实现内部所有功能
class LiveRoomFlowManagerImpl(private val hashCode: Int) : LiveRoomFlowManager, LiveLogger {
override val logTag: String
get() = "LiveRoomFlowManager"
private val mRoomTrace = LiveRoomFlowTrace(hashCode)
private val mTasks = mutableMapOf<LiveRoomStatus, LinkedList<LiveRoomFlowTask>>()
private val mComparator = Comparator<LiveRoomFlowTask> { o1, o2 -> o2.rule.priority.compareTo(o1.rule.priority) }
private var mCurrentRoomStatus: Int = LiveRoomStatus.ON_NONE.priority
private var isDestroyCalled = false
override fun dispatchTask(roomStatus: LiveRoomStatus) {
if (isDestroyCalled) return
mCurrentRoomStatus = mCurrentRoomStatus or roomStatus.priority
mRoomTrace.recordRoomStatusStart(roomStatus, SystemClock.elapsedRealtime())
mTasks[roomStatus]?.forEach {
if (isDestroyCalled) return
executeTask(it)
}
mRoomTrace.recordRoomStatusEnd(roomStatus, SystemClock.elapsedRealtime())
mRoomTrace.logRoomStatus(roomStatus)
}
override fun registerTask(task: LiveRoomFlowTask) {
if (isDestroyCalled) return
val queue = mTasks[task.liveRoomStatus] ?: LinkedList<LiveRoomFlowTask>().also {
mTasks[task.liveRoomStatus] = it
}
if (queue.contains(task)) {
return
}
setupRuleForTask(task, queue)
queue.add(task)
Collections.sort(queue, mComparator)
if (task.isSticky && hasRoomStatus(task.liveRoomStatus)) {
executeTask(task)
}
}
private fun setupRuleForTask(liveRoomFlowTask: LiveRoomFlowTask, queue: LinkedList<LiveRoomFlowTask>) {
val rule = liveRoomFlowTask.rule
if (rule.type == LiveRoomFlowRule.Type.PRIORITY) return
if (rule.type == LiveRoomFlowRule.Type.ALL_TOP) {
liveRoomFlowTask.rule.priority = HIGHEST_PRIORITY
return
}
val targetTask = getIndexByTag(rule.target, queue) ?: return
if (rule.type == LiveRoomFlowRule.Type.ABOVE) {
liveRoomFlowTask.rule.priority = targetTask.rule.priority + 1
}
if (rule.type == LiveRoomFlowRule.Type.BELOW) {
liveRoomFlowTask.rule.priority = targetTask.rule.priority - 1
}
}
private fun getIndexByTag(target: String, queue: LinkedList<LiveRoomFlowTask>): LiveRoomFlowTask? {
queue.forEachIndexed { _, liveRoomTask ->
if (target == liveRoomTask.tag) {
return liveRoomTask
}
}
return null
}
private fun hasRoomStatus(liveRoomStatus: LiveRoomStatus) = liveRoomStatus.priority and mCurrentRoomStatus == liveRoomStatus.priority
private fun executeTask(flowTask: LiveRoomFlowTask) {
val startTs = SystemClock.elapsedRealtime()
try {
flowTask.task()
val endTs = SystemClock.elapsedRealtime()
mRoomTrace.recordTask(flowTask, endTs - startTs)
} catch (e: Exception) {
val endTs = SystemClock.elapsedRealtime()
mRoomTrace.recordTask(flowTask, endTs - startTs, e.message)
}
}
override fun destroy() {
isDestroyCalled = true
mTasks.clear()
mRoomTrace.reportMap()
}
}
enum class LiveRoomStatus(val tag: String, val priority: Int) {
ON_NONE(“NONE”, ON_NONE_VAL),
ON_CREATE(“ON_CREATE”, ON_CREATE_VAL),
ON_RESUME(“ON_RESUME”, ON_RESUME_VAL),
ON_P0(“ON_P0”, ON_P0_VAL),
ON_P1(“ON_P1”, ON_P1_VAL)
}
class LiveRoomFlowRule(val target: String = "", val type: Type = Type.PRIORITY) {
var priority: Long = DEFAULT_PRIORITY
init {
when (type) {
Type.ABOVE, Type.BELOW, Type.PRIORITY -> Unit
Type.ALL_TOP -> priority = HIGHEST_PRIORITY
}
}
enum class Type {
ABOVE, BELOW, ALL_TOP, PRIORITY
}
companion object {
const val ON_NONE_VAL = 0x000
const val ON_CREATE_VAL = 0x001
const val ON_RESUME_VAL = 0x002
const val ON_P0_VAL = 0x004
const val ON_P1_VAL = 0x008
fun generatePriorityRule(priority: Long): LiveRoomFlowRule {
return LiveRoomFlowRule().apply { this.priority = priority }
}
}
}
(3)核心任务调度机制-接入指南
//首先确定LiveRoomFlowConfig 中确定要挂载的点,目前有 4种
ON_CREATE("ON_CREATE", ON_CREATE_VAL), // ON_CREATE 时机
ON_RESUME("ON_RESUME", ON_RESUME_VAL), // ON_RESUME 时机
ON_P0("ON_P0", ON_P0_VAL), // p0接口请求结束
ON_P1("ON_P1", ON_P1_VAL) //p1接口请求结束
//确认挂载点之后,在确认要执行的顺序优先级,写在固定的Object 下面,数值越大优先级越高
object LiveRoomFlowConfig {
const val LOW_PRIORITY = 1000L
const val DEFAULT_PRIORITY = 10000L
const val HIGH_PRIORITY = 1000000L
const val HIGHEST_PRIORITY = Long.MAX_VALUE
object OnCreate {
const val INIT_PARAMS = HIGHEST_PRIORITY
const val INIT_IJK = HIGHEST_PRIORITY - 1000L
const val INIT_ITRACKER = HIGHEST_PRIORITY - 2000L
const val INIT_ROOM_VIEW = HIGHEST_PRIORITY - 3000L
const val INIT_VIEWMODEL = HIGHEST_PRIORITY - 4000L
const val INIT_PLAYERFRAGMENT = HIGHEST_PRIORITY - 5000L
const val INIT_CONFIGANDPCU = HIGHEST_PRIORITY - 6000L
}
object OnResume {
const val COMMON = HIGH_PRIORITY
const val HEARTBEAT = HIGH_PRIORITY - 1000L
}
object OnP0 {
//viewModel
const val INIT_P1 = HIGH_PRIORITY
const val INIT_PLAYERDATA = HIGH_PRIORITY - 1000L
//View
const val INIT_PLAY = HIGH_PRIORITY - 2000L
}
object OnP1 {
const val PLAYER_VIEWMODEL = HIGH_PRIORITY
const val USER_VIEWMODEL = HIGH_PRIORITY - 1000L
const val SOCKET_VIEWMODEL = HIGH_PRIORITY - 2000L
const val INTERACTION_VIEWMODEL = HIGH_PRIORITY - 3000L
const val GIFT_VIEWMODEL = HIGH_PRIORITY - 4000L
const val BASIC_VIEWMODEL = HIGH_PRIORITY - 5000L
const val SPPLAYER_VIEWMODEL = HIGH_PRIORITY - 6000L
const val OPERATION_VIEWMODEL = HIGH_PRIORITY - 7000L
const val ANIM_VIEWMODEL = HIGH_PRIORITY - 8000L
const val SKIN_VIEWMODEL = HIGH_PRIORITY - 9000L
const val OPERATION_VIEWMODELV3 = HIGH_PRIORITY - 10000L
const val VOICE_VIEWMODEL = HIGH_PRIORITY - 11000L
const val SUPERCHAT_VIEWMODEL = HIGH_PRIORITY - 12000L
const val LPLSP_VIEWMODEL = HIGH_PRIORITY - 13000L
const val INTERACTIONPANEL_VIEWMODEL = HIGH_PRIORITY - 14000L
const val QUESTION_VIEWMODEL = HIGH_PRIORITY - 15000L
//分发 其他Basic数据
const val DISPATCH_BASIC_DATA = HIGH_PRIORITY - 16000L
const val DISPATCH_ROOM_INFO_COMPLETE = HIGH_PRIORITY - 17000L
}
}
//创建一个Task 继承自 AbstractLiveRoomFlowTask类型, 或者使用快捷创建方式
sealed class LiveRoomFlowTask(val task: () -> Unit) {
abstract val tag: String //tag标记
abstract val rule: LiveRoomFlowRule //规则,推荐优先级处理方式
abstract val liveRoomStatus: LiveRoomStatus //挂载点
open val isSticky: Boolean = false //是否支持粘性
}
internal abstract class AbstractLiveRoomFlowTask(val status: LiveRoomStatus, task: () -> Unit) : LiveRoomFlowTask(task) {
override val liveRoomStatus: LiveRoomStatus
get() = status
}
fun createFlowTask(tag: String, status: LiveRoomStatus, task: () -> Unit): LiveRoomFlowTask {
return createFlowTask(tag, status, LiveRoomFlowConfig.DEFAULT_PRIORITY, false, task)
}
fun createFlowTask(tag: String, status: LiveRoomStatus, priority: Long, task: () -> Unit): LiveRoomFlowTask {
return createFlowTask(tag, status, priority, false, task)
}
fun createFlowTask(tag: String, status: LiveRoomStatus, priority: Long, isSticky: Boolean, task: () -> Unit): LiveRoomFlowTask {
return object : AbstractLiveRoomFlowTask(status, task) {
override val tag: String
get() = tag
override val rule: LiveRoomFlowRule
get() = LiveRoomFlowRule.generatePriorityRule(priority)
override val isSticky: Boolean
get() = isSticky
}
}
//最后通过registerTask方法注入task
fun LiveRoomFlowManager.registerOnCreateTask(tag: String, priority: Long, task: () -> Unit) {
registerTask(createFlowTask(tag, LiveRoomStatus.ON_CREATE, priority, task))
}
fun LiveRoomFlowManager.registerOnResumeTask(tag: String, priority: Long, task: () -> Unit) {
registerTask(createFlowTask(tag, LiveRoomStatus.ON_RESUME, priority, task))
}
fun LiveRoomFlowManager.registerOnP0Task(tag: String, priority: Long, task: () -> Unit) {
registerTask(createFlowTask(tag, LiveRoomStatus.ON_P0, priority, task))
}
fun LiveRoomFlowManager.registerOnP1Task(tag: String, priority: Long, task: () -> Unit) {
registerTask(createFlowTask(tag, LiveRoomStatus.ON_P1, priority, task))
}
(三)直播间数据和通道能力建设
(1)BiDataReporter-业务埋点能力
随着直播间业务埋点需求和复杂度不断增加,统一建设埋点能力和埋点数据规范成为我们必要的工作,此文档中核心描述直播间业务埋点能力建设。
现状
目前直播技术已建设埋点数据能力,直播间架构未对此进行合理设计,较多业务埋点数据存在公用埋点,且公共埋点无抽象和封装,简单依赖Kotlin扩展解决问题,导致直播间架构层次依赖关系复杂。
目标
建设直播间埋点数据能力与组装数据规范,拆解清楚直播间架构层级依赖关系。
埋点能力建设
定义直播间业务埋点接口LiveRoomReporter,支持如下:
- 业务点击与曝光埋点;
- 技术事件埋点;
- 系统事件埋点;
- 自定义类型埋点;
LiveRoomReporter接口实现类LiveRoomReporterImpl生命周期绑定LiveRoomContext,且内部依赖DataStoreManager,以便完成公共参数默认组装,组装方式参见 埋点数据组装规范。
LiveRoomReporter的服务对象:
- Domain;
- ViewModel;
- 可以获得RoomContext的对象;
代码样例
|
|
(2)BiDataReporter-业务埋点规划
埋点通道规范
总结如下:
- 直播间业务统一使用LIveRoomReporter接口完成业务埋点;
- 非直播间使用LiveReporter单例完成业务埋点。
埋点数据规范
总结如下:
- 直播间业务使用LiveRoomReportData完成数据组装;
- 非直播间业务使用LiveReportData完成数据组装。
(3)DataStore - 数据隔离和访问能力
背景
1、直播间在接入核心任务调度机制之前,很多核心业务数据访问时间需要在数据的获取后
2、为了解决上述问题使用了,许多业务数据被迫使用了LiveData的声明方式,用来通知数据已获取
3、后续的许多业务场景沿用了以上设计,但大部分只读数据并不需要通知的能力
现有数据来源
1、如上图所示,进入直播间后会按顺序经过以下阶段(后续可继续拓展):进入直播间 → P0接口请求成功 → P1接口请求成功 → 用户接口请求成功。
2、在设计DataStore之前,直播间数据是没有分层的(baseData、P0data、P1data、UserData),对各个阶段访问的数据也没有设置相应的限制和约束,直播间所有数据不管有没有获取到,都可以在一个大对象里通过属性访问到。
3、这样会出现如下问题:上一个阶段的任务在需要访问下一个阶段任务对应的数据时,下一个阶段对应的数据尚未被赋值,会导致该访问异常。
4、并且开发人员因为能够拿到该属性,会以为是有值可用的,往往在线上出了问题才会被发现使用的时机不对。例如:在进入直播间阶段,会访问p1接口中的X数据,然而,该数据尚未被赋值,所以该访问操作应当在p1接口请求成功后在执行,但是因为可以访问到该数据对应的属性,开发人员无法感知自己的调用时机是错误的。
改造流程
1、将LiveRoomData中有关进房间接口的数据迁移至LiveRoomDataStore中
2、去掉房间接口数据中LiveData的通知方式,改为流程控制中task注册方式
3、各task中任务只能使用task返回的该阶段数据
4、针对房间复杂业务数据做特殊处理
5、完善DataStore - 数据隔离和访问规范(下一节内容)
(4)DataStore - 数据隔离和访问规范
DataStore概览
业务中常用的属性和方法:
LiveRoomDataStore:播间核心数据源,持有并管理各阶段的数据
- write(key: Key, value: Any) : 当有数据在接口下发后有修改操作,需要提供相关的key,并在write方法里注册
LiveRoomDataStoreManager:DataStore的管理类,提供各种数据相关的接口给业务方使用
- baseData:提供安全的baseData,如果在onCreate前调用,debug模式下回抛出异常,release模式会提供保底数据并上报到星际平台
- finalData:提供安全的finalData,如果在p1接口成功前调用,debug模式下回抛出异常,release模式会提供保底数据并上报到星际平台
- getDataByStatus():提供安全的各阶段数据获取方法,用于获取各阶段数据
- writeDataStoreValueByKey(key: Key, value: Any):提供安全的数据修改方法,仅在数据所属阶段后修改数据
ILiveRoomBaseData:房间基础数据集,包含房间外传入数据和进直播间生成的数据,feed模式下部分数据由推荐列表接口提供
LiveRoomP0Data:P0接口成功后提供的数据集,包含BaseData内容
LiveRoomP1Data:P1接口成功后提供的数据集,包含P0Data和BaseData内容
数据分层
在DataStroe机制中,对直播间数据进行了分层
如下图所示,进入直播间后会经过“进入直播间 → P0接口请求成功 → P1接口请求成功 → 用户接口请求成功”四个阶段(后续可继续拓展),各阶段数据的定义如下。
baseData:生成直播间所需要的基础信息,非接口返回而是直播间外带入的,包括房间id、房间跳转来源等;
P0Data:直播间的功能性信息,由进入直播间调用的第一个接口返回的数据生成;
P1Data:直播间的业务信息,各业务初始化所需数据集合;
UserData:用户基本信息,只有登录后才会存在
1、在上述阶段中会获取或创建不同的直播间数据,处在下个阶段的任务可以访问上一个阶段的数据,反之因为数据还不存在可能会导致业务异常。
2、DataStroe机制的建设解决了这个问题,业务在通过DataStroe访问各个阶段数据时,会判断当前所在的阶段,如果业务所处阶段已存在数据则可以访问,反之则无法访问。
基于核心任务调度机制的挂载点的业务,使用Task提供的数据集
@UiThread
fun registerOnP0Task(tag: String, priority: Long, task: (LiveRoomP0Data) -> Unit) {
registerTask(FlowTaskFactory.create(tag, LiveRoomStatus.ON_P0, priority) {
dataStoreManager.getDataByStatus<LiveRoomP0Data>()?.let { task(it) }
})
}
@UiThread
fun registerOnP1Task(tag: String, priority: Long, task: (LiveRoomP1Data) -> Unit) {
registerTask(FlowTaskFactory.create(tag, LiveRoomStatus.ON_P1, priority) {
dataStoreManager.getDataByStatus<LiveRoomP1Data>()?.let { task(it) }
})
}
@UiThread
fun registerOnUserInfoTask(tag: String, priority: Long, task: (BiliLiveRoomUserInfo) -> Unit) {
registerTask(FlowTaskFactory.create(tag, LiveRoomStatus.ON_USERINFO, priority) {
dataStoreManager.getDataByStatus<BiliLiveRoomUserInfo>()?.let { task(it) }
})
}
调用时it来访问数据
(5)RxBus - Event通信能力
使用现状
1、直播间数据通道不统一;
2、目前通道事件管理混乱,无法统计;
3、发送事件时未校验该事件是否注册;
解决方案
1、定义基类Event,收拢事件;
2、记录所有注册的事件和线程类型,post没有注册事件时在debug抛出异常,线上接入数据告警通道;
3、从LiveRoomData迁移到LiveRoomContext中;
class LiveRoomContext(roomData: LiveRoomData, val liveRoomFlowManager: LiveRoomFlowManager) {
// 用于主线程通信的RxData
val mainRxBus by lazy(LazyThreadSafetyMode.NONE) {
RxBus.newInstance()
}
// 用于子线程通信的RxData
val serializedRxBus by lazy {
RxBus.newInstance()
}
// 事件注册情况
private val subscribedEventMap = mutableMapOf<Class<out Event>, ThreadType>()
fun post(event: Event, threadType: ThreadType = ThreadType.MAIN) {
// 检测事件注册情况
if (subscribedEventMap[event::class.java] != threadType) {
if (BuildConfig.DEBUG) {
throw IllegalArgumentException("RxBus has no event subscribed in $threadType")
}
logError { "RxBus has no event subscribed in $threadType" }
}
when (threadType) {
ThreadType.MAIN -> postMainEvent(event)
ThreadType.SERIALIZED -> serializedRxBus.post(event)
}
}
fun <T : Event> subscribe(clazz: Class<T>, action: (T) -> Unit, threadType: ThreadType = ThreadType.MAIN) {
subscribedEventMap[clazz] = threadType
when (threadType) {
ThreadType.MAIN -> mainRxBus.registerNoinline(clazz).subscribe({
...
})
ThreadType.SERIALIZED -> serializedRxBus.registerNoinline(clazz)
.onBackpressureDrop {
...
}
}
fun cleanUp() {
mainRxBus.cleanUp()
serializedRxBus.cleanUp()
}
...
}
interface Event
enum class ThreadType {
MAIN, SERIALIZED
}
方便使用这几个ViewModel的扩展方法还是保留
@MainThread
fun LiveRoomBaseViewModel.postMainEvent(event: Event) {
roomContext.post(event)
}
inline fun <reified T : Event> LiveRoomBaseViewModel.subscribeMainEvent(crossinline action: (T) -> Unit) {
roomContext.subscribe(action)
}
@WorkerThread
fun LiveRoomBaseViewModel.postBackgroundEvent(event: Event) {
roomContext.post(event, ThreadType.SERIALIZED)
}
inline fun <reified T : Event> LiveRoomBaseViewModel.subscribeBackgroundEvent(crossinline action: (T) -> Unit) {
roomContext.subscribe(action, ThreadType.SERIALIZED)
}
(6)RxBus - Event通信规范
事件定义
直播间所有事件都需要实现Event接口;
interface Event
class LiveRoomLoginEvent(val requestCode: Int) : Event
事件收拢路径
直播间内所有事件都需定义在如下目录中;
andruid/app/bililive/bililiveLiveVideoPlayer/src/main/kotlin/com/bilibili/bililive/videoliveplayer/ui/roomv3/base/events
事件分类
事件分类bussiness和common两个大类。然后再按具体业务划分到文件中。
举例:
events/bussiness/PK.kt
//pk 倒计时开始
class PKPreEvent(val pkID: Long, val pkStatus: Int, val pkPreEntity: PKPreEntity) : Event
//pk 开始
class PKStartEvent(val pkID: Long, val pkStatus: Int, val pkStartEntity: PKStartEntity) : Event
//pk 结束
class PKEndEvent(val pkID: Long, val pkStatus: Int, val pkEndEntity: PKEndEntity) : Event
//pk 结算
class PKSettleEvent(val pkID: Long, val pkStatus: Int, val pkSettleEntity: PKSettleEntity) : Event
(四)动态层级管理器
(1)问题
当前直播间所有View布局,在初始化的时候统一加载到内存里,存在很多资源浪费,同时影响首帧,首屏耗时。
直播间当前布局都在一个xml文件中,业务之间耦合,层级梳理不清晰。
(2)目标
- 支持层级动态,懒加载方式,节约内存压力,减少初始化耗时。
- 层级梳理,动态生成层级关系快照,基于当前层级关系,方便后续业务迭代
- 对业务优化,简化业务接入方式,开发业务无需考虑内部实现
- 对表现层重新划分,按照区域划分方式,方便查找相关业务
(3)过程
基于竖版直播间改造,接入层级管理系统,同时梳理层级关系。加载流程
(4)代码设计
新增分区
- 新增区域时需要在 HierarchyScope 添加新区名
- 每个分区都有一个 HierarchyViewContainer 用来添加层级视图,一个 HierarchyAdapter 用来管理视图的配置、添加和移除
- 层级视图里业务关心的生命周期和返回键拦截等需要 HierarchyViewContainer 来分发
class LiveRoomActivityV3 {
private var mHierarchyContainer: HierarchyViewContainer? = null
private var mHierarchyAdapter: HierarchyAdapter = HierarchyAdapter(HierarchyScope.GLOBAL)
override fun onCreate(savedInstanceState: Bundle?) {
//...
mHierarchyContainer = HierarchyViewContainer(this)
mHierarchyContainer?.setAdapter(mHierarchyAdapter)
//...
}
override fun onResume() {
super.onResume()
mHierarchyContainer.onResumeView(this)
}
override fun onPause() {
super.onPause()
mHierarchyContainer.onPauseView(this)
}
override fun onDestroy() {
super.onDestroy()
mHierarchyContainer.onDestroyView(this)
}
override fun onBackPressed() {
if (mHierarchyContainer.onBackPressed()) {
return
}
super.onBackPressed()
}
}
新增层级视图
层级视图分为两类
- 区域初始化时通过配置构建,添加顺序决定来该视图的层级关系,继承自
HierarchyView
- 通过
HierarchyAdapter
动态添加,继承自DialogHierarchyView
HierarchyViewHolder
- 每个
HierarchyView
都有一个对应的HierarchyViewHolder
用于提供构建视图的能力和层级的信息 HierarchyAdapter
不会暴露对层级视图的直接操作,添加、删除等操作的对象都是HierarchyViewHolder
/**层级信息:
- tag : 层级的名称
- id : 层级的唯一标示,格式为tag_index,区域内新每添加一个相同tag的层级实例时index自增1
*/
class HierarchyAViewHolder : HierarchyViewHolder("layerA") {
override fun createHierarchyView(context: Context, adapter: HierarchyAdapter): HierarchyView {
return HierarchyAView(tag, adapter, context)
}
}
class HierarchyAView(tag: String, adapter: HierarchyAdapter, context: Context) : HierarchyView(tag, adapter, context) {
override fun onCreateView(context: Context, id: String) {
super.onCreateView(context, id)
inflate(context, R.layout.layout_layer_a, this)
}
}
HierarchyAdapter
外部对层级的操作均使用 HierarchyAdapter 进行
setViewHolderData 方法用来初始配置区域的初始层级视图
hierarchyAdapter.setViewHolderData(
listOf(
HierarchyAViewHolder(),
HierarchyBViewHolder(),
HierarchyCViewHolder(),
HierarchyDViewHolder()
)
)
这里建议使用方建立一个 HierarchyScopeDefine 用来管理区域配置信息
hierarchyAdapter.setViewHolderData(
HierarchyScopeDefine.Global().getHierarchyDefine()
)
class HierarchyScopeDefine {
interface ScopeDefine{
fun getHierarchyDefine(): List<HierarchyViewHolder>
}
class Global : ScopeDefine{
override fun getHierarchyDefine(): List<HierarchyViewHolder> {
return listOf(
HierarchyAViewHolder(),
HierarchyBViewHolder(),
HierarchyCViewHolder(),
HierarchyDViewHolder()
)
}
}
}
//createView 方法用来通过上述配置构建初始层级视图
hierarchyAdapter.createView(this)
//addHierarchy 方法用来根据规则添加视图
hierarchyAdapter.addHierarchy(
context,
DialogBHierarchyViewHolder(),
HierarchyRule("dialogBottom", HierarchyRule.Type.ABOVE))
hierarchyAdapter.addHierarchy(
context,
DialogBHierarchyViewHolder(),
HierarchyRule(type = HierarchyRule.Type.ALL_TOP))
removeHierarchyByTag
方法用来移除视图,移除所有符合tag类型的层级removeHierarchyById
方法用来移除视图,根据id移除层级snapshotDataHierarchy
方法用于打印adapter中数据的快照
(5)创建动态层级视图
新view需要继承LiveRoomBaseDynamicInflateView,并通过重写部分属性和方法来定制化实体view在层级容器中的位置:
class LiveRoomDemoView(activity: LiveRoomActivityV3, liveHierarchyManager: LiveHierarchyManager, lifecycleOwner: LifecycleOwner) : LiveRoomBaseDynamicInflateView(activity, liveHierarchyManager, lifecycleOwner) {
override val layoutRes: Int
get() = TODO("需要加载的XML")
override val tag: String
get() = TODO("TAG")
override val priority: LiveRoomViewPriority
get() = TODO("优先级")
override val defaultLayoutParams: LiveRoomViewLayoutParams
get() = TODO("层级布局")
override val supportScreenMode: Int
get() = TODO("支持的屏幕类型")
/**
* 默认View在 BUSINESS层 如果要改变所属的容器层,需要重写此方法
*/
override fun getScope() = HierarchyScope.BUSINESS
override fun onViewCreate(view: View) {
TODO("实体view被加载后的逻辑")
}
override fun onScreenModeChange(mode: PlayerScreenMode) {
TODO("屏幕方向变化后的逻辑")
}
override fun onDestroyIfInflate() {
TODO("如果实体view被加载,处理onDestroy的逻辑")
}
}
对布局和控件的管理
1、每个虚拟view会管理一个属于自己的实体view,通过重写layoutRes属性来提供业务的布局文件
2、使用bindInflateView或者bindInflateOptionalView方法获取布局中的控件
3、在onViewCreate里对控件进行UI操作
4、对于某些只有在实体view加载时才处理的销毁逻辑,在onDestroyIfInflate里处理
实体view被加载的时机
1、只有在调用了inflateView()方法,实体view才会被加载
2、inflateView()一般不需要业务手动调用,大多数场景是由某个事件触发的,具体见下面的事件驱动型订阅LiveData
3、如果一个view是刚进入直播间就需要加载的可以手动调用inflateView()
4、可以通过isInflated字段判断实体view是否已经加载
定义view支持的屏幕模式和各屏幕的适配
1、直播间有三种屏幕模式,重写supportScreenMode来定义该view在哪些屏幕下可见,默认支持三种屏幕模式
2、在屏幕模式发生变化时的逻辑,可以在onScreenModeChange里处理
3、supportScreenMode决定了priority和defaultLayoutParams的设定,每种屏幕模式都有对应的优先级和布局样式
4、所有的层级优先级可以在LiveRoomHierarchyConfig里查看,图例见0015. Android 竖屏直播间改版三期-三屏View复用方案
例:
override val priority: LiveRoomViewPriority = LiveRoomViewPriority(
thumbPriority = LiveRoomHierarchyConfig.ThumbBusiness.BUSINESS_PK,
verticalPriority = LiveRoomHierarchyConfig.VerticalBusiness.BUSINESS_PK,
horizontalPriority = LiveRoomHierarchyConfig.HorizontalBusiness.BUSINESS_PK
)
override val defaultLayoutParams = LiveRoomViewLayoutParams(
verticalLayoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT),
thumbLayoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT),
horizontalLayoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
LiveData的订阅方法
事件驱动型订阅
1、当前直播间内的大多数业务,在收到LiveData变化的通知后需要对实体view进行操作,换言之这部分操作需要在实体view加载好后执行
2、使用observerForInflateView订阅LiveData时,如果实体view没有加载,会先执行view的加载来保证后续的操作
3、对应的observerForever方法为observerForeverForInflateView
业务感知型订阅
1、另一种常见的订阅场景是,如果当前实体view已经被加载就执行后续操作
2、使用observerForActionIfInflated订阅LiveData时,会先检查当前的view是否加载,如果已经加载才会执行后续操作
3、对应的observerForever方法为observerForeverForActionIfInflated
特殊情况的处理
1、部分订阅场景不与view的加载有关,可以使用observerForNormal简化调用链
2、同时某些不是通过LiveData通知的场景需要加载view时,可以使用inflateView反复手动加载view
(五)直播间业务数据改造
直播间中的数据
1、数据的分类
1.1、环境数据
1、数据特点:多读多写,不属于任何业务,经常用来描述业务所处环境,例如:是否登录、屏幕状态、开关播状态、房间类型等
2、数据来源:globalDataService和roomDataService,业务方直接使用各自的ViewModel即可访问环境数据的最新值
1.2、埋点数据
1、数据特点:仅用于埋点传参,聚合类数据,公参基本由环境数据提供,不同埋点会带上不同业务独有的数据
2、数据来源:与埋点的时机和位置有关,业务方在埋点的地方获取环境数据和自己的业务数据,合成埋点所需要的数据集合,通常构建一个Map实例传递
1.3、业务数据
1、数据特点:每个数据对应唯一的业务方,由各个业务方维护,部分数据需要通过接口共享(提供给其他业务)
2、数据来源:房间聚合接口、socket广播、业务接口、用户动作等
2、直播间内数据的来源和流向
3、业务的启动和数据维护
DataStore的背景
1、直播间的初始数据由Intent、P0P1接口提供,业务的启动逻辑往往依赖这些数据以及这些接口返回的时机
2、原先DataStore的设计,是为了配合核心任务调度机制而存在的,保证了业务逻辑在某个任务中访问数据的安全性
3、这些初始数据大部分在初始化阶段后就不再使用,但还有一部分数据在业务运行阶段还会被修改和访问,为了提供非Task内访问数据的能力制定了复杂的DataStore - 数据隔离和访问规范
4、这些复杂的交互的根本原因是DataStore作为了数据的承载方:因为DataStore提供了初始值,所有用到这个值的业务都会向DataStore获取这个值,因此这个值在后续改变时只需要更新DataStore维护的这个值,所有用到这个值的业务再次向向DataStore获取这个值时,拿到的就是最新的值了
数据真正的承载方
1、数据的承载方应该与数据的描述有关,这个数据描述的是哪块业务,这块业务就应该承载这个数据,并负责后续的更新和向外提供,初始数据仅仅是这个数据的第一个赋值
2、如果某个数据描述的是整个直播间的基础状态信息,各个业务都需要使用该信息做逻辑判断,那么这个数据将不属于任何业务,他的承载方应该是房间的基础数据服务
3、将原先DataStore里的数据迁移到各个业务或基础数据服务中后,原本向DataStore获取这个值的地方将直接向该数据的承载方获取该值
业务服务的改变
-
ILiveRoomBizService需要新的数据管理方式
- 新增一个businessData,用来描述整个业务
- 新增各个时机的数据更新方法用来更新businessData的数据
- 新增startUp()方法作为服务启动的初始时机
- 新增优先级机制,可以决定分各个服务发数据和startUp()执行的先后顺序
-
LiveAppServiceManager提供获取全部房间业务服务的方法
- 在TaskManger的各个数据时机里进行数据分发,遍历serviceList调用响应时机的方法
- 在P1数据分发结束后,遍历serviceList调用startUp()方法,在或User数据分发结束结束后分发onLoginComplete()方法
4、不同业务交互场景下的数据流向分析
业务场景和数据的关系
- 一个业务的主体是由数据和逻辑处理组成的
- 简单的业务场景下,处理逻辑用到的数据一般都是归属于该业务自己的
- 实际情况中一个业务的逻辑往往会关心其他业务的数据,从而产生各种交互
两个业务间的交互场景
- 两个业务间最常见的交互:获取其他业务的数据、改变其他业务的数据
- 获取其他业务数据场景的设计相对比较统一,一般由数据维护方提供获取的能力
- 改变其他业务数据的场景就比较多元化,需要根据不同的业务场景制定:
- 当外部需要感知具体改动的数据是什么时,数据维护方可以提供更改某数据的能力供外部调用
- 当一个业务在某个时机发生的某个变化有多个其他业务需要改变时,往往会描述为该业务发出了一个改变的通知,关心这个通知的其他业务接到通知后对维护的数据执行改变
两个业务以上的交互场景
- 获取其他业务数据场景的设计与两个业务的设计相同,不在赘述
- 关于在加入第三个业务时,该业务对于已有的数据流向来说,是事件的生产方还是消费方决定这次通知应该如何描述:
- 如果新增的业务后有多个生产方,事件应该定义在消费事件的业务里,描述为消费方需要响应的事件
- 如果新增的业务后有多个消费方,事件应该定义在生产事件的业务里,描述为生产方产生的事件
(六)直播业务改造整理
LiveRoomActivityV3逻辑改造
问题:
1、Activity的上千行代码中除了必要的View交互外大致分为三部分:
- 直播间加载流程OnCreate、OnResume、OnReset任务的处理
- P0接口加载成功、失败(包含加密)逻辑的处理
- 播放器相关逻辑
2、部分功能的管理者由Activity持有,导致VerticalView滑动流程需要反向调用Activity中的方法
3、落地直播间流程与滑动重置直播间Item流程统一出口处理导致逻辑不清晰
业务分层
直播间引入上下滑功能后,现有业务没有分层设计,导致部分特殊生命周期业务组件的流程处理逻辑不清晰,拆分后如图:
1、绿色部分为房间维度业务,随直播间上下划出屏幕而重建
2、蓝色部分为滑动直播间中的滑动列表业务,该部分不随上下滑重建,但会随后续的直播间大刷重建
3、黄色部分为进直播间后就存在的业务,与Activity生命周期同步,大刷时仍需保持不变,随Activity的销毁而销毁
业务的组件化
目标:每一个模块都有自己独立的逻辑和UI,模块之间完全解耦
1、模块化的数据如何存储:数据需要分为各业务独自持有独自处理的部分(外部不可见)和共享出去的部分(外部可见)
2、不同业务间通讯和交互:各业务间不应该感知其他业务的存在,对于模块而言只有要做什么,各模块的数据变化的交互应该转换为各种输入和输出的行动指令
3、模块每一层处理该层的输入和输出:
- View:
- 输入:监听LiveData的数据变化、用户输入的UI事件
- 输出:调用ViewModel提供的功能接口
- ViewModel:
- 输入:监听持有的Service提供的callBack数据
- 输出:调用持有的Service提供的功能接口
- Service:
- 输入:Http的响应、监听Socket的广播、其他业务的数据变化
- 输出:Http的请求、共享数据变化(其他业务需要的数据)
原文地址:https://blog.csdn.net/m0_74837900/article/details/144036346
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!