一.不放效果图都是耍流氓
二.模拟服务端工具
因为忘记是在哪里下载的这个软件了 所以直接放到网盘上 或者自行搜索
链接: https://pan.baidu.com/s/1IZZPcUEJCzSBPIkalngNOg 提取码: afxr
三.项目结构
四.Push Library 代码
1.push library build.gradle
implementation "org.java-websocket:Java-WebSocket:1.4.0"
implementation 'io.reactivex.rxjava2:rxjava:2.2.10'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
2.常量类 ConstConfig.kt
object ConstConfig {
/**
* 发送显示广播的指定路径
*/
var BROADCAST_RECEIVER_PATH = ""
/**
* Notification渠道id
*/
const val PUSH_CHANNEL_ID = "push_channel_id"
/**
* Notification 用户可以看得到的渠道名
*/
const val PUSH_CHANNEL_NAME = "WEBSOCKET_PUSH"
/**
* Notification 用户可以看得到的渠道描述
*/
const val PUSH_CHANNEL_DESCRITION = "推送服务"
/**
* 消息转发的 key
*/
const val PUSH_MESSAGE_KEY = "push_service_key"
}
3.入口类 WebSocketPush.kt
class WebSocketPush {
companion object {
private lateinit var mWebSocketPush: WebSocketPush
private lateinit var mContext: Context
/**
* websocket地址
*/
private var WEB_SOCKET_URI = ""
fun getWebSocketUri(): String {
return WEB_SOCKET_URI
}
/**
* 心跳检测间隔时间
*/
private var BEAT_MILLISECONDS = 30L
fun getBeatMilliseconds(): Long {
return BEAT_MILLISECONDS
}
/**
* 是否打印日志
*/
private var IS_DEBUG = false
fun getIsDebug(): Boolean {
return IS_DEBUG
}
fun newBuilder(context: Context): WebSocketPush {
mWebSocketPush = WebSocketPush()
mContext = context
return mWebSocketPush
}
}
/**
* 设置webSocket链接
*/
fun setWebSocketUri(uri: String): WebSocketPush {
WEB_SOCKET_URI = uri
return this
}
/**
* web socket 发送心跳包间隔
*/
fun setBeatingTime(milliseconds: Long): WebSocketPush {
BEAT_MILLISECONDS = milliseconds
return this
}
/**
* 是否处于debug模式
*/
fun setIsDebug(isDebug: Boolean): WebSocketPush {
IS_DEBUG = isDebug
return this
}
/**
* 显示广播路径
*/
fun setBroadcastReceiverPath(path: String): WebSocketPush {
ConstConfig.BROADCAST_RECEIVER_PATH = path
return this
}
/**
* 启动服务
*/
fun startService() {
ServiceManager.instance.startService(mContext)
}
}
4.长链接服务 PushService.kt
class PushService : Service() {
private val TAG = "PushService"
private val TAG_WEBSOCKET = "WebSocket"
lateinit var client: JWebSocketClient
val compositeDisposable = CompositeDisposable()
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onCreate() {
Logs.w(TAG, "PushService --> onCreate")
super.onCreate()
startForegroundService()
initWebSocket()
heartbeat()
//logService()
}
//service 只有一个实例 多次调用会调用onStartCommand
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Logs.w(TAG, "PushService --> onStartCommand")
return START_STICKY
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
Logs.w(TAG, "PushService --> onTaskRemoved")
}
override fun onDestroy() {
super.onDestroy()
compositeDisposable.clear()
Logs.w(TAG, "PushService --> onDestroy")
}
//打印服务运行中
fun logService() {
Timer().schedule(object : TimerTask() {
override fun run() {
Logs.d(TAG, "running...")
}
}, 0, 10000)
}
/**
* 8.0开前台服务
*/
fun startForegroundService() {
val builder = Notification.Builder(applicationContext)
.setContentTitle("PushService")
.setContentText("PushService is running...")
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
val mNotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val importance = NotificationManager.IMPORTANCE_HIGH
val mChannel = NotificationChannel(ConstConfig.PUSH_CHANNEL_ID, ConstConfig.PUSH_CHANNEL_NAME, importance)
//配置通知渠道的属性
mChannel.description = ConstConfig.PUSH_CHANNEL_DESCRITION
//设置通知出现时的闪灯(如果 android 设备支持的话)
mChannel.enableLights(true)
mChannel.lightColor = Color.GREEN
//设置通知出现时的震动(如果 android 设备支持的话)
mChannel.enableVibration(true)
//mChannel.vibrationPattern = longArrayOf(100, 200, 300, 400, 500, 400, 300, 200, 400)
//最后在notificationmanager中创建该通知渠道 //
mNotificationManager.createNotificationChannel(mChannel)
//为通知设置去渠道id
builder.setChannelId(ConstConfig.PUSH_CHANNEL_ID)
}
startForeground(1, builder.build())
}
/**
* webSocket
*/
fun initWebSocket() {
if (TextUtils.isEmpty(WebSocketPush.getWebSocketUri())) {
Logs.e(TAG_WEBSOCKET, "web socket uri is empty")
return
}
val disposable = Observable.just("")
.observeOn(Schedulers.io())
.subscribe {
val uri = URI.create(WebSocketPush.getWebSocketUri())
client = object : JWebSocketClient(uri, this) {}
try {
client.connectBlocking()
} catch (e: Exception) {
e.printStackTrace()
}
}
compositeDisposable.add(disposable)
}
/**
* 心跳检测
*/
fun heartbeat() {
var sendTime = 0L
val disposable = RxUtil.polling(WebSocketPush.getBeatMilliseconds())
.subscribe {
Logs.i(TAG_WEBSOCKET, "heart beating....")
if (System.currentTimeMillis() - sendTime >= WebSocketPush.getBeatMilliseconds()) {
client?.let {
if (!it.isOpen) {
it.close()
Logs.e(TAG, "web socket try to reconnect.... ${WebSocketPush.getWebSocketUri()}")
initWebSocket()
} else {
it.send("beating " + System.currentTimeMillis())
}
}
}
sendTime = System.currentTimeMillis()
}
compositeDisposable.add(disposable)
}
}
心跳检测 RxUtil.kt
object RxUtil {
/**
* 轮询
*/
fun polling(period: Long): Observable<Long> {
return Observable.interval(0, period, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}
服务管理 ServiceManager.kt
class ServiceManager {
companion object {
val instance: ServiceManager by lazy { ServiceManager() }
}
fun startService(context: Context) {
val intent = Intent(context, PushService::class.java)
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
}
}
5.长链接客户端 JWebSocketClient.kt
open class JWebSocketClient(serverUri: URI?, val context: Context) : WebSocketClient(serverUri, Draft_6455()) {
private val TAG = "WebSocketPush"
init {
Logs.i(TAG, "JWebSocketClient -- init")
}
override fun onOpen(handshakedata: ServerHandshake?) {
Logs.i(TAG, "onOpen")
}
override fun onClose(code: Int, reason: String?, remote: Boolean) {
Logs.i(TAG, "onClose --> code: $code" + "reason: $reason")
}
override fun onMessage(message: String?) {
Logs.i(TAG, "onMessage: \n$message")
//发送显式广播
val intent = Intent()
intent.setClassName(context, ConstConfig.BROADCAST_RECEIVER_PATH)
intent.putExtra(ConstConfig.PUSH_MESSAGE_KEY, message)
context.sendBroadcast(intent)
}
override fun onError(ex: Exception?) {
Logs.e(TAG, "onError: " + "\n" + ex?.printStackTrace())
}
}
五.使用
1.接收消息广播 PushReceiver.kt
class PushReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Log.w("WebSocketPush", "onReceive")
p1?.let {
val message = it.getStringExtra(ConstConfig.PUSH_MESSAGE_KEY)
Log.w("WebSocketPush -->", message)
}
}
}
2.初始化使用 MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startService()
}
fun startService() {
WebSocketPush.newBuilder(this)
.setWebSocketUri("ws://192.168.254.70:8234")//web socket地址
.setBeatingTime(15000)//发送心跳包间隔
.setBroadcastReceiverPath(PushReceiver::class.java.name)//广播路径 用于发送显式广播
.setIsDebug(true)//是否打印日志
.startService()//开启服务
}
}
3.权限及注册广播 AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zian.websocketpush">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name="com.hxdl.push.websocket.PushService"/>
<receiver android:name=".PushReceiver"/>
</application>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
</manifest>
六.代码下载地址