本项目是一个依据多进程的Android IM即时通讯中间件,旨在帮助Android开发者了解怎么在完结IM即时通讯的一起,进步使用程序的安稳性和安全性。
经过本项目,您将了解到多进程在Android IM即时通讯中的重要性,并学习到怎么规划和完结一个依据WebSocket的中间件。咱们将深入讲解多进程架构和流程规划,并展示具体的时序图和类图,让您更好地了解该中间件的完结过程。
此外,咱们还将介绍怎么经过多进程通讯来完结音讯传递和状况同步,进步使用程序的功用和安稳性。最后,咱们将展示怎么将该中间件集成到您的Android使用程序中,让您的用户能够愈加快速、可靠地进行即时通讯。
假如您是一个Android开发者,并且正在寻找一种安稳、安全、可扩展的即时通讯解决方案,那么本项目将是您的不贰选择。
客户端项目地址:github.com/kongxiaoan/…
移动端即时通讯的特点:
- 即时传输:即时通讯是指音讯的传输速度非常快,音讯能够在几乎一起发送和接纳。
- 实时反馈:移动端即时通讯一般能够供给实时反馈,让用户能够迅速得到对方的回应,然后使得沟通愈加流通。
- 多媒体支撑:移动端即时通讯能够支撑多种类型的媒体文件,如文字、图片、语音、视频等,使得用户能够愈加便利地进行沟通和分享。
- 多设备同步:移动端即时通讯一般能够支撑多设备同步,即同一个用户能够在不同的设备上一起登录并进行沟通,并且音讯会主动同步到一切的设备上。
- 安全性:移动端即时通讯在数据传输过程中一般会选用加密技术,确保用户的通讯内容不会被窃取或篡改。
- 个性化设置:移动端即时通讯一般能够供给个性化设置,例如自界说谈天背景、表情包、字体颜色等,让用户能够愈加自由地表达自己。
- 群组谈天:移动端即时通讯一般能够支撑群组谈天,用户能够创立一个群组并约请其他用户参加,然后进行多人沟通。
- 在线状况:移动端即时通讯一般能够显现用户的在线状况,让用户能够了解对方是否在线,并且能够便利地与对方树立联系。
这些一切的特性都是在“能够完结快速、便利、实时的沟通和沟通”的基础上完结的,然而在Android客户端,能否实时取决于进程是否存活,假如进程被收回了,那这些特性都将失效。
做Android的都知道,进程的收回APP是自己管控不了的,
Android 进程
在 Android 体系中,进程是纠正在运转的使用程序的实例。每个进程都有自己独立的虚拟机和体系资源,它们之间相互隔离,互不干扰。
进程一般由一个或多个组件构成,如 Activity、Service、BroadcastReceiver 和 ContentProvider 等。每个组件都运转在它地点的进程中,但不同的组件能够运转在不同的进程中。
生命周期遭到体系内存办理机制的影响。当体系内存不足时,体系会依据一定的战略来收回一些进程,以开释内存资源。Android 进程办理机制是依据 Linux 内核的,体系会依据进程的重要性和内存占用情况来进行进程收回和优先级调度。
Android 进程并不是一个静态的概念,它的状况是动态变化的。使用程序发动时会创立进程,当使用程序不再需要该进程时,体系会将其收回。
进程根本分为四种类型
- 前台进程(Foreground Process):这类进程是纠正在与用户交互的使用程序进程,如正在显现在屏幕上的 Activity,或正在播映音乐的使用程序进程等。前台进程优先级最高,体系会尽或许坚持这类进程的运转状况,以确保用户能够流通地使用使用程序。
- 可见进程(Visible Process):这类进程是指尽管没有直接与用户进行交互,但是对用户当时操作有影响的使用程序进程,如正在后台播映音乐的使用程序进程等。可见进程的优先级次于前台进程,但仍然比较高,体系会尽或许坚持这类进程的运转状况。
- 后台进程(Background Process):这类进程是指现已被用户封闭,但是仍然在后台运转的使用程序进程。后台进程的优先级较低,当体系内存占用过高时,体系会优先收回这类进程来开释内存。
- 空进程(Empty Process):这类进程是指体系为了加快使用程序的发动而创立的进程,但是没有实际运转任何使用程序代码的进程。这类进程的主要作用是为使用程序的发动供给一个空壳,以缩短发动时刻。
以上四类进程的优先级和占用资源等要素都会影响 Android 体系的功用和安稳性,因而,在开发使用程序时,需要尽或许地减少内存的占用和资源的浪费,以进步体系的响应速度和用户体验。
进程收回
为了确保体系的安稳性和资源办理的功率,体系会对进程进行收回。进程收回的流程如下:
- 当体系出现内存不足的情况时,体系会依据进程的优先级和占用的资源量等要素,选择一些进程进行收回。
- 体系首先会杀掉进程中的一切后台进程和服务进程,开释它们占用的资源,但会保存前台进程和用户正在交互的使用程序进程。
- 假如体系仍是内存不足,那么就会持续收回前台进程和用户正在交互的使用程序进程,直到体系获得足够的内存中止。
单进程和多进程的区别
- 使用单进程收回:假如使用程序只要一个进程,那么当体系收回进程时,整个使用程序都会被收回,包含使用程序中的一切组件和数据等信息。这会导致使用程序需要从头发动,并从头加载数据等信息,用户体验不佳。
- 使用多进程收回:假如使用程序选用多进程的办法来运转,那么当体系收回进程时,只要被收回的进程会被中止,其他进程仍然能够持续运转,然后确保使用程序的部分功用能够正常使用。这种情况下,用户体验相对较好,但一起也会增加体系的负担和内存占用量。
在开发中多进程拉活尽管不能保活了,但是也是伪保活的一种重要手段。
即时通讯对于Android进程,改怎么做
其实简略的过一下Android进程相关的东西,是为了说明APP对本身的生命周期根本没有操控力,要想确保即时通讯建立,地点进程活着,显然是最主要的。
什么是服务进程
服务进程并不是进程的分类,而是进程的一种特殊用处。在 Android 中,服务进程是指运转服务组件的进程。服务进程一般会在使用程序发动时被创立,并在整个使用程序的生命周期内一直存在,直到使用程序被封闭或许体系内存不足时被收回。
服务进程一般用于履行一些需要在后台长期运转的使命,如播映音乐、下载文件、推送音讯等。将这些使命放在服务进程中能够确保它们不会因为使用程序被封闭而中断,然后确保使用程序的耐久衔接和安稳性。
即时通讯应该切进程
- 为了确保即时通讯的实时性,一般将即时通讯放在服务进程中。服务进程拥有较高的优先级和安稳的生命周期,体系会优先保存服务进程,并且服务进程的优先级比一般使用程序进程的优先级高,能够更长时刻地保存在内存中。因而,将即时通讯放在服务进程中,能够确保即时通讯的安稳性和实时性。
- 服务进程与使用程序进程是分离的,即便使用程序进程被收回,服务进程仍然能够持续运转,然后确保即时通讯的持续性。别的,服务进程还能够经过绑定办法,为使用程序进程供给即时通讯的功用,然后进步了通讯的功率和安稳性。
切进程的优势
看完上面的一长串介绍后应该了解,切进程的优势是非常巨大的,总结一下:
-
进步响应速度:将即时通讯相关的使命放在独自的进程中处理,能够防止使用程序主进程的堵塞,然后进步使用程序的响应速度和用户体验。
-
进步安稳性:将即时通讯相关的使命放在独自的进程中处理,能够减少使用程序崩溃的风险,然后进步使用程序的安稳性。
-
便利办理:将即时通讯相关的使命放在独自的进程中处理,能够便利地对其进行办理和调试,然后进步开发功率。
客户端组件物理结构规划
将APP作为客户端,依靠IM中间件供给的接口完结即时通讯。中间件作为服务进程独立运转,担任长衔接、Push、Https等数据源处理,将处理后的数据经过AIDL回调到APP中进行显现和处理。
能够有效防止进程收回对通讯质量的影响,进步即时通讯的安稳性和可靠性。一起,中间件作为服务进程独立于使用进程运转,能够确保即便使用进程被收回,也不会影响即时通讯服务的运转。这种规划方案能够让数据处理逻辑与使用程序逻辑分离,进步代码的可维护性和扩展性。
如下图(箭头方向为依靠方向)
简略类图表明物理结构为:
-
Application
:表明Android使用程序的入口,担任发动和办理IM中间件进程; -
IMiddleware
:表明IM中间件的接口,界说了登录和发送音讯的办法; -
Middleware
:表明IM中间件的完结,完结了IMiddleware
接口,担任长衔接、Push、Https等数据源处理; -
ICallback
:表明回调接口,界说了音讯接纳的回调办法; -
Callback
:表明回调的完结,完结了ICallback
接口,担任将接纳到的音讯回调到使用程序中; -
Application
依靠Middleware
,即便用程序依靠IM中间件; -
Middleware
完结了IMiddleware
接口,供给了登录和发送音讯的功用; -
Middleware
依靠Callback
,即中间件依靠回调完结,用于将接纳到的音讯回调到使用程序中; -
Callback
完结了ICallback
接口,供给了音讯接纳的回调功用。
调用时序图
具体IM 中间件规划
进程间通讯接口规划
依照接口隔离原则,咱们能够将接口分为两个部分:
-
客户端向服务端
- 注册/反注册客户端接口
- 发送音讯
interface IMessageProvider{
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); }
- 同步登录状况
interface IMLoginStatusReceiver { void loginStatus(int status); } ```
-
传输数据
目前先使用model代替,后续采取pb传输
中间件的流程
- 客户端调用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毁掉。
中间件
开发一个webSocket 服务器
写Demo 的时分没有调试用的服务器,简略有用node.js 写一个
环境:
- Node
Mac 上装置 Node.js 的步骤
- 前往 Node.js 官网(nodejs.org/)下载 Node.js 的装置程序。
- 翻开下载好的装置程序,依照提示进行装置。
- 装置完结后,翻开终端使用程序(Terminal),输入以下指令查看 Node.js 是否已正确装置:
Copy code
node -v
-
假如成功装置了 Node.js,会显现 Node.js 的版本号。
脚本:
const WebSocket = require('ws');
// 创立 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');
将脚本保存为 .js 文件,然后在终端使用程序中使用以下指令运转脚本:
node 文件名.js
其间,”文件名.js” 是你保存脚本的文件名。运转该指令后,将发动 WebSocket 服务器,并开端监听本地的 8080 端口 发动成功后
中间件开发:
不熟悉AIDL开发的能够找材料看看,整个中间件部分都有IMClient 操控,客户端在进行操作时,只需要调用IMClient 即可
MessageService 鉴权
为了安全起见,咱们能够使用自界说权限和包名辨别发动程序是否为合法程序
override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
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 中增加:
<permission
android:name="com.example.mylibrary.permission.REMOTE_SERVICE_PERMISSION"
android:protectionLevel="normal" />
- 在MessageService 中进行查看
override fun onBind(intent: Intent?): IBinder? {
if (checkCallingOrSelfPermission("com.example.mylibrary.permission.REMOTE_SERVICE_PERMISSION") == PackageManager.PERMISSION_DENIED) {
throw RuntimeException("不合法调用, 未增加正确权限")
}
return messageSender
}
初始化
由于是多进程,所以需要在Application中判别进程,在使用进程中初始化即可
private fun init() {
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()
}
音讯接纳
class IMReceiver : IMMessageReceiver.Stub() {
override fun onMessageReceived(receiveMessage: MessageModel?) {
Logger.log("客户端接纳到的音讯 $receiveMessage")
}
}
衔接websocket
object WebSocketManager {
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)
}
}
}
Demo
目前完结
- IM单进程,双向能够通讯
- webscoket 衔接
- 增加webscoket 登录验证、心跳等惯例操作
后续规划
- 增加重试机制
- 增加中间件数据传输封装(使用pb)
- 完善离线音讯处理
- 想到啥写啥
项目地址IM-Middleware(https://github.com/kongxiaoan/IM-Middleware)