前介
花了将近 2
天的时间,总算是把所有功能写完了,说实话,当时认为很简单,应该话费不了多长时间,但是最后发现越写逻辑越多。不过总算是完成了,看看最终的效果吧!
代码使用了,我开发的
Andorid
资源优化插件,非常还用了,大家可以试试 安卓资源瘦身丶混淆丶去重插件。
确定目标
开始干活
当时定完目标,感觉难点都一一攻破了,没想到开发过程中这点逻辑还挺复杂的,接下来听我一一道来。
集成机器人
集成机器人方面,都是看阅读官方文档即可。
图灵机器人 没啥好说的就是一个简单的 Http
请求,我是使用 Retrofit + OkHttp3 + Kotlin协程
完成 Http
网络请求的(Retrofit 2.6.0
内置支持协程啦,可以和 RxJava
说拜拜了)。
/**
* 图灵机器人API
*/
object TuLingMsgRetrofit : BaseRtrofit("http://www.tuling123.com") {
val api = retrofit.create(TuLingMsgApi::class.java)
}
data class TuLingMsg(val key: String, val info: String)
interface TuLingMsgApi {
@POST("/openapi/api")
suspend fun getMsg(@Body msg: TuLingMsg): TuLingResult
}
第四范式客服机器人 和 图灵机器人 的实现方式不一样,它提供的方式本质是 WebSocket
。但是它设计上存在一个缺陷,就是机器反馈每条消息与我发送的消息没有一个一一对应关系。
举个例子,A
用户和 B
用户同时和我对话,我肯定只连接一个 WebSocket
,A
说 你好
,同时 B
说 在吗
,机器人回馈的 2
条结果,没法区分是给 A
还是 B
。
回头想想,可能 第四范式客服机器人 设计的 1
个连接,只能对应一个客户吧。
最终我也没有找到比较完美的解决办法,只能通过一个队列来管理消息回馈了(或多或少还是存在问题)。
/**
* 第四范式自动回复机器人
*/
object PDbootManager : BotLibClient.MessageListener, BotLibClient.ConnectionListener {
var classLoader: ClassLoader? = null
/**
* 存放消息的队列
*/
val msgTask = ConcurrentLinkedQueue<String>()
override fun onConnectionStateChanged(state: Int) {
when (state) {
BotKitClient.ConnectionIdel -> log("连接断开")
BotKitClient.ConnectionConnecting -> log("正在连接...")
BotKitClient.ConnectionConnectedRobot, BotKitClient.ConnectionConnectedRobot -> log("连接成功 ${BotLibClient.getInstance().robotName}") // 显示机器人名字
else -> log("连接失败")
}
}
override fun onAppendMessage(message: Message) {
if (message.direction == 1) {
// 接收到消息
try {
// 去除回馈id
val wxid = msgTask.poll()
// 判断逻辑
if (wxid?.isNotBlank() == true && classLoader != null && message.contentType == Message.ContentTypeText) {
val contentText = message.content as MessageContentText
Core.receiverMsg(classLoader!!, contentText.text, wxid)
}
} catch (e: Exception) {
}
}
}
override fun onReceivedSuggestion(suggestions: ArrayList<MenuItem>?) {
}
fun initConfig(context: Context) {
this.classLoader = context.classLoader
if (SharedPreferencesUtils.XIAO_SHI_ACCESS_KEY?.isNotBlank() == true) {
log("初始化${SharedPreferencesUtils.XIAO_SHI_ACCESS_KEY}")
BotKitClient.getInstance().init(context, SharedPreferencesUtils.XIAO_SHI_ACCESS_KEY);
BotKitClient.getInstance().connect()
}
}
init {
BotKitClient.getInstance().setMessageListener(this)
BotKitClient.getInstance().setConnectionListener(this)
}
fun sendMsg(msg: String, talker: String) {
// 加入回馈队列
msgTask.add(talker)
BotKitClient.getInstance().askQuestion(msg)
}
}
来看看 第四范式客服机器人 效果!
哈哈,这回复有点作死啊!!也不能怪机器人,毕竟 第四范式客服机器人 宣传的是
客服
。大家可以首选图灵机器人。
设置界面
说到设置界面,这还不简单吗?不就是一个布局的事情吗?其实不然,我们要记住我们开发的是一款 Xposed
插件,根据 Xposed
的原理,资源是无法注入的(当然其实技术方案还是有的,可以参考换肤的原理)。
其实动态加载资源也是可以的。但是为了这一个布局,增加一个资源文件,你还需要考虑资源文件怎么获取。其实这个问题一直困扰我,主要原因是我懒。
最终我的方案,就是自定义View,所有的布局都靠代码构建(想一想都恶心)。由于我使用的是 Kotlin
开发,存在一种 DSL
的写法,再加上 Kotlin
提供的 Anko 库,可以很方便的通过代码编写一个布局(有兴趣可以去学习下此库)。
定时任务
难点都攻破了,为何不在做一些有趣的事情呢?回想下,你追求的女孩每天定时定点,能收到一条天气状况和温馨提醒丶每天一条 鸡汤话
丶每天一条 情话
。
定时任务,最先想到的就是 AlarmManager
,它是系统服务提供的一种方案 (虽然存在一些误差)。但是最后发现这个 API
在分身大师X版本既然无效(分身BUG),真是处处碰坎坷啊。
/**
* 定时检查任务 15秒一次
*/
object CoreAlarmManager {
var handler: Handler
var classLoader: ClassLoader? = null
init {
val handlerThread = HandlerThread("TASK_WORK")
handlerThread.start()
handler = Handler(handlerThread.looper) {
if (it.what == 1) {
classLoader?.let { loader ->
TaskManager.invokeTask(loader)
}
postMessage()
}
true
}
}
private fun postMessage(delayMillis:Long=15*1000L){
handler.removeMessages(1)
handler.sendMessageDelayed(Message.obtain().apply {
what = 1
},delayMillis)
}
/**
* 判断定时任务
*
* 15秒扫描一次
*/
@Synchronized
fun init(context: Context) {
this.classLoader = context.classLoader
postMessage(0)
}
}
当然还有
每日一句
丶土味情话
丶自定义情话
丶 的设置和今日天气
的流程一样,大家可以自行尝试。
补充
由于大部分功能我已将完成了,所以我已经将微信版本做了兼容,已经支持微信 7.0.9
版本。
我是用我公司的 360分身大师X版 做的免 Root
使用 Xposed
(微信版本是7.0.7哦)。