其他
Android IM即时通信,设计一个聊天中间件吧
https://juejin.cn/user/2365804752418232
https://github.com/kongxiaoan/IM-Middleware.git
即时传输:即时通信是指消息的传输速度非常快,消息可以在几乎同时发送和接收。 实时反馈:移动端即时通信通常能够提供实时反馈,让用户可以迅速得到对方的回应,从而使得交流更加流畅。 多媒体支持:移动端即时通信可以支持多种类型的媒体文件,如文字、图片、语音、视频等,使得用户能够更加方便地进行交流和分享。 多设备同步:移动端即时通信通常能够支持多设备同步,即同一个用户可以在不同的设备上同时登录并进行交流,而且消息会自动同步到所有的设备上。 安全性:移动端即时通信在数据传输过程中通常会采用加密技术,确保用户的通信内容不会被窃取或篡改。 个性化设置:移动端即时通信通常能够提供个性化设置,例如自定义聊天背景、表情包、字体颜色等,让用户可以更加自由地表达自己。 群组聊天:移动端即时通信通常能够支持群组聊天,用户可以创建一个群组并邀请其他用户加入,从而进行多人交流。 在线状态:移动端即时通信通常能够显示用户的在线状态,让用户可以了解对方是否在线,并且可以方便地与对方建立联系。
进程基本分为四种类型
前台进程(Foreground Process):这类进程是指正在与用户交互的应用程序进程,如正在显示在屏幕上的 Activity,或正在播放音乐的应用程序进程等。前台进程优先级最高,系统会尽可能保持这类进程的运行状态,以确保用户能够流畅地使用应用程序。 可见进程(Visible Process):这类进程是指虽然没有直接与用户进行交互,但是对用户当前操作有影响的应用程序进程,如正在后台播放音乐的应用程序进程等。可见进程的优先级次于前台进程,但仍然比较高,系统会尽可能保持这类进程的运行状态。 后台进程(Background Process):这类进程是指已经被用户关闭,但是仍然在后台运行的应用程序进程。后台进程的优先级较低,当系统内存占用过高时,系统会优先回收这类进程来释放内存。 空进程(Empty Process):这类进程是指系统为了加速应用程序的启动而创建的进程,但是没有实际运行任何应用程序代码的进程。这类进程的主要作用是为应用程序的启动提供一个空壳,以缩短启动时间。
进程回收
当系统出现内存不足的情况时,系统会根据进程的优先级和占用的资源量等因素,选择一些进程进行回收。 系统首先会杀掉进程中的所有后台进程和服务进程,释放它们占用的资源,但会保留前台进程和用户正在交互的应用程序进程。 如果系统还是内存不足,那么就会继续回收前台进程和用户正在交互的应用程序进程,直到系统获得足够的内存为止。
单进程和多进程的区别
应用单进程回收:如果应用程序只有一个进程,那么当系统回收进程时,整个应用程序都会被回收,包括应用程序中的所有组件和数据等信息。这会导致应用程序需要重新启动,并重新加载数据等信息,用户体验不佳。 应用多进程回收:如果应用程序采用多进程的方式来运行,那么当系统回收进程时,只有被回收的进程会被停止,其他进程仍然可以继续运行,从而保证应用程序的部分功能可以正常使用。这种情况下,用户体验相对较好,但同时也会增加系统的负担和内存占用量。
什么是服务进程
即时通信应该切进程
为了确保即时通信的实时性,通常将即时通信放在服务进程中。服务进程拥有较高的优先级和稳定的生命周期,系统会优先保留服务进程,而且服务进程的优先级比普通应用程序进程的优先级高,能够更长时间地保留在内存中。因此,将即时通信放在服务进程中,可以保证即时通信的稳定性和实时性。 服务进程与应用程序进程是分离的,即使应用程序进程被回收,服务进程仍然可以继续运行,从而保证即时通信的持续性。另外,服务进程还可以通过绑定方式,为应用程序进程提供即时通信的功能,从而提高了通信的效率和稳定性。
切进程的优势
提高响应速度:将即时通信相关的任务放在单独的进程中处理,可以避免应用程序主进程的阻塞,从而提高应用程序的响应速度和用户体验。 提高稳定性:将即时通信相关的任务放在单独的进程中处理,可以减少应用程序崩溃的风险,从而提高应用程序的稳定性。 方便管理:将即时通信相关的任务放在单独的进程中处理,可以方便地对其进行管理和调试,从而提高开发效率。
简单类图表示物理结构为:
Application:表示 Android 应用程序的入口,负责启动和管理 IM 中间件进程; IMiddleware:表示 IM 中间件的接口,定义了登录和发送消息的方法; Middleware:表示 IM 中间件的实现,实现了 IMiddleware 接口,负责长连接、Push、Https 等数据源处理; ICallback:表示回调接口,定义了消息接收的回调方法; Callback:表示回调的实现,实现了 ICallback 接口,负责将接收到的消息回调到应用程序中; Application依赖 Middleware,即应用程序依赖IM中间件; Middleware实现了 IMiddleware 接口,提供了登录和发送消息的功能; Middleware依赖 Callback,即中间件依赖回调实现,用于将接收到的消息回调到应用程序中; Callback实现了 ICallback 接口,提供了消息接收的回调功能。
调用时序图
进程间通信接口设计
注册/反注册客户端接口 发送消息
void sendMessage(String message);
void registerMessageReceiveListener(IMMessageReceiver messageReceiver);
void unRegisterMessageReceiveListener(IMMessageReceiver messageReceiver);
void registerLoginReceiveListener(IMLoginStatusReceiver loginStatusReceiver);
void unRegisterLoginReceiveListener(IMLoginStatusReceiver loginStatusReceiver);
}
发送消息
interface IMMessageReceiver {
void onMessageReceived(in MessageModel receiveMessage);
}
同步登录状态
void loginStatus(int status);
}
中间件的流程
客户端调用 IMClient 的 init 方法,并传入参数 Application、IMLoginStatusReceiver.Stub 实现类、IMMessageReceiver.Stub 实现类。 IMClient 在 init 方法中启动 MessageService,并返回 Binder 对象。 客户端通过 ServiceConnection 连接 MessageService,连接成功后 IMClient 通过 MessageService 注册 IMMessageReceiver 和IMLoginStatusReceiver。 MessageService 在注册 IMLoginStatusReceiver 成功后开始调用 WebSocket 连接,连接成功后通过 IMLoginStatusReceiver 的回调接口通知客户端登录状态的变化。 客户端通过 IMClient 的 send 方法发送消息,IMClient 在回调接口中调用 WebSocket 的发送方法发送消息。 WebSocket 接收到消息后,通过已经注册的 IMMessageReceiver 回调给客户端。 客户端调用 IMClient 的 loginOut 方法,IMClient 反注册 IMMessageReceiver 和 IMLoginStatusReceiver,并取消 WebSocket 连接。 最后,IMClient 停止 MessageService,MessageService 销毁。
前往 Node.js 官网(nodejs.org/)下载 Node.js 的安装程序。 打开下载好的安装程序,按照提示进行安装。 安装完成后,打开终端应用程序(Terminal),输入以下命令检查 Node.js 是否已正确安装:
node -v
如果成功安装了 Node.js,会显示 Node.js 的版本号。
脚本
// 创建 WebSocket 服务器
const server = new WebSocket.Server({ port: 8080 });
// 监听连接事件
server.on('connection', (socket) => {
console.log('Client connected');
// 监听消息事件
socket.on('message', (data) => {
console.log(`Received message: ${data}`);
// 发送消息到客户端
socket.send(`You sent: ${data}`);
});
// 监听断开连接事件
socket.on('close', () => {
console.log('Client disconnected');
});
});
console.log('WebSocket server started');
MessageService 鉴权
var packageName: String? = null
val packages: Array<String> =
packageManager.getPackagesForUid(getCallingUid()) ?: arrayOf()
if (packages.isNotEmpty()) {
packageName = packages[0]
}
// 指定包名
if (packageName == null || !packageName.startsWith("com.example")) {
Logger.log("权限校验失败 $packageName")
throw RuntimeException("非法调用 $packageName")
}
return super.onTransact(code, data, reply, flags)
}
在 AndroidManifest 中添加:
android:name="com.example.mylibrary.permission.REMOTE_SERVICE_PERMISSION"
android:protectionLevel="normal" />
在 MessageService 中进行检查。
if (checkCallingOrSelfPermission("com.example.mylibrary.permission.REMOTE_SERVICE_PERMISSION") == PackageManager.PERMISSION_DENIED) {
throw RuntimeException("非法调用, 未添加正确权限")
}
return messageSender
}
初始化
val myPid = Process.myPid()
val mActivityManager =
this.getSystemService(Context.ACTIVITY_SERVICE) as android.app.ActivityManager
val var3 = mActivityManager.runningAppProcesses?.iterator()
while (var3?.hasNext() == true) {
val appProcessInfo = var3.next() as android.app.ActivityManager.RunningAppProcessInfo
if (appProcessInfo.pid == myPid && appProcessInfo.processName.equals(
this.packageName,
ignoreCase = true
)
) {
this.initApp()
break
}
}
}
// 初始化东西
abstract fun initApp()
* 初始化IM
*/
fun init(application: Application) {
Logger.log("IM 初始化")
IMClient.init(application, IMParams.Builder().build(), object : IMLoginStatusReceiver.Stub() {
override fun loginStatus(status: Int) {
Logger.log("登录状态 $status")
}
}, IMReceiver())
}
/**
* 发送消息
*/
fun send(message: String) {
IMClient.send(message)
}
/**
* 登出
*/
fun logOut() {
IMClient.loginOut()
}
override fun onMessageReceived(receiveMessage: MessageModel?) {
Logger.log("客户端接收到的消息 $receiveMessage")
}
}
连接websocket
private const val WS_URL = "ws://192.168.31.222:8080"
private val httpClient by lazy {
OkHttpClient().newBuilder()
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.connectTimeout(10, TimeUnit.SECONDS)
.pingInterval(40, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.build()
}
private var mWebSocket: WebSocket? = null
public fun connect() {
val request = Request.Builder()
.url(WS_URL)
.build()
mWebSocket = httpClient.newWebSocket(request, wsListener)
}
fun release() {
mWebSocket?.cancel()
}
fun send(message: String) {
mWebSocket?.send(message)
}
/**
* 获取当前进程名
*
* @param context 上下文
* @return
*/
fun getCurProcessName(context: Context): String? {
// 获取此进程的标识符
val pid = Process.myPid()
// 获取活动管理器
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// 从应用程序进程列表找到当前进程,是:返回当前进程名
for (appProcess in activityManager.runningAppProcesses) {
if (appProcess.pid == pid) {
return appProcess.processName
}
}
return null
}
private val wsListener = object : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response)
Logger.log("onOpen ${webSocket == null}")
mWebSocket = webSocket
IMClient.sendLoginStatus(IMLoginStatus.CONNECT_SUCCESS.ordinal)
Logger.log("onOpen ${mWebSocket == null}")
webSocket.send(
"我是客户端代码发送的 ${
IMClient?.mApplication?.applicationContext?.let {
getCurProcessName(
it
)
}
}"
)
IMClient.loginCallback?.loginStatus(IMLoginStatus.CONNECT_SUCCESS.ordinal)
}
override fun onMessage(webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
Logger.log("onMessage text $text")
val messageModel = MessageModel().apply {
from = "service"
to = "client"
content = "${System.currentTimeMillis()}"
}
IMClient.mReceiver?.onMessageReceived(messageModel)
}
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
super.onClosed(webSocket, code, reason)
IMClient.sendLoginStatus(IMLoginStatus.CONNECT_FAIL.ordinal)
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
super.onClosing(webSocket, code, reason)
IMClient.sendLoginStatus(IMLoginStatus.CONNECT_FAIL.ordinal)
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
super.onFailure(webSocket, t, response)
IMClient.sendLoginStatus(IMLoginStatus.CONNECT_FAIL.ordinal)
Logger.log("onFailure " + t.localizedMessage)
}
}
}
IM单进程,双向可以通信 webscoket 连接 添加 webscoket 登录验证、心跳等常规操作
添加重试机制 添加中间件数据传输封装(使用pb) 完善离线消息处理 想到啥写啥
https://github.com/kongxiaoan/IM-Middleware