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

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