其他
骚操作玩这么花的吗?基于Activity实现行为录制与回放!
本文作者
作者:Newki
链接:
https://juejin.cn/post/7330104253825646601
本文由作者授权发布。
那么话不多说,Let's go
public class EventState {
public MotionEvent event; //事件
public long time; //开始录制到该事件发生的时间
}
/**
* 以Activity为单位,以队列的形式存储MotionEvent
*/
public class ActEventStates {
/**
* 存储元素为一个队列,存放一个Act中的操作状态。如果有多个Act,则是双重队列
*/
public static Queue<Queue<EventState>> eventStates = new LinkedList<>();
public static boolean isRecord = false; //是否在录制
public static boolean isPlay = false; //是否在播放
}
首先我们只需要回放一次,如果想回放多次可以用持久化存储,对于已经回放过的事件我们不希望还存在内存中,特别是后期做多 Activity 之间的跳转之后的回放,如果之前的事件还存在内存中会有重复回放的问题,而用 List 去手动管理没有 Queue 方便。
//开启录制
fun startRecord() {
//如果是录制状态
if (ActEventStates.isRecord) {
ActEventStates.isPlay = false
//初始化队列,对应一个Act是一个队列
activityEvents = LinkedList()
// Act录制事件的开始时间
startTime = System.currentTimeMillis()
//保存到内存中
ActEventStates.eventStates.add(activityEvents)
}
}
//停止录制
fun stopRecord() {
val state = EventState()
state.event = null
state.time = System.currentTimeMillis() - startTime
activityEvents?.add(state)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
//只有在录制状态下才会保存事件并添加到队列中
if (ActEventStates.isRecord && activityEvents != null) {
//不要直接存原始的 MotionEvent,因为用过就回收的,之前我们是通过自定义 Event 来做的,这里简单一点直接重新伪造一次其实更方便
val obtain = MotionEvent.obtain(ev)
//初始化自己的 EventState 用于保存当前事件对象
val state = EventState()
//赋值当前事件,用伪造过的事件
state.event = obtain
//赋值当前事件发生的时间
state.time = System.currentTimeMillis() - startTime
//把每一次事件 EventState 对象添加到队列中
activityEvents?.add(state)
}
return super.dispatchTouchEvent(ev)
}
//回放录制
fun playRecord() {
//如果是播放状态
if (ActEventStates.isPlay) {
ActEventStates.isRecord = false
//延时1秒开始播放
handler.postDelayed({
Thread {
if (!ActEventStates.eventStates.isEmpty()) {
//遍历每一个Act的事件,支持多个Act的录制与回放
val pop = ActEventStates.eventStates.remove()
while (!pop.isEmpty()) {
val state = pop.remove()
//根据事件的时间顺序播放
handler.postDelayed({
if (state.event == null) {
YYLogUtils.w("没了,回放录制完成")
} else {
dispatchTouchEvent(state.event)
}
}, state.time)
}
}
}.start()
}, 1000)
}
}
startRecode.click {
ActEventStates.isRecord = true
toast("开始录制")
startRecord()
}
endRecode.click {
ActEventStates.isRecord = false
toast("停止录制")
stopRecord()
}
//点击回放
btnReplay.click {
ActEventStates.isPlay = true
toast("回放录制")
playRecord()
}
单独的 Activity 上录制与回放是可以了,但是我们的应用又不是 Compose 或 Flutter,我们大部分项目还是多 Activity 的,如何实现多 Activity 跳转之后的录制与回放才是真正的问题。
abstract class BaseActivity<VM : BaseViewModel> : AbsActivity() {
...
// ================== 事件录制 ======================
var handler = Handler(Looper.getMainLooper())
/**
* 存放当前activity中的事件
*/
private var activityEvents: Queue<EventState>? = null
/**
* 当前activity可见之后的时间点,每次 onResume 之后都创建一个新的队列,同时也赋值新的statetime
*/
private var startTime: Long = 0
override fun onResume() {
super.onResume()
startRecord() //尝试录制
playRecord() //尝试回放
}
//开启录制
protected fun startRecord() {
//如果是录制状态
if (ActEventStates.isRecord) {
ActEventStates.isPlay = false
//初始化队列,对应一个Act是一个队列
activityEvents = LinkedList()
// Act录制事件的开始时间
startTime = System.currentTimeMillis()
//保存到内存中
ActEventStates.eventStates.add(activityEvents)
}
}
//停止录制
protected fun stopRecord() {
val state = EventState()
state.event = null
state.time = System.currentTimeMillis() - startTime
activityEvents?.add(state)
}
override fun onBackPressed() {
val state = EventState()
state.event = null
state.isBackPress = true
state.time = System.currentTimeMillis() - startTime
activityEvents?.add(state)
super.onBackPressed()
}
//回放录制
protected fun playRecord() {
//如果是播放状态
if (ActEventStates.isPlay) {
ActEventStates.isRecord = false
//延时1秒开始播放
handler.postDelayed({
Thread {
if (!ActEventStates.eventStates.isEmpty()) {
//遍历每一个Act的事件,支持多个Act的录制与回放
val pop = ActEventStates.eventStates.remove()
while (!pop.isEmpty()) {
val state = pop.remove()
//根据事件的时间顺序播放
handler.postDelayed({
if (state.event == null) {
if (state.isBackPress) {
YYLogUtils.w("手动调用系统返回按键")
onBackPressed() //手动调用系统返回按键
} else {
YYLogUtils.w("没了,回放录制完成")
}
} else {
dispatchTouchEvent(state.event)
}
}, state.time)
}
}
}.start()
}, 1000)
}
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
//只有在录制状态下才会保存事件并添加到队列中
if (ActEventStates.isRecord && activityEvents != null) {
//不要直接存原始的 MotionEvent,因为用过就回收的,之前我们是通过自定义 Event 来做的,这里简单一点直接重新伪造一次其实更方便
val obtain = MotionEvent.obtain(ev)
//初始化自己的 EventState 用于保存当前事件对象
val state = EventState()
//赋值当前事件,用伪造过的事件
state.event = obtain
//赋值当前事件发生的时间
state.time = System.currentTimeMillis() - startTime
//把每一次事件 EventState 对象添加到队列中
activityEvents?.add(state)
}
return super.dispatchTouchEvent(ev)
}
}
public class EventState {
public boolean isBackPress;
public MotionEvent event; //事件
public long time; //开始录制到该事件发生的时间
}
startRecode.click {
ActEventStates.isRecord = true
toast("开始录制")
startRecord()
}
endRecode.click {
ActEventStates.isRecord = false
toast("停止录制")
stopRecord()
}
//点击回放
btnReplay.click {
ActEventStates.isPlay = true
toast("回放录制")
playRecord()
}
btnJump1.click {
TemperatureViewActivity.startInstance()
}
btnJump2.click {
ViewGroup9Activity.startInstance()
}
如果想回放多次,则需要在停止录制的时候把事件保存到本地,如何保存对象到本地?和前文一样的思路,可以用Json,可以压缩,可以加密,甚至可以自定义数据格式与解析,这一个步骤就无需我多说了吧。
言归正传,关于本文的内容如果想查看源码可以点击这里 【传送门】。你也可以关注我的这个Kotlin项目,我有时间都会持续更新。
https://gitee.com/newki123456/Kotlin-Room
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!