Android 使用Java-WebSocket實現簡易推送流程

一.不放效果圖都是耍流氓

效果圖
二.模擬服務端工具

因爲忘記是在哪裏下載的這個軟件了 所以直接放到網盤上 或者自行搜索

鏈接: 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>

六.代碼下載地址

https://download.csdn.net/download/qq_30837235/11578658

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章