一.不放效果圖都是耍流氓
二.模擬服務端工具
因爲忘記是在哪裏下載的這個軟件了 所以直接放到網盤上 或者自行搜索
鏈接: 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>
六.代碼下載地址