远程控制平台四之优化部署

服务器端打包

把服务器打成jar包对于后台开发的朋友来说小菜一碟,但对于前端开发可能有些细节要注意一下,尤其是有依赖其他第三方库的情况下,这里梳理了一下流程:

File – Project Structure – Artifacts – add – JAR – From modules and dependencies


选中module和主类,其他保持默认即可 – OK

Add copy of – Module Output – OK

右侧全选引用到的类库,右键鼠标,Exact into Output Root – OK

Build – Build Artifacts – Rebuild

前台运行(日志即时打印,但关闭窗口程序会退出):

java -jar rt.jar

后台运行:

nohup java -jar rt.jar &

客户端保活

在目前国内手机厂商的严格管制下,绝对的保活是不存在的,只能说,我们自己使用的APP,尽可能自己在手机上把APP加入白名单,使用前台服务,忽略电池使用,允许后台联网,心跳保活等等,多管齐下,才可能让APP尽可能长时间存活。
①忽略电池优化

/**
 * 忽略电池优化
 */
fun Activity.ignoreBatteryOptimizations(): Boolean {
    // 这个方法返回true的时候表明已经关闭,false则是已开启优化
    val powerManager = getSystemService(Service.POWER_SERVICE) as PowerManager
    val hasBeanIgnore = powerManager.isIgnoringBatteryOptimizations(packageName)
    if (!hasBeanIgnore) {
        ToastTools.showLong("请忽略电池优化后继续")
        try {
            val i = Intent().apply {
                action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
            }
            startActivity(i)
        } catch (e: Exception) {
            L.e(e.message)
        }
    }
    return hasBeanIgnore
}

②使用前台服务

fun Service.startForegroundNotification() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        var mChannel: NotificationChannel? = null
        mChannel = NotificationChannel(ControlService.CHANNEL_ID_STRING, getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH)
        mChannel.importance = NotificationManager.IMPORTANCE_LOW
        notificationManager.createNotificationChannel(mChannel)
        val notification: Notification = Notification.Builder(applicationContext,
            ControlService.CHANNEL_ID_STRING
        )
        .setSmallIcon(R.mipmap.ic_notification)
        .setBadgeIconType(NotificationCompat.BADGE_ICON_NONE)
        .build()
        notification.flags = Notification.FLAG_ONGOING_EVENT
        startForeground(ControlService.ID, notification)
    }
}

③解决Socket在后台运行断开连接的问题

fun ZMQ.Socket.enableCommonAuth() {
    curvePublicKey = clientCert.publicKey
    curveSecretKey = clientCert.secretKey
    curveServerKey = FollowManager.currentFollowBean.k

    // 重连
    setTCPKeepAlive(1)
    setTCPKeepAliveIdle(20)
    setTCPKeepAliveInterval(10)
    // 心跳周期
    setHeartbeatIvl(2*1000)
    // 心跳超时
    setHeartbeatTimeout(5*1000)
    // 超过则会让连接超时
    setHeartbeatTtl(10*1000)
    setReconnectIVL(500)
    setReconnectIVLMax(1000)
}

④定时唤醒

/**
 * 定时检查是否已经没有网络了,没有网络则亮屏唤醒一下
 */
object AliveManager {
    private const val ACTION_STOP_SCAN = "keep_alive_rest"

    /**
     * 5分钟检查一下
     */
    private const val CHECK_INTERVAL_MILLIS = 5 * 60_000L

    private val checkNetExecutor = Executors.newSingleThreadExecutor()
    private val mAlarmManager = GlobalContext.getContext().getSystemService(ALARM_SERVICE) as AlarmManager

    private val taskReceiver = object : WakefulBroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            checkConnectServerStatus()
        }
    }

    private fun checkConnectServerStatus() {
        pinging = true
        val serverIp = FollowManager.currentFollowBean.p
        if (!TextUtils.isEmpty(serverIp)) {
            checkNetExecutor.submit(Runnable {
                val networkAvailable = NetworkUtils.isAvailableByPing(serverIp)
                // 到服务器的网络不可达,那么就亮屏唤起CPU
                if (!networkAvailable && !stopped) {
                    AppUtils.wakeupScreen()
                }
                GlobalContext.runOnUiThread {
                    // 继续下一次扫描
                    pinging = false
                    if (!stopped) {
                        start()
                    }
                }
            })
        } else {
            // 继续下一次扫描
            pinging = false
            if (!stopped) {
                start()
            }
        }
    }

    private val restIntent = Intent(ACTION_STOP_SCAN)
    private val restPendingIntent = PendingIntent.getBroadcast(
        GlobalContext.getContext(),
        0,
        restIntent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
    )
    private val intentFilter = IntentFilter().apply {
        addAction(ACTION_STOP_SCAN)
    }

    @Volatile
    private var stopped = true
    @Volatile
    private var pinging = false
    /**
     * 开始定时检查
     */
    fun start() {
        if (pinging) {
            return
        }
        stopped = false
        mAlarmManager.cancel(restPendingIntent)
        GlobalContext.getContext().registerReceiver(taskReceiver, intentFilter, null, null)
        val alarmClockInfo = AlarmClockInfo(System.currentTimeMillis() + CHECK_INTERVAL_MILLIS, null)
        mAlarmManager.setAlarmClock(alarmClockInfo, restPendingIntent)
    }

    fun stop() {
        stopped = true
        mAlarmManager.cancel(restPendingIntent)
        try {
            GlobalContext.getContext().unregisterReceiver(taskReceiver)
        } catch (e: Exception) {
            L.e(e.message)
        }
    }
}

防止串流

防串流的方法也很简单,就是主控端在调用的时候,把当前正在操控的受控端ID也通过命令传过去,受控端解析命令,如果发现命令不是发给自己的,就停止推流。前提是只有一个主控端,如果多个主控端同时看,还是会串流的,但对于我们个人使用而言,这个是可以控制的,如果要严格要求只有一个主控端,目前看来应用单点登陆策略是一个办法。

/**
 * 受控端连接服务器
 */
fun startFollowService() {
    followCmdUrl = "tcp://${FollowManager.currentFollowBean.p}:${KeyValue.publishPortCmd}"
    followExecutorService.submit(Runnable {
        if (followSocket != null) {
            return@Runnable
        }
        try {
            followContext = ZContext()
            followSocket = followContext?.createSocket(SocketType.SUB)
            followSocket?.enableCommonAuth()
            followSocket?.enableCommonChat()
            followSocket?.connect(followCmdUrl)
            followSocket?.subscribe("".toByteArray())
        } catch (e: Exception) {
            AppUtils.relaunchAppOnChildThread("受控端命令服务失败")
        }

        while (followSocket != null) {
            val receivedData = followSocket?.recv(0)
            // 当前不是受控端的话,不用处理指令
            if (receivedData == null || receivedData.isEmpty()) {
                continue
            }

            val cmdString = String(receivedData, ZMQ.CHARSET)
            val cmdArray = cmdString.split(RtConstants.MESSAGE_SPLIT)
            // 非法指令,不处理
            if (cmdArray.size != RtConstants.CMD_SIZE) {
                continue
            }

            // 指令不是发送给当前设备的
            if (!TextUtils.equals(cmdArray[0], KeyValue.deviceId)) {
                lastCmdMillis = 0L
                continue
            }

            // 解析命令
            parseCmd(cmdArray)
        }
    })
}

其他要点

剩下就是一些辅助的知识点了,例如服务器启动时发邮件,发件人的账号密码加密隐藏等等,都是可选的,这些在实现的时候,可以选择性实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ithouse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值