Android开发使用框架手把手搭建一个简单项目

前言

年关将近,公司也没有什么业务了,基本上都是些旧项目维护以及客户给出的功能改动等小需求。正好其中有个需求是关于某个维护了近五年的小项目功能改动,由于这个项目当时搭建得并不好再加上后续的功能变化和最初设计时出入比较大,以至于每次收到有关这个项目的新需求时我都会非常头疼,于是就趁着这段业务空闲期把整个项目优化重构一遍,这样一来就算后续有了新的改动需求维护这个项目的同事也不再会因为 “在屎山里面加屎” 而感到头疼了。整个重构过程包括后续的测试优化前前后后大概花了两个星期的时间,在这里把整个重构的流程和思路都记录一下,方便自己对项目搭建能力的巩固和归纳总结,也适用于一些刚入门不久想要提升项目搭建能力的同学参考和交流学习。由于自身的才疏学浅在项目搭建过程中可能会有许多的错漏和不足之处还请大家见谅,也希望各位大佬能够及时纠正并指点一二!

一、项目描述

1. 需求描述

一款适用于基于 android 系统的定制手表通过各种连接方式以不同的通信链路连接北斗设备进行指令交互以实现 “监测并反馈实时的设备状况” 和 “通过卫星消息收发文本、语音以及拆包图片” 以及除这两个主要功能以外的其他次要功能。其中通信链路包括:BLE低功耗蓝牙、经典蓝牙、USB Host、USB Accessory 和 Serial Port串口

2. 难点分析

项目的整体功能非常简单:实现基本的连接功能之后对设备输出反馈的标准北斗协议、设备自定义协议、以及公司自定的消息内容协议进行解析,获取到对应的设备参数和通信内容并实时更新页面及功能即可。
其中相对复杂的部分大概有以下几点:
1. 由于兼容的通信链路比较多并且某些链路的运作方式与其他链路的同质性差异较大导致项目中的连接管理功能部分变得非常臃肿复杂
2. 由于兼容的设备类型比较多,除了兼容标准的北斗协议以外还要兼容各种设备自身的特有指令和卫星消息中的自定义的内容协议,不同的协议反馈了不同功能和模块需要用到的参数并且其中有许多参数是多个模块同时需要用到亦或是一个模块用到了多种不同协议参数的场景导致项目中对获取到的数据参数分类和管理困难
3. 由于卫星通信方式较为特殊,对设备实时反馈的设备状态及环境因素比较严格,并且需要根据不同类型的北斗设备和北斗卡来限制各自发送卫星消息的发送条件、发送内容长度和发送间隔,特别是图片分包发送功能中的发送流程控制逻辑非常复杂

3. 项目说明

项目采用的是 Java 和 Kotlin 两种语言混合开发,采用 MVVM 框架,为了规范和项目后续的维护改动采用多模块开发方式。整个项目中使用到的各开发技术点大致有:ViewModel/ViewBinding等Jetpack组件、Kotlin协程、EventBus观察者模式、路由、GreenDao数据库、多模块开发等等
源码链接:https://github.com/LXTTTTTT/BDProject

二、项目搭建

1. 基本结构

项目主要拆分为3个模块(壳工程以外),分别是:主页面模块、数据处理和工具模块、数据存储模块(感觉这里有点为拆而拆了.. 没办法这个APP的功能实在太少)其实在这里数据连接和协议解析部分也应该单独抽出来作为各自单独的一个模块,正好最近有个任务是为了方便客户进行二次开发让我设计编写通信链路的SDK和设备协议解析的SDK,等开发完成之后再整合到这个项目里面
在这里我先从项目的主要和基本部分开始再到页面和功能一步步地分解和说明

1. 主程序

主程序中只执行一系列项目前置初始化工作,需要进行初始化的任务有:全局帮助类(提供APP上下文环境)初始化、MMKV工具初始化、系统信息工具初始化、GreenDao数据库初始化、Arouter路由初始化、中山大学低码率语音和渐进式图片压缩库初始化、全局异常捕获初始化、注册页面生命周期变化监听、注册系统定边变化监听
在这里使用了前段时间学习的某个大型项目框架中用到的拓扑算法来对各初始化任务执行先后进行排序

class MainApplication:Application(), ViewModelStoreOwner {

// 常量 ------------------------------------------
    val TAG = "MainApplication"
    var aliveActivityCount = 0  // 已开启页面统计

    companion object{
        private lateinit var mainApplication: MainApplication
        fun getInstance(): MainApplication {
            return mainApplication
        }
    }

    override fun onCreate() {
        super.onCreate()
        mainApplication = this;

        // 执行程序初始化
        TaskDispatcher.init(this)  // 初始化调度器
        TaskDispatcher.createInstance().addTask(InitAppUtilTask(this))
            .addTask(InitMmkvTask())
            .addTask(InitSystemInfoTask())
            .addTask(InitGreenDaoTask())
            .addTask(InitArouterTask())
            .addTask(InitZDCompression())
            .addTask(InitCatchException())
            .start()
        TaskDispatcher.createInstance().await()

        // Activity 生命周期注册
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks{
            override fun onActivityCreated(p0: Activity, p1: Bundle?) {
                Log.d(TAG, "onActivityCreated: 打开页面 $p0.localClassName")
                ActivityManagementUtils.getInstance().push(p0)
            }
            override fun onActivityStarted(p0: Activity) {
                aliveActivityCount++;if (aliveActivityCount == 1) { Log.e(TAG, "切换到前台") }
            }
            override fun onActivityResumed(p0: Activity) {}
            override fun onActivityPaused(p0: Activity) {}
            override fun onActivityStopped(p0: Activity) {
                aliveActivityCount--;if (aliveActivityCount == 0) { Log.e(TAG, "切换到后台") }
            }
            override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {}
            override fun onActivityDestroyed(p0: Activity) {
                Log.d(TAG, "onActivityCreated: 关闭页面 $p0.localClassName")
                ActivityManagementUtils.getInstance().pop(p0)
            }
        })

        // 注册系统定位变化监听
        SystemLocationUtils.init(this)
    }

    private val appViewModelStore: ViewModelStore by lazy { ViewModelStore() }
    override fun getViewModelStore(): ViewModelStore {
        return appViewModelStore
    }

}

2. 基类

2.1 Activity 基类

页面基类分为三种:普通基类、ViewBinding基类、MVVM基类
依据不同情况选择实现,其中ViewBinding基类继承于普通基类,MVVM基类继承于ViewBinding基类,因此当需要进行全局性的页面修改时只要对普通基类进行修改即可
2.1.1 普通基类
APP功能较少,在基类中没有进行过多的额外方法定义,在这里对其中几个主要的方法进行说明:beforeSetLayout():在设置页面布局之前执行,预留,在这个项目中并没有用上,抽象方法必须实现
setLayout():指定页面布局对象,抽象方法必须实现
setActivityLayout():主要的设置布局执行方法,从setLayout()中获取布局对象并判断获取的类型为布局id还是view进行相应的绑定操作。开放方法,在 ViewBinding 基类中重写这个方法为通过反射直接获取指定的 ViewBinding 对象
initView(savedInstanceState: Bundle?):初始化页面方法,在主线程中执行,主要执行对UI相关事件的初始化操作,抽象方法必须实现
initData():初始化数据(主线程),抽象方法必须实现
initDataSuspend():初始化数据(子线程),抽象方法必须实现

abstract class BaseActivity : AppCompatActivity(){

    val TAG : String? = this::class.java.simpleName
    val APP : Application by lazy { ApplicationUtils.getApplication() }
    lateinit var activity_window : Window
    lateinit var my_context : Context
    var title_textview: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activity_window = window
        my_context = this
        beforeSetLayout()
        supportActionBar?.let {it.hide()}  // 隐藏标题栏
        setActivityLayout()  // 绑定布局
        try { title_textview = findViewById<TextView>(R.id.title) } catch (e: Exception) { loge("这个页面没有 title bar") }  // 必须放在初始化布局之后,初始化数据之前
        setOrientationPortrait()  // 锁定垂直布局
        initView(savedInstanceState);  // 初始化页面
        CoroutineScope(Dispatchers.Main).launch{
            initData();  // 初始化数据
            withContext(Dispatchers.IO){
                initDataSuspend()
            }
        }
        if(enableEventBus()){EventBus.getDefault().register(this)}  // 是否需要开启 EventBus 功能
    }


    abstract fun setLayout(): Any?
    abstract fun beforeSetLayout()
    abstract fun initView(savedInstanceState: Bundle?)
    abstract fun initData()
    abstract suspend fun initDataSuspend()
    open fun enableEventBus():Boolean=false

    // 绑定布局
    open fun setActivityLayout(){
        loge("手动绑定布局")
        setLayout()?.let {
            if (setLayout() is Int) {
                setContentView((setLayout() as Int?)!!) // 手动绑定 R.layout.id
            } else {
                setContentView(setLayout() as View?) // 手动绑定 ViewBinding
            }
        }
    }

    // 打印 loge
    fun loge(log: String?) {
        Log.e(TAG, log!!)
    }

    // 设置页面标题
    fun setTitle(title : String){
        loge("设置页面标题:$title")
        title_textview?.let { it.text = title }
    }

    // 锁定页面垂直
    fun setOrientationPortrait() {
        requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
    }

    override fun onDestroy() {
        if(enableEventBus()){EventBus.getDefault().unregister(this)}
//        unregisterSlideBack()
        super.onDestroy()
    }

    // 页面返回响应
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        loge("页面返回,请求码是:$requestCode 响应码是:$resultCode")
    }


    // 权限申请响应
    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
        loge("请求权限码是:" + requestCode + " / 权限列表:" + Arrays.toString(permissions) + " / 结果列表:" + Arrays.toString(grantResults))
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
}

2.1.2 ViewBinding 基类

继承于普通基类,重写setActivityLayout()设置布局方法为:通过反射获取指定的 ViewBinding 对象并绑定页面布局,声明时需传入指定的 ViewBinding 对象类型VB

abstract class BaseViewBindingActivity<VB : ViewBinding> : BaseActivity() {

    lateinit var viewBinding : VB

    // 重写设置布局方法
    override fun setActivityLayout() {
        // 通过反射获取到对应的 Binding 对象并拿到他的 Binding.inflate(layoutInflater) 方法执行
        val type = javaClass.genericSuperclass  // getClass().getGenericSuperclass();
        // 拿到 ViewBinding 类对象
        val vbClass: Class<VB> = type!!.saveAs<ParameterizedType>().actualTypeArguments[0].saveAs()  // genericSuperclass 强转为 ParameterizedType
        // 拿到 ViewBinding 类的inflate方法
        val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
        // 执行 ViewBinding.inflate(getLayoutInflater()); 前面的变量已声明类型VB所以不需要再指定<VB>
        viewBinding = method.invoke(this, layoutInflater)!!.saveAsUnChecked()
        setContentView(viewBinding.root)  // 设置布局
    }

    override fun setLayout(): Any? {return null}  // 直接返回 null 就好了

}

2.1.3 MVVM 基类

继承于 ViewBinding 基类,重写initData()方法,通过反射获取指定的 ViewModel 对象,声明时需传入指定的 ViewModel 对象类型VB和 Boolean 类型参数决定使用的 ViewModel 生命周期,子类实现时需要执行super.initData()方法

abstract class BaseMVVMActivity<VB : ViewBinding ,VM : ViewModel>(global_model:Boolean = false) : BaseViewBindingActivity<VB>(){

    val global = global_model  // 是否全局使用的 ViewModel
    lateinit var viewModel: VM

    // 第一次重写,子类还要重写一次并且必须调用super方法执行下面的初始化
    override fun initData() {

        // 反射创建 ViewModel 对象
//        val argument = (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
        if(global){loge("使用全局ViewModel")}else{loge("使用临时ViewModel")}
        viewModel = if(global){
            // 使用全局 ViewModel
//            ApplicationUtils.getGlobalViewModel(argument[1] as Class<VM>)!!
            val superClass = this::class.supertypes
            loge("构造函数:${superClass[0]}")
            val kArgument = superClass[0].arguments
            loge("ViewModel对象:${kArgument[1]}")
            val classVM = kArgument[1].type?.classifier
            val globalViewModelStore : ViewModelStore =
                if(ApplicationUtils.globalViewModelStore==null){
                    ApplicationUtils.globalViewModelStore = viewModelStore
                    viewModelStore
                }else{
                    loge("已有 globalViewModelStore ")
                    ApplicationUtils.globalViewModelStore!!
                }
            ViewModelLazy(
                classVM as KClass<VM>,
                { globalViewModelStore },
                { defaultViewModelProviderFactory }
            ).value
        }else{
            val argument = (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
            ViewModelProvider(this).get(argument[1] as Class<VM>)
        }

    }

}
2.2 Fragment 基类

思路与Activity基类相似,不做赘述
2.2.1 普通基类

abstract class BaseFragment:Fragment() {

    private var TAG:String = javaClass.name
    val APP : Application by lazy { ApplicationUtils.getApplication() }
    lateinit var my_context: Context;
    lateinit var title: TextView;
    lateinit var back: ImageView;
    lateinit var layoutView:View;

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG, "onCreate: 打开 Fragment")
        setHasOptionsMenu(true)  // 开启菜单项
        beforeSetLayout()
        my_context = requireContext()
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        layoutView = if(setLayout() is Int) inflater.inflate((setLayout() as Int),container,false) else (setLayout() as View)  // 使用 R.layout 或 ViewBinding
        return layoutView
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        getView()?.let { initView(it) }  // 初始化控件事件
        CoroutineScope(Dispatchers.Main).launch{
            initData();  // 初始化数据
            withContext(Dispatchers.IO){
                initDataSuspend()
            }
        }
    }

    abstract fun beforeSetLayout()
    abstract fun setLayout():Any?
    abstract fun initView(view: View)
    abstract fun initData()
    abstract suspend fun initDataSuspend()

    fun loge(log:String){
        Log.e(TAG, log )
    }


    override fun onDestroy() {
        Log.d(TAG, "onCreate: 关闭 Fragment")
        super.onDestroy()
    }
}

2.2.2 ViewBinding 基类

abstract class BaseViewBindingFragment<VB : ViewBinding> : BaseFragment() {

    lateinit var viewBinding : VB

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val type = javaClass.genericSuperclass
        val vbClass: Class<VB> = type!!.saveAs<ParameterizedType>().actualTypeArguments[0].saveAs()
        val method = vbClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
        viewBinding = method.invoke(this, layoutInflater)!!.saveAsUnChecked()
        return viewBinding.root
    }

    override fun setLayout(): Any? { return null }

}

2.2.3 MVVM 基类

abstract class BaseMVVMFragment<VB : ViewBinding, VM : ViewModel>(global_model:Boolean = false) : BaseViewBindingFragment<VB>() {

    val global = global_model  // 是否全局使用的 ViewModel
    lateinit var viewModel: VM

    override fun initData() {
        // 反射创建 ViewModel 对象
        if(global){loge("使用全局ViewModel")}else{loge("使用临时ViewModel")}
        viewModel = if(global){
            // 使用全局 ViewModel
            val superClass = this::class.supertypes
            loge("构造函数:${superClass[0]}")
            val kArgument = superClass[0].arguments
            loge("ViewModel对象:${kArgument[1]}")
            val classVM = kArgument[1].type?.classifier
            val globalViewModelStore : ViewModelStore =
                if(ApplicationUtils.globalViewModelStore==null){
                    ApplicationUtils.globalViewModelStore = viewModelStore
                    viewModelStore
                }else{
                    loge("已有 globalViewModelStore ")
                    ApplicationUtils.globalViewModelStore!!
                }
            ViewModelLazy(
                classVM as KClass<VM>,
                { globalViewModelStore },
                { defaultViewModelProviderFactory }
            ).value
        }else{
            val argument = (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
            ViewModelProvider(this).get(argument[1] as Class<VM>)
        }
    }

}
2.3 Adapter 基类

子类实现简单,声明时需指定子数据类型及子布局的 ViewBinding 对象,兼容头脚布局功能
2.3.1 Adapter 基类

abstract class BaseRecyclerViewAdapter<T, B : ViewBinding> : RecyclerView.Adapter<BaseViewHolder>() {

    // 数据
    private var data: MutableList<T> = mutableListOf()

    // 这个项目用不上
    private lateinit var mHeaderLayout: LinearLayout  // 头布局
    private lateinit var mFooterLayout: LinearLayout  // 脚布局

    companion object {
        const val HEADER_VIEW = 0x10000111
        const val FOOTER_VIEW = 0x10000222
    }


// 接口 ------------------------------------------------------------------------------------
    var onItemClickListener: ((view: View, position: Int) -> Unit)? = null  // 点击
    var onItemLongClickListener: ((view: View, position: Int) -> Boolean) = { view, position -> false }  // 长按

    /**
     * 子类不可重载,如果有需要请重写[onCreateDefViewHolder]实现自定义ViewHolder
     * 或者重写[getViewBinding]传入布局,不需要创建ViewHolder
     */
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder {
        val baseViewHolder: BaseViewHolder
        when (viewType) {
            // 头、脚、子布局
            HEADER_VIEW -> {
                val headerParent: ViewParent? = mHeaderLayout.parent
                if (headerParent is ViewGroup) {
                    headerParent.removeView(mHeaderLayout)
                }
                baseViewHolder = BaseViewHolder(mHeaderLayout)
            }
            FOOTER_VIEW -> {
                val headerParent: ViewParent? = mFooterLayout.parent
                if (headerParent is ViewGroup) {
                    headerParent.removeView(mFooterLayout)
                }
                baseViewHolder = BaseViewHolder(mFooterLayout)
            }
            else -> {
                val layoutInflater = LayoutInflater.from(parent.context)
                baseViewHolder = onCreateDefViewHolder(layoutInflater, parent, viewType)
                bindViewClickListener(baseViewHolder)  // 设置点击事件
            }
        }
        return baseViewHolder
    }

    /**
     * 子类可以选择重载该方法,如果有需要可重写[onBindDefViewHolder],点击事件调用super即可
     */
    override fun onBindViewHolder(holder: BaseViewHolder, position: Int) {
        when (holder.itemViewType) {
            HEADER_VIEW, FOOTER_VIEW -> {
                return
            }
            else -> {
                //holder必须继承自BaseBindViewHolder
                if (holder is BaseBindViewHolder<*>) {
                    holder as BaseBindViewHolder<B>
                    val realPosition = position - headerLayoutCount
                    val item = getItem(realPosition)
                    item?.let {
                        onBindDefViewHolder(holder, it, realPosition)
                    }
                }
            }
        }
    }

    protected open fun bindViewClickListener(holder: BaseViewHolder) {
        onItemClickListener?.let {
            holder.itemView.setOnClickListener { v ->
                var position = holder.adapterPosition
                if (position == RecyclerView.NO_POSITION) {
                    return@setOnClickListener
                }
                position -= headerLayoutCount
                it.invoke(holder.itemView, position)
            }
        }

        onItemLongClickListener?.let {
            holder.itemView.setOnLongClickListener { v ->
                var position = holder.adapterPosition
                if (position == RecyclerView.NO_POSITION) {
                    return@setOnLongClickListener false
                }
                position -= headerLayoutCount
                it.invoke(holder.itemView, position)
            }
        }
    }

    /**
     * 子类重写该方法绑定数据
     * 重写[onCreateDefViewHolder]即可实现不同ViewHolder传递
     */
    protected abstract fun onBindDefViewHolder(holder: BaseBindViewHolder<B>, item: T?, position: Int)

    /**
     * 子类不可重载该方法,如有需要请重写[getDefItemViewType]
     */
    // 子项类型
    override fun getItemViewType(position: Int): Int {
        return if (hasHeaderView() && position == headerViewPosition) {
            // 如果初始化了头布局并且位置是0就是头布局
            HEADER_VIEW
        } else if (hasFooterView() && position == footerViewPosition) {
            // 如果初始化了脚布局并且位置是末尾就是脚布局
            FOOTER_VIEW
        } else {
            val realPosition = if (hasHeaderView()) {
                position - 1
            } else {
                position
            }
            getDefItemViewType(realPosition)
        }
    }

    // 重写此方法,返回 ViewType
    protected open fun getDefItemViewType(position: Int): Int {
        return super.getItemViewType(position)
    }

    /**
     * 不要重写此方法,如果有需要请重写[getDefItemCount]
     */
    override fun getItemCount(): Int {
        return headerLayoutCount + getDefItemCount() + footerLayoutCount
    }


    // 重写此方法,返回数据量
    protected open fun getDefItemCount(): Int {
        return data.size
    }

    // 子类实现创建自定义ViewHolder,父类提供了LayoutInflater
    protected open fun onCreateDefViewHolder(
        layoutInflater: LayoutInflater,
        parent: ViewGroup,
        viewType: Int
    ): BaseViewHolder {
        return BaseBindViewHolder(getViewBinding(layoutInflater, parent, viewType))
    }

    // 获取子项的 ViewBinding :子类实现ViewBinding,父类提供了LayoutInflater,可以根据不同的viewType传递不同的viewBinding
    abstract fun getViewBinding(layoutInflater: LayoutInflater, parent: ViewGroup, viewType: Int): B

    /**
     * 添加HeaderView
     * @param view
     * @param index view在HeaderView中的位置
     * @return Int
     */
    fun addHeadView(view: View, index: Int = -1): Int {
        if (!this::mHeaderLayout.isInitialized) {
            mHeaderLayout = LinearLayout(view.context)
            mHeaderLayout.orientation = LinearLayout.VERTICAL
            mHeaderLayout.layoutParams = RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
        }
        val childCount = mHeaderLayout.childCount
        var mIndex = index
        if (index < 0 || index > childCount) {
            mIndex = childCount
        }
        mHeaderLayout.addView(view, mIndex)
        if (mHeaderLayout.childCount == 1) {
            notifyItemInserted(headerViewPosition)
        }
        return mIndex
    }

    // 移除头布局
    fun removeHeadView(header: View) {
        if (hasHeaderView()) {
            mHeaderLayout.removeView(header)
            if (mHeaderLayout.childCount == 0) {
                notifyItemRemoved(headerViewPosition)
            }
        }
    }

    // 移除所有头布局
    fun removeAllHeadView() {
        if (hasHeaderView()) {
            mHeaderLayout.removeAllViews()
            notifyItemRemoved(headerViewPosition)
        }
    }


    // Heater 位置
    val headerViewPosition: Int = 0

    // 是否有头布局
    fun hasHeaderView(): Boolean {
        // 头布局已经初始化并且里面有内容
        return this::mHeaderLayout.isInitialized && mHeaderLayout.childCount > 0
    }


    // 头布局数量
    val headerLayoutCount: Int
        get() {
            return if (hasHeaderView()) {
                1
            } else {
                0
            }
        }

    // 头布局的子View数量
    val headerViewCount: Int
        get() {
            return if (hasHeaderView()) {
                mHeaderLayout.childCount
            } else {
                0
            }
        }


    // 获取头布局
    val headerBinding: LinearLayout?
        get() {
            return if (this::mHeaderLayout.isInitialized) {
                mHeaderLayout
            } else {
                null
            }
        }

    /**
     * 添加FooterView
     * @param view
     * @param index view在FooterView中的位置
     * @return Int
     */
    fun addFootView(view: View, index: Int = -1): Int {
        if (!this::mFooterLayout.isInitialized) {
            mFooterLayout = LinearLayout(view.context)
            mFooterLayout.orientation = LinearLayout.VERTICAL
            mFooterLayout.layoutParams = RecyclerView.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
        }
        val childCount = mFooterLayout.childCount
        var mIndex = index
        if (index < 0 || index > childCount) {
            mIndex = childCount
        }
        mFooterLayout.addView(view, mIndex)
        if (mFooterLayout.childCount == 1) {
            notifyItemInserted(footerViewPosition)
        }
        return mIndex
    }

    // 移除脚布局
    fun removeFootView(header: View) {
        if (hasFooterView()) {
            mFooterLayout.removeView(header)
            if (mFooterLayout.childCount == 0) {
                notifyItemRemoved(footerViewPosition)
            }
        }
    }


    // 移除所有脚布局
    fun removeAllFootView() {
        if (hasFooterView()) {
            mFooterLayout.removeAllViews()
            notifyItemRemoved(footerViewPosition)
        }
    }

    // 是否有 FooterView
    fun hasFooterView(): Boolean {
        return this::mFooterLayout.isInitialized && mFooterLayout.childCount > 0
    }


    // 脚布局的位置是 头布局+子项数量
    val footerViewPosition: Int
        get() = headerLayoutCount + data.size

    // 脚布局数量
    val footerLayoutCount: Int
        get() {
            return if (hasFooterView()) {
                1
            } else {
                0
            }
        }


    // footerView的子View数量
    val footerViewCount: Int
        get() {
            return if (hasFooterView()) {
                mFooterLayout.childCount
            } else {
                0
            }
        }

    // 获取尾布局
    val footerBinding: LinearLayout?
        get() {
            return if (this::mFooterLayout.isInitialized) {
                mFooterLayout
            } else {
                null
            }
        }


    // 获取data
    fun getData(): MutableList<T> {
        return data
    }

    // 设置/更新列表数据
    fun setData(list: Collection<T>?) {
        this.data.clear()
        if (!list.isNullOrEmpty()) {
            this.data.addAll(list)
        }
        notifyDataSetChanged()
    }

    // 添加数据
    fun addAll(newList: Collection<T>?) {
        if (newList.isNullOrEmpty()) {
            return
        }
        val lastIndex = data.size + headerLayoutCount
        if (this.data.addAll(newList)) {
            notifyItemRangeInserted(lastIndex, newList.size)
        }
    }

    // 清空数据
    fun clear() {
        this.data.clear()
        notifyDataSetChanged()
    }

    // 获取指定item
    fun getItem(@IntRange(from = 0) position: Int): T? {
        if (position >= data.size) {
            return null
        }
        return data[position]
    }

    // 获取item位置,如果返回-1,表示不存在
    fun getItemPosition(item: T?): Int {
        return if ((item != null && data.isNotEmpty())) {
            data.indexOf(item)
        } else {
            -1
        }
    }

    // 更新某一个位置上的数据
    fun updateItem(@IntRange(from = 0) position: Int, data: T) {
        if (position >= this.data.size) {
            return
        }
        this.data[position] = data
        notifyItemChanged(position + headerLayoutCount)
    }

    // 在某一个位置上添加一条数据
    fun setItem(@IntRange(from = 0) position: Int, data: T) {
        //如果超出则添加到最后
        val realPosition = if (position > this.data.size) {
            this.data.size
        } else {
            position
        }
        this.data.add(realPosition, data)
        notifyItemInserted(realPosition + headerLayoutCount)
    }

    // 增加一条数据到末尾
    fun addItem(data: T) {
        this.data.add(data)
        notifyItemInserted(this.data.size - 1 + headerLayoutCount)
    }

    // 移除某个位置上的数据
    fun removeAt(@IntRange(from = 0) position: Int): T? {
        if (position >= data.size) {
            return null
        }
        val remove = this.data.removeAt(position)
        notifyItemRemoved(position + headerLayoutCount)
        return remove
    }

    // 移除某个item数据,-1表示不存在该条数据
    fun remove(data: T): Int {
        val index = this.data.indexOf(data)
        if (index != -1) {
            removeAt(index)
        }
        return index
    }

}

2.3.2 ViewHolder 基类

open class BaseViewHolder(rootView: View) : RecyclerView.ViewHolder(rootView)
open class BaseBindViewHolder<B : ViewBinding>(val binding: B) : BaseViewHolder(binding.root)

2.3.3 实现例子
用一个蓝牙列表作为简单的例子吧

public class BluetoothListAdapter extends BaseRecyclerViewAdapter<BluetoothDevice, AdapterBluetoothItemBinding> {
    @NonNull
    @Override
    public AdapterBluetoothItemBinding getViewBinding(@NonNull LayoutInflater layoutInflater, @NonNull ViewGroup parent, int viewType) {
        return AdapterBluetoothItemBinding.inflate(layoutInflater,parent,false);
    }

    @Override
    protected void onBindDefViewHolder(@NonNull BaseBindViewHolder<AdapterBluetoothItemBinding> holder, @Nullable BluetoothDevice item, int position) {
        if(item==null){return;}
        AdapterBluetoothItemBinding binding = holder.getBinding();
        if(binding!=null){
            binding.deviceName.setText(item.getName());
            binding.deviceAddr.setText(item.getAddress());
        }
    }
}
2.4 ViewModel 基类

ViewModel 基类里面主要定义了三个基本方法
2.4.1 倒计时任务
由于北斗通信存在频度限制因此程序在每次执行通信操作之后需要进行倒计时,这里通过 flow 定义一个简单的倒计时方法

fun countDownCoroutines(
        total: Int,
        scope: CoroutineScope,
        onTick: (Int) -> Unit,
        onStart: (() -> Unit)? = null,
        onFinish: (() -> Unit)? = null,
    ): Job {
        return flow {
            for (i in total downTo 0) {
                emit(i)
                delay(1000)
            }
        }
            .flowOn(Dispatchers.Main)
            .onStart { onStart?.invoke() }
            .onCompletion { onFinish?.invoke() }  // 等效 java finally
            .onEach { onTick.invoke(it) }  // 每次倒计时时执行
            .launchIn(scope)
    }

2.4.2 获取数据方法
在子线程中获取数据

suspend fun <T> safeApiCallWithResult(
        responseBlock: suspend () -> T?
    ): T? {
        try {
            // 子线程执行请求函数获取数据 10秒超时
            val response = withContext(Dispatchers.IO) {
                withTimeout(10 * 1000) {
                    responseBlock()
                }
            } ?: return null  // 为空返回 null
            return response  // 返回数据
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }

2.4.3 获取数据并在主线程中处理数据

与第二个方法联合使用,现在子线程中获取数据再切回到主线程中进行UI刷新等任务,这种方法也适用于网络请求数据的情景但这个项目中并没有涉及到网络编程的部分因此获取数据操作大多都只是在数据库中获取数据

fun <T> launchUIWithResult(
        responseBlock: suspend () -> T?,  // 挂起函数拿到数据:无输入 → 输出T
        successBlock: (T?) -> Unit  // 处理函数
    ) {
        // 在主线程调用
        viewModelScope.launch(Dispatchers.Main) {
            val result = safeApiCallWithResult(responseBlock)  // 子线程获取数据
            result?.let { successBlock(it) }  // 主线程处理结果
        }
    }
2.5 Connector 基类

本项目的精华所在,通过分析各种通信链路总结归纳出来的通信链路连接器基类。
以下是各通信链路的连接流程:
低功耗蓝牙:判断权限 一> 开启扫描 一> 获取设备列表 一> 判断权限 一> 连接
经典蓝牙:判断权限 一> 开启扫描 一> 获取设备列表 一> 判断权限(配对) 一> 连接
USB Host: 获取设备列表 一> 判断权限 一> 连接
USB Accessory: 获取设备列表 一> 判断权限 一> 连接
Serial Port:判断权限 一> 获取可用地址列表 一> 判断权限 一> 打开
通过从以上流程大致可以归纳出以下几个共用方法:
2.5.1 获取可用设备列表
除蓝牙需要注册广播监听系统扫描结果逐个拿到子设备对象不符合以外其他连接方式都可以一次性获取到所有的子连接对象,但Serial Port在获取之前还需要作判断权限的前置操作因此再定义一个判断条件的前置方法

open suspend fun <T> getDevicesWithCondition(
        before: (()->Unit)?=null,
        condition: (()->Boolean),
        search: suspend ()->MutableList<T>?,
        after: (()->Unit)?=null
    ): MutableList<T>? {
        try {
            before?.let { it.invoke() }
            if(condition()){
                val devices = withContext(Dispatchers.IO){
                    withTimeout(10*1000){
                        search()
                    }
                }
                return if(devices!=null){
                    after?.invoke()
                    devices
                }else{
                    null
                }
                
            }else{
                Log.e(TAG, "获取设备条件不足!")
                return null
            }
        }catch (e:Exception){
            e.printStackTrace()
            return null
        }
    }

方法分析:若定义了前置准备工作则执行,再根据条件判断是否执行获取设备列表方法,若满足条件并获取设备成功则执行后置操作并返回设备对象,否则返回null

2.5.2 连接设备
所有的连接方式都是在连接设备之前先进行权限判断,因此像获取可用设备方法一样定义一个条件判断方法再决定是否执行连接操作即可

open fun <T> connectDeviceWithCondition(
        device: T,
        before: (()->Unit)?=null,  // 前置准备
        condition: (()->Boolean),  // 前置条件
        connectWithTransfer:(T)->Boolean,  // 连接操作
        conditionFail: (()->Unit)?=null,  // 条件检测失败
        success: (()->Unit)?=null,  // 成功
        fail: (()->Unit)?=null,  // 失败
    ){
        before?.let { it.invoke() }
        if(condition()){
            if(connectWithTransfer(device)){
                Log.e(TAG, "设备连接成功" )
                success?.let { it.invoke() }
            }else{
                Log.e(TAG, "设备连接失败" )
                fail?.let { it.invoke() }
            }
        }else{
            Log.e(TAG, "连接条件不足!")
            conditionFail?.let { it.invoke() }
        }
    }

方法分析:若定义了前置准备工作则执行,再根据条件判断是否执行连接设备操作,若满足条件并连接设备成功则执行连接成功后置操作,若连接设备失败则执行连接失败后置处理,若不满足连接条件则执行条件失败后置处理

2.5.3 根据条件自动连接设备
由于连接的设备都是公司自己研发的设备,往往会在项目说明文件中直接指出所需连接设备的设备标识,因此通常我们并不需要将获取可用设备列表和连接设备分开操作,而是在搜索设备完成后直接筛选设备并执行连接,所以这里增加一个获取可用设备和连接设备合并的方法

open fun <T> autoConnectDevice(
        before: (()->Unit)? = null,  // 前置准备
        searchCondition: (()->Boolean),  // 获取设备前置条件
        search: suspend ()->MutableList<T>?,  // 获取设备
        filtrationDevice:(MutableList<T>?)->T,  // 过滤设备
        connectionCondition: ()->Boolean,  // 连接设备
        connectDevice:(T)->Boolean,
        success: (()->Unit)?=null,  // 成功
        fail: (()->Unit)?=null,  // 失败
    ){
        GlobalScope.launch(Dispatchers.IO) {
            before?.let { it.invoke() }
            val devices = getDevicesWithCondition(condition = searchCondition, search = search)
            devices?.let {
                val device = filtrationDevice(it)
                connectDeviceWithCondition(device, condition = connectionCondition, connectWithTransfer = connectDevice, success = success, fail = fail)
            }
        }
    }

方法分析:若定义了前置准备工作则执行,后续执行前文的获取可用设备方法并筛选出目标设备再进行连接设备操作

2.5.4 子类需实现的抽象方法
connect(device:Any):连接设备
disconnect():断开连接
getDevices():获取可用设备
除了以上三个基本操作以外还有两个程序比较常用的操作
initDevice():每次在连接设备之后需要下发一些列的初始化指令和其他一系列初始化工作
sendMessage(targetCardNumber:String, type:Int, content_str:String):发送北斗消息,这也是最常用的功能之一并且根据设备种类的不同所需下发的协议也有所不同
完整基类

abstract class BaseConnector {

    var TAG = "BaseConnector"
    var isConnected = false  // 连接状态

    companion object{
        var connector:BaseConnector? = null  // 全局唯一连接器
        @JvmName("setConnectorJava")
        fun setConnector(connector: BaseConnector?){
            this.connector = connector
        }
    }

    abstract fun connect(device:Any)  // 连接设备
    abstract fun disconnect()  // 断开连接
    abstract suspend fun getDevices():List<Any>?  // 获取可用设备
    abstract fun initDevice()  // 初始化设备(下发指令)
    abstract fun sendMessage(targetCardNumber:String, type:Int, content_str:String)  // 发送消息


    // 连接设备
    open fun <T> connectDevice(
        device: T,
        before: (()->Unit)?=null,  // 前置准备
        connectWithTransfer:(T)->Boolean,  // 连接操作
        success: (()->Unit)?=null,  // 成功
        fail: (()->Unit)?=null,  // 失败
    ){
        before?.let { it.invoke() }
        if(connectWithTransfer(device)){
            Log.e(TAG, "设备连接成功" )
            success?.let { it.invoke() }
        }else{
            Log.e(TAG, "设备连接失败" )
            fail?.let { it.invoke() }
        }
    }

    // 连接设备(前置条件)
    open fun <T> connectDeviceWithCondition(
        device: T,
        before: (()->Unit)?=null,  // 前置准备
        condition: (()->Boolean),  // 前置条件
        connectWithTransfer:(T)->Boolean,  // 连接操作
        conditionFail: (()->Unit)?=null,  // 条件检测失败
        success: (()->Unit)?=null,  // 成功
        fail: (()->Unit)?=null,  // 失败
    ){
        before?.let { it.invoke() }
        if(condition()){
            if(connectWithTransfer(device)){
                Log.e(TAG, "设备连接成功" )
                success?.let { it.invoke() }
            }else{
                Log.e(TAG, "设备连接失败" )
                fail?.let { it.invoke() }
            }
        }else{
            Log.e(TAG, "连接条件不足!")
            conditionFail?.let { it.invoke() }
        }
    }


    // 从所有设备的列表中筛选并根据条件自动连接
    open fun <T> autoConnectDevice(
        before: (()->Unit)? = null,  // 前置准备
        searchCondition: (()->Boolean),  // 获取设备前置条件
        search: suspend ()->MutableList<T>?,  // 获取设备
        filtrationDevice:(MutableList<T>?)->T,  // 过滤设备
        connectionCondition: ()->Boolean,  // 连接设备
        connectDevice:(T)->Boolean,
        success: (()->Unit)?=null,  // 成功
        fail: (()->Unit)?=null,  // 失败
    ){
        GlobalScope.launch(Dispatchers.Main) {
            before?.let { it.invoke() }
            withContext(Dispatchers.IO){
                val devices = getDevicesWithCondition(condition = searchCondition, search = search)
                devices?.let {
                    val device = filtrationDevice(it)
                    connectDeviceWithCondition(device, condition = connectionCondition, connectWithTransfer = connectDevice, success = success, fail = fail)
                }
            }

        }
    }

    // 获取可用设备列表
    open suspend fun <T> getDevicesWithCondition(
        before: (()->Unit)?=null,
        condition: (()->Boolean),
        search: suspend ()->MutableList<T>?,
        after: (()->Unit)?=null
    ): MutableList<T>? {
        try {
            before?.let { it.invoke() }
            if(condition()){
                val devices = withContext(Dispatchers.IO){
                    withTimeout(10*1000){
                        search()
                    }
                }
                return if(devices!=null){
                    after?.invoke()
                    devices
                }else{
                    null
                }

            }else{
                Log.e(TAG, "获取设备条件不足!")
                return null
            }
        }catch (e:Exception){
            e.printStackTrace()
            return null
        }
    }


}

3. ViewModel

使用的 ViewModel 只有两个

3.1 CommunicationVM

存储联系人和聊天消息记录,主要在选择联系人和聊天页面使用

class CommunicationVM : BaseViewModel() {

    private val TAG = "CommunicationVM"
    private val contactList : MutableLiveData<MutableList<Contact>?> = MutableLiveData()  // 联系人列表
    private val messageList : MutableLiveData<MutableList<Message>?> = MutableLiveData()  // 消息列表

    // 获取联系人列表数据
    fun getContact() : LiveData<MutableList<Contact>?> {
        upDateContact()
        return contactList
    }
    fun upDateContact(){
        launchUIWithResult(
            responseBlock = {
                DaoUtils.getContacts()
            },
            successBlock = {
                it?.let { contacts ->
                    Log.e(TAG, "成功查询到: ${contacts.size} contact" )
                    contactList.value = contacts
                }
            }
        )
    }

    fun getMessage(number:String) : LiveData<MutableList<Message>?> {
        upDateMessage(number)
        return messageList
    }

    fun upDateMessage(number:String){
        launchUIWithResult(
            responseBlock = {
                DaoUtils.getMessages(number)
            },
            successBlock = {
                it?.let { messages ->
                    Log.e(TAG, "成功查询到: ${messages.size} messages" )
                    messageList.value = messages
                    // 清除未读消息数量
                    DaoUtils.getInstance().clearContactUnread(number)
                }
            }
        )
    }


}
3.2 MainVM

存储设备连接状态、实时设备参数等全局使用的变量

class MainVM : BaseViewModel() {

    val TAG = "MainVM"
    val isConnectDevice : MutableLiveData<Boolean?> = MutableLiveData()  // 是否连接设备
    val deviceCardID : MutableLiveData<String?> = MutableLiveData()  // 卡号
    val deviceCardFrequency : MutableLiveData<Int?> = MutableLiveData()  // 频度
    val deviceCardLevel : MutableLiveData<Int?> = MutableLiveData()  // 通信等级
    val deviceBatteryLevel : MutableLiveData<Int?> = MutableLiveData()  // 电量
    val signal : MutableLiveData<IntArray?> = MutableLiveData()  // 信号
    val deviceLongitude : MutableLiveData<Double?> = MutableLiveData()  // 设备经度
    val deviceLatitude : MutableLiveData<Double?> = MutableLiveData()  // 设备纬度
    val deviceAltitude : MutableLiveData<Double?> = MutableLiveData()  // 设备高度
    val waitTime : MutableLiveData<Int?> = MutableLiveData()  // 等待时间
    val unreadMessageCount : MutableLiveData<Int?> = MutableLiveData()  // 总未读消息数量

    val systemLongitude : MutableLiveData<Double?> = MutableLiveData()  // 系统经度
    val systemLatitude : MutableLiveData<Double?> = MutableLiveData()  // 系统纬度
    val systemAltitude : MutableLiveData<Double?> = MutableLiveData()  // 系统高度

    init {
        initDeviceParameter()
        initSystemParameter()
    }

    // 初始化默认参数
    fun initDeviceParameter(){
        isConnectDevice.postValue(false)
        deviceCardID.postValue("-")
        deviceCardFrequency.postValue(-1)
        deviceCardLevel.postValue(-1)
        deviceBatteryLevel.postValue(-1)
        signal.postValue(intArrayOf(0,0,0,0,0,0,0,0,0,0))
        waitTime.postValue(0)
        deviceLongitude.postValue(0.0)
        deviceLatitude.postValue(0.0)
        deviceAltitude.postValue(0.0)
    }

    fun initSystemParameter(){
        systemLongitude.postValue(0.0)
        systemLatitude.postValue(0.0)
        systemAltitude.postValue(0.0)
    }

    // 当前信号是否良好
    fun isSignalWell():Boolean{
        if(isConnectDevice.value==false){return false}
        val max_single = Arrays.stream(signal.value).max().orElse(0) // 拿到所有信号中的最大值
        return max_single>40  // 大于40可发消息
    }

    fun getUnreadMessageCount() : LiveData<Int?> {
        upDateUnreadMessageCount()
        return unreadMessageCount
    }
    fun upDateUnreadMessageCount(){
        launchUIWithResult(
            responseBlock = {
                DaoUtils.getContacts()
            },
            successBlock = {
                // 计算未读消息数量
                it?.let {  contacts ->
                    var count = 0
                    contacts.forEach {  contact ->
                        if(contact.unreadCount>0){
                            count += contact.unreadCount
                        }
                    }
                    unreadMessageCount.value = count
                }
            }
        )
    }

    private var countDownJob: Job? = null
    fun startCountDown(){
        if(deviceCardFrequency.value==-1){return}
        val frequency = deviceCardFrequency.value!!.plus(2)  // 总倒计时:频度+2秒
        countDownJob = countDownCoroutines(frequency, viewModelScope,
            onTick = { countDownSeconds ->
                waitTime.postValue(countDownSeconds)
            },
            onStart = {},
            onFinish = {countDownJob?.cancel()}
        )
    }
}

2. 基本功能实现

1. 页面跳转

项目使用路由框架实现页面跳转功能,整个项目中需要根据条件跳转至不同页面的地方只有一处,所以没有专门设置拦截事件,使用最基本的跳转功能即可满足需求
定义路由路径常量

// 路由路径 ---------------------------------------------------------
    public static final String MESSAGE_ACTIVITY = "/main/activity/message";
    public static final String STATE_ACTIVITY = "/main/activity/state";
    public static final String MAP_ACTIVITY = "/main/activity/map";
    public static final String HEALTHY_ACTIVITY = "/main/activity/healthy";
    public static final String SOS_ACTIVITY = "/main/activity/sos";
    public static final String SETTING_ACTIVITY = "/main/activity/setting";
    public static final String VOICE_AUTH_ACTIVITY = "/main/activity/setting/voice_auth";
    public static final String COMPRESSION_RATE_ACTIVITY = "/main/activity/setting/compression_rate";
    public static final String PLATFORM_SETTING_ACTIVITY = "/main/activity/setting/platform_setting";
    public static final String SWIFT_MESSAGE_ACTIVITY = "/main/activity/setting/swift_message";
    public static final String ABOUT_US_ACTIVITY = "/main/activity/setting/about_us";
    public static final String COMMUNICATION_LINK_ACTIVITY = "/main/activity/setting/communication_link";
    public static final String CONNECT_BLUETOOTH_ACTIVITY = "/main/activity/connect_bluetooth";
    public static final String CONNECT_USB_HOST_ACTIVITY = "/main/activity/connect_usb_host";
    public static final String CONNECT_USB_ACCESSORY_ACTIVITY = "/main/activity/connect_usb_accessory";
    public static final String CONNECT_SERIAL_PORT = "/main/activity/connect_serial_port";

页面编写完成后注册路由路径

@Route(path = Constant.MESSAGE_ACTIVITY)
class MessageActivity : BaseMVVMActivity<ActivityMessageBinding, CommunicationVM>(false)

页面跳转

// 直接跳转
ARouter.getInstance().build(Constant.COMMUNICATION_LINK_ACTIVITY).navigation()
// 带参跳转
ARouter.getInstance().build(Constant.COMMUNICATION_LINK_ACTIVITY).withInt("mode",0).navigation()

2. 通信链路

2.1 经典蓝牙通信

使用蓝牙socket传输数据时需要自订一套数据传输规则,实现较为复杂,可以参考我的另一篇文章:https://blog.csdn.net/lxt1292352578/article/details/135267832

public class BluetoothSocketTransferUtils {

    private static String TAG = "BluetoothSocketTransferUtils";
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothSocket bluetoothSocket;
    private BluetoothServerSocket bluetoothServerSocket;
    private BluetoothDevice nowDevice;
    private InputStream inputStream;
    private OutputStream outputStream;
    private ReceiveDataThread receiveDataThread;  // 接收数据线程
    private ListenThread listenThread;  // 监听连接线程

    private final UUID MY_UUID_SECURE = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");

    private int state = 0;
    private final int STATE_DISCONNECT = 0;
    private final int STATE_CONNECTING = 1;
    private final int STATE_CONNECTED = 2;

    public boolean isConnectedDevice = false;
    public boolean isSendFile = false;
// 单例 ----------------------------------------------------------------
    private static BluetoothSocketTransferUtils bluetoothSocketUtils;

    public static BluetoothSocketTransferUtils getInstance() {
        if (bluetoothSocketUtils == null) {
            bluetoothSocketUtils = new BluetoothSocketTransferUtils();
        }
        return bluetoothSocketUtils;
    }

    public BluetoothSocketTransferUtils() {
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    }

    public Set<BluetoothDevice> getPairedDeviceList(){
        return null;
    }


    public synchronized boolean connect(BluetoothDevice device) {
        Log.e(TAG, "连接设备: " + device.getName()+"/"+state);
        if (state == STATE_CONNECTING || state == STATE_CONNECTED) {
            GlobalControlUtils.INSTANCE.showToast("正在连接设备",0);return false;
        }
        Connection connection = new Connection(device);
        Thread connectThread = new Thread(connection);
        connectThread.start();
        try {
            connectThread.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
        boolean result = connection.getResult();
        return result;
    }

    private class Connection implements Runnable {
        private BluetoothDevice device;
        private boolean result;
        public Connection(BluetoothDevice device){
            this.device = device;
        }
        @Override
        public void run() {
            // 执行任务,这里简单地模拟一个耗时操作
            try {
                bluetoothSocket = device.createRfcommSocketToServiceRecord(MY_UUID_SECURE);
                state = STATE_CONNECTING;
                if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onConnecting();}
                bluetoothSocket.connect();
                inputStream = bluetoothSocket.getInputStream();
                outputStream = bluetoothSocket.getOutputStream();
                state = STATE_CONNECTED;
                isConnectedDevice = true;
                nowDevice = device;
                receiveDataThread = new ReceiveDataThread();
                receiveDataThread.start();  // 开启读数据线程
                if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onConnected(device.getName());}
                Log.e(TAG, "连接成功" );
                result = true;
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, "连接失败了" );
                disconnect();
                result = false;
            }
        }

        public boolean getResult() {
            return result;
        }
    }

    private byte[] readBuffer = new byte[1024*1024];
    private class ReceiveDataThread extends Thread{
        private boolean receive = false;
        byte[] buffer = new byte[1024*1024];
        @Override
        public void run() {
            if(inputStream==null){return;}
            receive = true;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while (receive){
                try{
                    int size = inputStream.read(buffer);
                    if(size>0){
                        baos.write(buffer, 0, size);
                        readBuffer = baos.toByteArray();

                        if(DispatcherExecutor.INSTANCE.getIOExecutor()!=null){
                            DispatcherExecutor.INSTANCE.getIOExecutor().execute(new Runnable() {@Override public void run() {
                                receiveData(readBuffer);
                            }});
                        }
                        baos.reset();
                    }else if(size==-1){
                        Log.e(TAG, "BluetoothSocket: 断开了");
                        cancel();
                        disconnect();
                        break;
                    }
                }catch (Exception e){
                    e.printStackTrace();
                    // 断开连接了
                    Log.e(TAG, "BluetoothSocket: 读取数据错误,断开连接");
                    cancel();
                    disconnect();
                }
            }
        }

        public void cancel(){
            receive = false;
        }
    }

    public void listen(){
        if(state!=STATE_DISCONNECT){return;}
        if(listenThread!=null){
            listenThread.cancel();
            listenThread = null;
        }
        listenThread = new ListenThread();
        listenThread.start();
    }

    private class ListenThread extends Thread{
        private boolean listen = false;
        public ListenThread(){
            try {
                bluetoothServerSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord("name", MY_UUID_SECURE);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        @Override
        public void run() {
            listen = true;
            Log.e(TAG, "开启设备连接监听"+listen+"/"+(state==STATE_DISCONNECT) );
            while (listen && state==STATE_DISCONNECT){
                try {
                    if(bluetoothSocket==null){
                        bluetoothSocket = bluetoothServerSocket.accept();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (bluetoothSocket != null) {
                    try {
                        Log.e(TAG, "监听到设备连接" );
                        state = STATE_CONNECTING;
                        if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onConnecting();}
                        inputStream = bluetoothSocket.getInputStream();
                        outputStream = bluetoothSocket.getOutputStream();
                        nowDevice = bluetoothSocket.getRemoteDevice();
                        receiveDataThread = new ReceiveDataThread();
                        receiveDataThread.start();  // 开启读数据线程
                        state = STATE_CONNECTED;
                        isConnectedDevice = true;
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        public void cancel(){
            listen = false;
            try {
                if (bluetoothServerSocket != null) {
                    bluetoothServerSocket.close();
                    bluetoothServerSocket = null;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void send_text(String data_str){
        if(outputStream==null){return;}
        if(isSendFile){return;}
        ExecutorService executorService = DispatcherExecutor.INSTANCE.getIOExecutor();
        if(executorService!=null){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        byte[] data_bytes = data_str.getBytes("GB18030");
                        String head = "$*1*$"+String.format("%08X", data_bytes.length);
                        outputStream.write(head.getBytes(StandardCharsets.UTF_8));
                        outputStream.write(data_bytes);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    // 下发北斗消息
    public void send_message(String targetCardNumber, int type, String content_str){
        send_text(DataUtils.hex2String(BDProtocolUtils.CCTCQ(targetCardNumber,type,content_str)));
        // 开始倒计时
        ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).startCountDown();
    }

    public final int SEND_FILE_AUDIO = 0;
    public final int SEND_FILE_PICTURE = 1;
    public void send_file(String path,int type){
        if(outputStream==null){return;}
        if(isSendFile){return;}
        ExecutorService executorService = DispatcherExecutor.INSTANCE.getIOExecutor();
        if(executorService!=null){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    File file = new File(path);
                    if (!file.exists() || !file.isFile()) {
                        Log.e(TAG, "文件不存在");
                        return;
                    }else {
                        GlobalControlUtils.INSTANCE.showToast("开始发送文件",0);
                        Log.e(TAG, "开始发送文件");
                        isSendFile = true;
                    }

                    byte[] file_byte = fileToBytes(path);
                    try {
                        Thread.sleep(500);
                        int bytesRead;
                        // 写入文件内容到输出流
                        String head;
                        if(type==SEND_FILE_AUDIO){head = "$*2*$"+String.format("%08X", file_byte.length);}
                        else {head = "$*3*$"+String.format("%08X", file_byte.length);}
                        Log.e(TAG, "head: "+head );
                        outputStream.write(head.getBytes(StandardCharsets.UTF_8));
                        outputStream.write(file_byte);
                        isSendFile = false;
                    } catch (Exception e) {
                        e.printStackTrace();
                        Log.e(TAG, "文件发送失败", e);
                        isSendFile = false;
                    }
                }
            });
        }
    }

    // 下发初始化指令
    public void init_device(){
        send_text(DataUtils.hex2String(BDProtocolUtils.CCPWD()));
        send_text(DataUtils.hex2String(BDProtocolUtils.CCICR(0,"00")));
        send_text(DataUtils.hex2String(BDProtocolUtils.CCRMO("PWI",2,5)));
        send_text(DataUtils.hex2String(BDProtocolUtils.CCRNS(0,0,0,0,0,0)));
    }

    public void disconnect(){
        try {
            if(receiveDataThread!=null){
                receiveDataThread.cancel();
                receiveDataThread = null;
            }
            if(inputStream!=null){
                inputStream.close();
                inputStream=null;
            }
            if(outputStream!=null){
                outputStream.close();
                outputStream=null;
            }
            if(bluetoothSocket!=null){
                bluetoothSocket.close();
                bluetoothSocket = null;
            }
            if(onBluetoothSocketWork!=null){onBluetoothSocketWork.onDisconnecting();}
            state = STATE_DISCONNECT;
            isConnectedDevice = false;
            nowDevice = null;
            initReceiveParameter();  // 初始化接收数据参数
//            listen();  // 断开后重新开启设备连接监听
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private boolean isStart = false;
    private ByteArrayOutputStream file_bytes_baos = new ByteArrayOutputStream();
    private long file_length = 0;  // 文件数据长度
    private int message_type = -1;  // 消息类型:-1-未知 0-设备指令 1-文本 2-语音 3-图片
    private Timer resetTimmer = null;
    private void initReceiveParameter(){
        isStart = false;
        file_bytes_baos.reset();
        file_length = 0;
        message_type = -1;
    }
    private synchronized void receiveData(byte[] data_bytes) {
        Log.e(TAG, "处理数据: "+data_bytes.length );
        if(!isStart){
            try{
                // $*1*$00339433
                String data_str = new String(data_bytes,StandardCharsets.UTF_8);
                int head_index = data_str.indexOf("$*");
                // 有头,开始接收
                if(head_index>=0){
                    isStart = true;
                    String head = data_str.substring(head_index,head_index+13);
                    String msg_type = head.substring(0,5);
                    if(msg_type.contains("1")){
                        message_type = 1;
                    }else if(msg_type.contains("2")){
                        message_type = 2;
                    }else if(msg_type.contains("3")){
                        message_type = 3;
                    }else if(msg_type.contains("0")){
                        message_type = 0;
                    }else {
                        message_type = -1;
                    }
                    String length_hex = head.substring(5);
                    file_length = Long.parseLong(length_hex,16);
                    Log.e(TAG, "收到头消息 head: "+head+" 文件数据长度:"+file_length);

                    file_bytes_baos.write(data_bytes,13,data_bytes.length-13);
                    // 如果是单次数据:文本
                    if(data_bytes.length==file_length+13){
                        parseData();
                    }
                }else {
                    Log.e(TAG, "receiveData: 没有头"+data_str );
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }else {
            try {
                file_bytes_baos.write(data_bytes);
                Log.e(TAG, "总长度: "+file_length+" /已接收长度:"+file_bytes_baos.size());
            } catch (IOException e) {
                e.printStackTrace();
            }
            if(file_bytes_baos.size()>=file_length){
                parseData();
            }
        }

        // 如果发送文件途中丢包或者发送文件时读取长度太长导致收到的数据一直达不到长度
        if(resetTimmer!=null){
            resetTimmer.cancel();
            resetTimmer = null;
        }
        resetTimmer = new Timer();
        resetTimmer.schedule(new TimerTask() {
            @Override
            public void run() {
                Log.e(TAG, "重置接收文件状态" );
                if(isStart){isStart = false;}
            }
        },3000);
    }

    public void parseData(){
        if(message_type==-1){
            initReceiveParameter();
            return;
        }
        if(message_type==1){
            String content = "";
            try {
                content = new String(file_bytes_baos.toByteArray(),"GB18030");
                Log.e(TAG, "数据接收完毕,文本:"+content);
            } catch (Exception e) {
                e.printStackTrace();
            }
            initReceiveParameter();
        }else if(message_type==2){
            Log.e(TAG, "数据接收完毕,语音" );
            // 保存语音数据
            try {
                String imgFilePath= FileUtils.getTransferImgFile()+"transfer" + DataUtils.getTimeSerial()+".pcm";
                File imageFile = new File(imgFilePath);
                try (FileOutputStream fos = new FileOutputStream(imageFile)) {
                    fos.write(file_bytes_baos.toByteArray());
                }
                initReceiveParameter();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if(message_type==3){
            Log.e(TAG, "数据接收完毕,图片" );
            try {
                String imgFilePath= FileUtils.getTransferImgFile()+"transfer" + DataUtils.getTimeSerial()+".jpg";
                File imageFile = new File(imgFilePath);
                try (FileOutputStream fos = new FileOutputStream(imageFile); FileChannel channel = fos.getChannel()) {
                    ByteBuffer buffer = ByteBuffer.wrap(file_bytes_baos.toByteArray());
                    channel.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                forceFilesystemCache(imgFilePath);
                initReceiveParameter();
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if(message_type==0){
            Log.e(TAG, "数据接收完毕,指令" );
            String content = "";
            try {
                content = new String(file_bytes_baos.toByteArray(),"GB18030");
                Log.e(TAG, "数据接收完毕,指令:"+content);
            } catch (Exception e) {
                e.printStackTrace();
            }
            initReceiveParameter();
            // 解析指令
            JsonElement jsonElement = new JsonParser().parse(content);
            // 检查JSON元素是否是JsonObject
            if (jsonElement.isJsonObject()) {
                // 将JsonElement转换为JsonObject
                JsonObject jsonObject = jsonElement.getAsJsonObject();
                // 获取特定键的值
                String number = jsonObject.get("targetNumber").getAsString();
                if(number!=null&&!number.equals("")){
                    Log.e(TAG, "收到指令,修改目标地址:"+number);
                }
            }

        }

    }
    private static void forceFilesystemCache(String filePath) {
        try (FileOutputStream fos = new FileOutputStream(new File(filePath), true);
             FileChannel channel = fos.getChannel()) {
            // 强制刷新文件系统缓存
            channel.force(true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static byte[] fileToBytes(String filePath){
        File file = new File(filePath);
        try (FileInputStream fis = new FileInputStream(file);
             ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024*1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            return bos.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }


// 接口 ---------------------------------------------
    public interface OnBluetoothSocketWork{
        void onConnecting();
        void onConnected(String device_name);
        void onDisconnecting();
        void onDiscoverNewDevice(List<BluetoothDevice> devices);
        void receiveData(byte[] data);
    }
    public OnBluetoothSocketWork onBluetoothSocketWork;
    public void setOnBluetoothSocketWork(OnBluetoothSocketWork onBluetoothSocketWork){
        this.onBluetoothSocketWork = onBluetoothSocketWork;
    }

}
2.2 USB Host通信

具体可以参考我的文章:https://blog.csdn.net/lxt1292352578/article/details/131976810

public class USBHostTransferUtils {

    private String TAG = "USBHostTransferUtil";
    private Application APP = ApplicationUtils.INSTANCE.getApplication();
    private UsbManager manager = (UsbManager) APP.getSystemService(Context.USB_SERVICE);  // usb管理器

    private BroadcastReceiver usbReceiver;  // 广播监听:判断usb设备授权操作
    private static final String INTENT_ACTION_GRANT_USB = "com.bdtx.main.INTENT_ACTION_GRANT_USB";  // usb权限请求标识
    private final String IDENTIFICATION = " USB-Serial Controller D";  // 目标设备标识

    private List<UsbSerialDriver> availableDrivers = new ArrayList<>();  // 所有可用设备
    private UsbSerialDriver usbSerialDriver;  // 当前连接的设备
    private UsbDeviceConnection usbDeviceConnection;  // 连接对象
    private UsbSerialPort usbSerialPort;  // 设备端口对象,通过这个读写数据
    private SerialInputOutputManager inputOutputManager;  // 数据输入输出流管理器

// 连接参数,按需求自行修改 ---------------------
    private int baudRate = 115200;  // 波特率
    private int dataBits = 8;  // 数据位
    private int stopBits = UsbSerialPort.STOPBITS_1;  // 停止位
    private int parity = UsbSerialPort.PARITY_NONE;// 奇偶校验

// 单例 -------------------------
    private static USBHostTransferUtils usbHostTransferUtil;
    public static USBHostTransferUtils getInstance() {
        if(usbHostTransferUtil == null){
            usbHostTransferUtil = new USBHostTransferUtils();
        }
        return usbHostTransferUtil;
    }


    // 注册usb授权监听广播
    public void registerReceiver(){
        usbReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if(INTENT_ACTION_GRANT_USB.equals(intent.getAction())) {
                    // 授权操作完成,连接
//                    boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);  // 不知为何获取到的永远都是 false 因此无法判断授权还是拒绝
                    Log.e(TAG, "授予权限");
                }
            }
        };
        APP.registerReceiver(usbReceiver,new IntentFilter(INTENT_ACTION_GRANT_USB));
    }

    public void setConnectionParameters(int baudRate,int dataBits,int stopBits,int parity){
        this.baudRate = baudRate;this.dataBits = dataBits;this.stopBits = stopBits;this.parity = parity;
    }

    // 刷新当前可用 usb设备
    public List<UsbSerialDriver> refreshDevice(){
        availableDrivers.clear();
        availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
        Log.e(TAG, "当前可用 usb 设备数量: " + availableDrivers.size() );
        // 有设备可以连接
        if(availableDrivers.size() >= 0){
            GlobalControlUtils.INSTANCE.showToast("当前可连接设备:"+availableDrivers.size(),0);
            return availableDrivers;
        }
        // 没有设备
        else {
            GlobalControlUtils.INSTANCE.showToast("请先接入设备",0);
            return null;
        }
    }

    // 检查设备权限
    public boolean checkDevicePermission(UsbSerialDriver usbSerialDriver){
        boolean hasPermission = manager.hasPermission(usbSerialDriver.getDevice());
        if(!hasPermission){
            // 申请设备权限
            int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0;
            PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(APP, 0, new Intent(INTENT_ACTION_GRANT_USB), flags);
            manager.requestPermission(usbSerialDriver.getDevice(), usbPermissionIntent);
            GlobalControlUtils.INSTANCE.showToast("请先授予连接权限",0);
        }
        return hasPermission;
    }

    // 连接设备
    public boolean connectDevice(UsbSerialDriver usbSerialDriver){
        this.usbSerialDriver = usbSerialDriver;
        usbSerialPort = usbSerialDriver.getPorts().get(0);  // 一般设备的端口都只有一个,具体要参考设备的说明文档
        usbDeviceConnection = manager.openDevice(usbSerialDriver.getDevice());  // 拿到连接对象
        if(usbSerialPort == null){return false;}
        try {
            usbSerialPort.open(usbDeviceConnection);  // 打开串口
            usbSerialPort.setParameters(baudRate, dataBits, stopBits, parity);  // 设置串口参数:波特率 - 115200 , 数据位 - 8 , 停止位 - 1 , 奇偶校验 - 无
            return startReceiveData();  // 开启读数据线程
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "连接错误" );
            return false;
        }
    }

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    private byte[] readBuffer = new byte[1024 * 2];  // 缓冲区
    // 开启数据接收监听
    public boolean startReceiveData(){
        if(usbSerialPort == null || !usbSerialPort.isOpen()){return false;}
        inputOutputManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() {
            @Override
            public void onNewData(byte[] data) {
            // 在这里处理接收到的 usb 数据 -------------------------------
                // 拼接处理
                baos.write(data,0,data.length);
                readBuffer = baos.toByteArray();
                if (readBuffer.length >= 2 && readBuffer[readBuffer.length - 2] == (byte)'\r' && readBuffer[readBuffer.length - 1] == (byte)'\n') {
                    String data_str = DataUtils.bytes2string(readBuffer);
                    String data_hex = DataUtils.bytes2Hex(readBuffer);
                    Log.i(TAG, "收到 usb 数据: " + data_str);

                    String[] data_hex_array = data_hex.split("0d0a");  // 分割后处理
                    for (String s : data_hex_array) {
                        String s_str = DataUtils.hex2String(s);
                        Pattern pattern = Pattern.compile("FKI|ICP|ICI|TCI|PWI|SNR|GGA|GLL|PRX|RNX|ZDX|TXR");
                        Matcher matcher = pattern.matcher(s_str);
                        if (matcher.find()) {
                            BDProtocolUtils.getInstance().parseData(s_str);
                        }
                    }
                    baos.reset();  // 重置
                }
            }
            @Override
            public void onRunError(Exception e) {
                Log.e(TAG, "usb 断开了" );
                disconnect();
                e.printStackTrace();
            }
        });
        inputOutputManager.start();
        return true;
    }

    // 下发数据:建议使用线程池
    public void write(String data_hex){
        if(usbSerialPort != null){
            Log.e(TAG, "当前usb状态: isOpen-" + usbSerialPort.isOpen() );
            // 当串口打开时再下发
            if(usbSerialPort.isOpen()){
                byte[] data_bytes = DataUtils.hex2bytes(data_hex);  // 将字符数据转化为 byte[]
                if (data_bytes == null || data_bytes.length == 0) return;
                try {
                    usbSerialPort.write(data_bytes,0);  // 写入数据,延迟设置太大的话如果下发间隔太小可能报错
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }else {
                GlobalControlUtils.INSTANCE.showToast("usb 未连接" ,0);
            }
        }
    }

    // 下发北斗消息
    public void sendMessage(String targetCardNumber, int type, String content_str){
        ExecutorService executorService = DispatcherExecutor.INSTANCE.getIOExecutor();
        if(executorService!=null){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    write(BDProtocolUtils.CCTCQ(targetCardNumber,type,content_str));
                    // 开始倒计时
                    ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).startCountDown();
                }
            });
        }
    }

    // 下发初始化指令
    public void init_device(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    sleep(300);
                    write(BDProtocolUtils.CCPWD());  // 登录
                    sleep(300);
                    write(BDProtocolUtils.CCICR(0,"00"));  // 查询ic信息
                    sleep(300);
                    write(BDProtocolUtils.CCRMO("PWI",2,5));  // 北三信号间隔 5
                    sleep(300);
                    write(BDProtocolUtils.CCRNS(0,0,0,0,0,0));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }


    // 断开连接
    public void disconnect(){
        try{
            // 停止数据接收监听
            if(inputOutputManager != null){
                inputOutputManager.stop();
                inputOutputManager = null;
            }
            // 关闭端口
            if(usbSerialPort != null){
                usbSerialPort.close();
                usbSerialPort = null;
            }
            // 关闭连接
            if(usbDeviceConnection != null){
                usbDeviceConnection.close();
                usbDeviceConnection = null;
            }
            // 清除设备
            if(usbSerialDriver != null){
                usbSerialDriver = null;
            }
            // 清空设备列表
            availableDrivers.clear();
            // 注销广播监听
            if(usbReceiver != null){
                APP.unregisterReceiver(usbReceiver);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }



}
2.3 USB Accessory通信

具体可以参考我的文章:https://blog.csdn.net/lxt1292352578/article/details/132061922

public class USBAccessoryTransferUtils {

    String TAG = "USBAccessoryTransferUtil";
    private Application APP = ApplicationUtils.INSTANCE.getApplication();  // 主程序
    public UsbManager usbManager = (UsbManager) APP.getSystemService(Context.USB_SERVICE);

    private BroadcastReceiver usbAccessoryReceiver = null;  // 广播监听:判断设备授权操作
    public UsbAccessory usbAccessory = null;  // 当前连接的 USB附件 对象
    public ParcelFileDescriptor fileDescriptor = null;
    public FileInputStream inputStream = null;  // 输入流
    public FileOutputStream outputStream = null;  // 输出流
    public ReadThread readThread = null;  // 接收数据线程

    private final String ACTION_USB_PERMISSION = "com.bdtx.main.INTENT_ACTION_GRANT_USB_ACCESSORY";  // usb权限请求标识
    private final String IDENTIFICATION = "WCHAccessory1";  // 目标设备的序列号标识

// 单例 -------------------------------------------------------------------
    private static USBAccessoryTransferUtils usbAccessoryTransferUtil;
    public static USBAccessoryTransferUtils getInstance() {
        if(usbAccessoryTransferUtil == null){
            usbAccessoryTransferUtil = new USBAccessoryTransferUtils();
        }
        return usbAccessoryTransferUtil;
    }


    // 注册usb授权监听广播
    public void registerReceiver(){
        usbAccessoryReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                Log.e(TAG, "onReceive: "+action);
                // 收到 ACTION_USB_PERMISSION 请求权限广播
                if (ACTION_USB_PERMISSION.equals(action)) {
                    // 确保只有一个线程执行里面的任务,不与其他应用冲突
                    synchronized (this) {
                        usbAccessory = (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
                        if(usbAccessory==null){Log.e(TAG, "usbAccessory 对象为空" );return;}
                        // 判断是否授予了权限
                        boolean havePermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
                        if (havePermission) {
                            GlobalControlUtils.INSTANCE.showToast("授予 USB 权限", Toast.LENGTH_SHORT);
                        }
                        else {
                            GlobalControlUtils.INSTANCE.showToast("拒绝 USB 权限", Toast.LENGTH_SHORT);
                        }
                    }
                }
                // 收到 USB附件 拔出的广播
                else if (UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {  // android.hardware.usb.action.USB_ACCESSORY_DETACHED
                    // 断开连接
                    disconnect();
                }
                else {
                    Log.e(TAG, "registerReceiver/onReceive其它:"+action);
                }
            }
        };
        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);  // 当收到 usb附件 拔出广播动作
        APP.registerReceiver(usbAccessoryReceiver, filter);  // 注册
    }

    public List<UsbAccessory> refreshDevice(){
        UsbAccessory[] accessories = usbManager.getAccessoryList();
        if(accessories==null || accessories.length<1){
            return new ArrayList<UsbAccessory>();
        }else {
            Log.e(TAG, "获取 UsbAccessory 数量: "+accessories.length );
            return Arrays.asList(accessories);
        }
    }

    // 连接设备
    public boolean connectDevice(UsbAccessory usbAccessory){
        boolean result = false;
//        if(!checkDevice(usbAccessory)){return;}
        this.usbAccessory = usbAccessory;
        fileDescriptor = usbManager.openAccessory(usbAccessory);
        if(fileDescriptor != null){
            FileDescriptor fd = fileDescriptor.getFileDescriptor();
            // 拿到输入/输出流
            inputStream = new FileInputStream(fd);
            outputStream = new FileOutputStream(fd);
            // 开启接收数据线程
            readThread = new ReadThread();
            readThread.start();
            result = true;
        }
        return result;
    }

    public boolean checkPermission(UsbAccessory usbAccessory){
        boolean hasPermission = usbManager.hasPermission(usbAccessory);
        if(!hasPermission){
            synchronized (usbAccessoryReceiver) {
                GlobalControlUtils.INSTANCE.showToast("请授予 USB 权限", Toast.LENGTH_SHORT);
                PendingIntent pendingIntent = PendingIntent.getBroadcast(APP, 0, new Intent(ACTION_USB_PERMISSION), 0);
                usbManager.requestPermission(usbAccessory,pendingIntent);
            }
        }
        return hasPermission;
    }

    public String ManufacturerString = "mManufacturer=WCH";
    public String ModelString1 = "mModel=WCHUARTDemo";
    public String VersionString = "mVersion=1.0";
    public boolean checkDevice(UsbAccessory usbAccessory){
        if( -1 == usbAccessory.toString().indexOf(ManufacturerString)) {
            GlobalControlUtils.INSTANCE.showToast("Manufacturer is not matched!", Toast.LENGTH_SHORT);
            return false;
        }
        if( -1 == usbAccessory.toString().indexOf(ModelString1) ) {
            GlobalControlUtils.INSTANCE.showToast("Model is not matched!", Toast.LENGTH_SHORT);
            return false;
        }
        if( -1 == usbAccessory.toString().indexOf(VersionString)) {
            GlobalControlUtils.INSTANCE.showToast("Version is not matched!", Toast.LENGTH_SHORT);
            return false;
        }
        GlobalControlUtils.INSTANCE.showToast("制造商、型号和版本匹配", Toast.LENGTH_SHORT);
        return true;
    }


    // 下发数据(16进制字符串)
    public void write(String data_hex) {
        if(outputStream==null){return;}
        try {
            byte[] data_bytes = DataUtils.hex2bytes(data_hex);
            this.outputStream.write(data_bytes);
            Log.e(TAG, "write 下发的指令是: " + DataUtils.hex2String(data_hex) );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 下发北斗消息
    public void sendMessage(String targetCardNumber, int type, String content_str){
        ExecutorService executorService = DispatcherExecutor.INSTANCE.getIOExecutor();
        if(executorService!=null){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    write(BDProtocolUtils.CCTCQ(targetCardNumber,type,content_str));
                    // 开始倒计时
                    ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).startCountDown();
                }
            });
        }
    }


    // 下发初始化指令
    public void init_device(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(300);
                    write(BDProtocolUtils.CCRMO("PWI",2,9));  // 设置pwi信号输出频度
                    Thread.sleep(300);
                    write(BDProtocolUtils.CCRMO("MCH",1,0));  // 关闭设备的HCM指令输出
                    Thread.sleep(300);
                    write(BDProtocolUtils.CCRNS(5,5,5,5,5,5));  // 设置rn指令输出频度
                    Thread.sleep(300);
                    write(BDProtocolUtils.CCICR(0,"00"));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }



    // 断开连接
    public void disconnect(){
        try {
            // 停止数据监听
            if(readThread != null){
                readThread.close();
                readThread = null;
            }
            // 关闭输入输出流
            if(inputStream != null){
                inputStream.close();
                inputStream = null;
            }
            if(outputStream != null){
                outputStream.close();
                outputStream = null;
            }
            if(fileDescriptor != null){
                fileDescriptor.close();
                fileDescriptor = null;
            }
            // 清除设备
            if(usbAccessory != null){
                usbAccessory = null;
            }
            // 注销广播
            if(usbAccessoryReceiver != null){
                APP.unregisterReceiver(usbAccessoryReceiver);
                usbAccessoryReceiver = null;
            }

        }catch (Exception e){
            e.printStackTrace();
        }

    }


    // 读取 USB附件 数据线程
    private byte[] readBuffer = new byte[1024 * 2];  // 缓冲区
    private class ReadThread extends Thread {
        boolean alive = true;
        ReadThread(){
            this.setPriority(Thread.MAX_PRIORITY);  // 设置线程的优先级:最高级
        }

        byte[] buf = new byte[2048];  // 每次从输入流读取的最大数据量:这个大小直接影响接收数据的速率,根据需求修改
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        public void run() {
            if(inputStream == null){return;}
//            init_device();  // 下发初始化指令
            Log.e(TAG, "开启数据监听");
            while(alive) {
                try {
                    int size = inputStream.read(buf);
                    if(size>0){
                        baos.write(buf,0,size);
                        readBuffer = baos.toByteArray();
                        // 根据需求设置停止位:由于我需要接收的是北斗指令,指令格式最后两位为 “回车换行(\r\n)” 所以只需要判断数据末尾两位
                        // 设置停止位,当最后两位为 \r\n 时就传出去
                        if (readBuffer.length >= 2 && readBuffer[readBuffer.length - 2] == (byte)'\r' && readBuffer[readBuffer.length - 1] == (byte)'\n') {
                            String data_str = DataUtils.bytes2string(readBuffer);
                            String data_hex = DataUtils.bytes2Hex(readBuffer);
                            Log.i(TAG, "收到 usb_accessory 数据: " + data_str);
                            String[] data_hex_array = data_hex.split("0d0a");  // 分割后处理
                            for (String s : data_hex_array) {
                                String s_str = DataUtils.hex2String(s);
                                Pattern pattern = Pattern.compile("FKI|ICP|ICI|TCI|PWI|SNR|GGA|GLL|PRX|RNX|ZDX|TXR");
                                Matcher matcher = pattern.matcher(s_str);
                                if (matcher.find()) {
                                    BDProtocolUtils.getInstance().parseData(s_str);
                                }
                            }
                            baos.reset();  // 重置
                        }
                    }
                    sleep(10);  // 设置循环间隔
                } catch (Throwable var3) {
                    if(var3.getMessage() != null){
                        Log.e(TAG, "ReadThread:" + var3.getMessage());
                    }
                    return;
                }
            }
        }

        public void close(){
            alive = false;
            this.interrupt();
        }
    }

}
2.4 Serial Port串口通信

使用第三方串口连接库实现,可以参考文章:https://blog.csdn.net/qq_36270361/article/details/105405075

public class SerialPortTransferUtils {

    private String TAG = "SerialPortTransferUtils";
    private String path = "/dev/ttyS1";  // 串口地址
    private int baudrate = 115200;  // 波特率
    private int stopBits = 1;  // 停止位
    private int dataBits = 8;  // 数据位
    private int parity = 0;  // 校验位
    private int flowCon = 0;
    private int flags = 0;
    private SerialPort serialPort = null;
    private OutputStream outputStream;  // 输出流,写入数据
    private InputStream inputStream;  // 输入流,读取数据
    private ReadThread readThread;  // 读数据线程
    // 单例 -------------------------------------------------------------------
    private static SerialPortTransferUtils serialPortTransferUtils;
    public static SerialPortTransferUtils getInstance() {
        if(serialPortTransferUtils == null){
            serialPortTransferUtils = new SerialPortTransferUtils();
        }
        return serialPortTransferUtils;
    }

    // 设置串口参数,一般只有这两个参数需要改变
    public void setSerialPortParameters(String path,int baudrate){
        this.path = path; this.baudrate = baudrate;
    }
    public boolean openSerialPort(){
        boolean result = false;
        try{
            serialPort = new SerialPort(new File(path), baudrate, stopBits, dataBits, parity, flowCon, flags);  // 打开串口
            outputStream = serialPort.getOutputStream();  // 拿到输出流
            inputStream = serialPort.getInputStream();  // 拿到输出流
            readThread = new ReadThread();  // 开启读数据线程
            readThread.start();
            result = true;
            Log.e(TAG, "打开串口成功" );
        }catch (Exception e){
            result = false;
            Log.e(TAG, "打开串口失败" );
            e.printStackTrace();
        }
        return result;
    }

    public List<String> getSerialPortPaths(){
        try{
            SerialPortFinder serialPortFinder = new SerialPortFinder();
            String[] allDevicesPath = serialPortFinder.getAllDevicesPath();
            if(allDevicesPath==null || allDevicesPath.length<1){
                return new ArrayList<String>();
            } else {
                return Arrays.asList(allDevicesPath);
            }
        }catch (Exception e){
            e.printStackTrace();
            return new ArrayList<String>();
        }
    }

    // 下发数据(16进制字符串)
    public void write(String data_hex) {
        if(outputStream==null){return;}
        try {
            byte[] data_bytes = DataUtils.hex2bytes(data_hex);
            this.outputStream.write(data_bytes);
            Log.e(TAG, "write 下发的指令是: " + DataUtils.hex2String(data_hex) );
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 下发北斗消息
    public void sendMessage(String targetCardNumber, int type, String content_str){
        ExecutorService executorService = DispatcherExecutor.INSTANCE.getIOExecutor();
        if(executorService!=null){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    write(BDProtocolUtils.CCTCQ(targetCardNumber,type,content_str));
                    // 开始倒计时
                    ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).startCountDown();
                }
            });
        }
    }

    public void init_device(){
        ExecutorService executorService = DispatcherExecutor.INSTANCE.getIOExecutor();
        if(executorService!=null){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        write(BDProtocolUtils.CCPWD());  // 登录
                        sleep(300);
                        write(BDProtocolUtils.CCICR(0,"00"));  // 查询ic信息
                        sleep(300);
                        write(BDProtocolUtils.CCRMO("PWI",2,5));  // 北三信号间隔 5
                        sleep(300);
                        write(BDProtocolUtils.CCRNS(0,0,0,0,0,0));
                    }catch (Exception e){
                        e.printStackTrace();
                    }


                }
            });
        }
    }


    public void close(){
        try {
            // 停止数据监听
            if(readThread != null){
                readThread.close();
                readThread = null;
            }
            // 关闭输入输出流
            if(inputStream != null){
                inputStream.close();
                inputStream = null;
            }
            if(outputStream != null){
                outputStream.close();
                outputStream = null;
            }
            // 关闭串口
            if(serialPort != null){
                serialPort.close();
                serialPort = null;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private byte[] readBuffer = new byte[1024 * 4];
    private class ReadThread extends Thread {
        boolean alive = true;
        public void run() {
            super.run();
            if(inputStream == null){return;}
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Log.e(TAG, "开启数据监听");
            while(alive) {
                try {
                    if (inputStream.available() > 0) {
                        byte[] buf = new byte[10];  // 原来 2048,这个大小直接影响接收数据的速率,例如:当byte[10]时可能出现很长时间不输出,一次性输出多条指令
                        int size = inputStream.read(buf);
                        baos.write(buf,0,size);
                        readBuffer=baos.toByteArray();
                        if (readBuffer.length >= 2 && readBuffer[readBuffer.length - 2] == (byte)'\r' && readBuffer[readBuffer.length - 1] == (byte)'\n') {
                            String data_str = DataUtils.bytes2string(readBuffer);
                            String data_hex = DataUtils.bytes2Hex(readBuffer);
                            Log.i(TAG, "收到 usb_accessory 数据: " + data_str);
                            String[] data_hex_array = data_hex.split("0d0a");  // 分割后处理
                            for (String s : data_hex_array) {
                                String s_str = DataUtils.hex2String(s);
                                Pattern pattern = Pattern.compile("FKI|ICP|ICI|TCI|PWI|SNR|GGA|GLL|PRX|RNX|ZDX|TXR");
                                Matcher matcher = pattern.matcher(s_str);
                                if (matcher.find()) {
                                    BDProtocolUtils.getInstance().parseData(s_str);
                                }
                            }
                            baos.reset();  // 重置
                        }
                    }
                    sleep(10);
                } catch (Throwable var3) {
                    if(var3.getMessage() != null){
                        Log.e("error", var3.getMessage());
                    }
                    return;
                }
            }
        }

        public void close(){
            alive = false;
            this.interrupt();
        }
    }

}
2.5 低功耗蓝牙通信

这里在连接过程中做了针对公司设备特征值自动筛选处理

public class BluetoothTransferUtils {

    private static String TAG = "BluetoothTransferUtil";
    private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

    private Application APP;

    private List<BluetoothDevice> devices = new ArrayList();  // 扫描到的设备
    private static final UUID ID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");  // 由设备决定,不同设备要改变
    private int max_size = 20;  // 每次传输数据的最大大小,连接设备时拿到,如果发的数据大于这个大小就要拆包
    private int max_mtu = 503;  // 设置传输数据的最大值:原 200
    private BluetoothGatt bluetoothGatt = null;
    private BluetoothGattCharacteristic writeCharacteristic = null;
    private boolean isSetNotification = false;  // 是否设置了可通知特征值

    private volatile boolean data_send_complete = true;  // 数据发送完成标识
    private Queue<byte[]> queue = new LinkedList();  // 消息队列
    private Queue<byte[]> remainQueue = null;  // 长度太长的消息被拆包后的队列,先把这个发完再发上面的
    public String deviceAddress = "";


// 单例 --------------------------------------------------
    private static BluetoothTransferUtils bluetoothTransferUtil;
    public static BluetoothTransferUtils getInstance() {
        if (bluetoothTransferUtil == null) {
            bluetoothTransferUtil = new BluetoothTransferUtils();
        }
        return bluetoothTransferUtil;
    }

    public BluetoothTransferUtils(){
        APP = ApplicationUtils.INSTANCE.getApplication();
//        registerBroadcast();
    }

    // 直接用 主程序 作为context
    public void registerBroadcast() {
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.bluetooth.device.action.FOUND");
        filter.addAction("android.bluetooth.adapter.action.DISCOVERY_FINISHED");
        filter.addAction("android.bluetooth.device.action.ACL_CONNECTED");
        filter.addAction("android.bluetooth.device.action.ACL_DISCONNECTED");
        APP.registerReceiver(receiver, filter);
        Log.e(TAG, "广播注册成功");
    }

    // 蓝牙连接监听广播
    private final BroadcastReceiver receiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if ("android.bluetooth.device.action.FOUND".equals(action)) {
                BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra("android.bluetooth.device.extra.DEVICE");
                if (device.getName() == null) {return;}
                // 蓝牙是否已配对,没配对才操作
                if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
                    if (!devices.contains(device)) {
                        devices.add(device);
                        if(onBluetoothWork!=null){onBluetoothWork.onScanningResult(devices, device);}
                    }
                }
            }
        }
    };

    // 开始扫描
    public void startDiscovery() {
        if(bluetoothAdapter==null){return;}
        if (bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.cancelDiscovery();
        }
        bluetoothAdapter.startDiscovery();
    }
    // 停止扫描
    public void stopDiscovery() {
        if (bluetoothAdapter != null && bluetoothAdapter.isDiscovering()) {
            devices.clear();
            bluetoothAdapter.cancelDiscovery();
        }
    }

    // 蓝牙回调
    private final BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        // 连接状态改变
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.e(TAG, "onConnectionStateChange:蓝牙状态 " + status + " // "+newState );
            if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothGatt.STATE_CONNECTED) {
                Log.e(TAG, "蓝牙连接");
                // 设置传输速率
                if(!setMtu()){
                    bluetoothGatt.discoverServices();
                };
            }
            // 蓝牙自动断了
            else if (status == BluetoothGatt.GATT_SERVER && newState == BluetoothGatt.STATE_DISCONNECTED) {
                // 重连
                disconnectDevice();
                connectDevice(deviceAddress);
            }
            // 蓝牙手动断开
            else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                Log.e(TAG, "蓝牙断开");
                disconnectDevice();
            }

        }

        // 在 bluetoothGatt.requestMtu(max_mtu); 改变mtu时触发,开始连接服务
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
            Log.e(TAG, "onMtuChanged:改变 MTU "+status + " // " + mtu);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                max_size = mtu - 3;  // 拿到最大的分包大小
            } else {
                Log.e(TAG, "改变 MTU 失败");
            }
            gatt.discoverServices();
        }

        // 连接设备成功后发现服务时
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            Log.e(TAG, "onServicesDiscovered:发现服务 "+status);
            if(status == BluetoothGatt.GATT_SUCCESS){
                List<BluetoothGattService> servicesList = gatt.getServices();
                analysisGattService(gatt, servicesList);  // 根据设备选择读和写
                if (onBluetoothWork != null) {
                    onBluetoothWork.onConnectSucceed();
                }
                ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).isConnectDevice().postValue(true);
                Log.e(TAG, "平台号码: "+ MMKV.defaultMMKV().decodeInt(Constant.SYSTEM_NUMBER));
            }else {
                Log.e(TAG, "onServicesDiscovered:发现服务失败");
            }
        }
        // 写入描述符 gatt.writeDescriptor(descriptor); 时被调用
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            Log.e(TAG + " onDescriptorWrite", "状态" + status);
            super.onDescriptorWrite(gatt, descriptor, status);
            Log.e(TAG, " onDescriptorWrite:写入描述符 " + status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                init_device();  // 下发初始化指令
            } else {
                Log.e(TAG, "onDescriptorWrite:写入描述符失败");
            }
        }

        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            // 接收数据处理
            if(DispatcherExecutor.INSTANCE.getIOExecutor()!=null){
                DispatcherExecutor.INSTANCE.getIOExecutor().execute(new Runnable() {
                    @Override
                    public void run() {
                        receiveData(characteristic);
                    }
                });
            }
        }

        // 写入数据 write(byte[] data) 方法里面 bluetoothGatt.writeCharacteristic 时被调用,每次下发数据后判断 remainQueue 拆包后的数据有没有下发完
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.e(TAG, "onCharacteristicWrite: 下发数据 "+status );
            if (remainQueue != null && !remainQueue.isEmpty()) {
                byte[] send_bytes = (byte[]) remainQueue.remove();  // 从队列取出消息
                write(send_bytes);
            } else {
                try {
                    Thread.sleep(150L);
                } catch (InterruptedException var5) {
                    var5.printStackTrace();
                }
                data_send_complete = true;  // 队列消息发送完了,修改标识
                sendNext();
            }
        }

        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyUpdate(gatt, txPhy, rxPhy, status);
            Log.e(TAG, "onPhyUpdate: 状态 "+status );
        }
    };

    // 通过设备连接
    public boolean connectDevice(final BluetoothDevice device) {
        stopDiscovery();
        deviceAddress = device.getAddress();
        // 子线程连接
        new Thread() {
            public void run() {
                if (bluetoothGatt != null) {
                    bluetoothGatt.close();
                    bluetoothGatt = null;
                }
                if (Build.VERSION.SDK_INT >= 23) {
//                    bluetoothGatt = device.connectGatt(APP, false, bluetoothGattCallback);
                    bluetoothGatt = device.connectGatt(APP, false, bluetoothGattCallback, BluetoothDevice.TRANSPORT_LE);  // 低功耗
                } else {
                    bluetoothGatt = device.connectGatt(APP, false, bluetoothGattCallback);
                }
            }
        }.start();
        return true;
    }

    // 通过地址连接
    public void connectDevice(final String address) {
        stopDiscovery();
        deviceAddress = address;
        // 子线程连接
        new Thread() {
            public void run() {
                if (bluetoothAdapter != null && deviceAddress != null && !deviceAddress.equals("")) {
                    BluetoothDevice device;
                    if ((device = bluetoothAdapter.getRemoteDevice(deviceAddress)) != null) {
                        if (bluetoothGatt != null) {
                            bluetoothGatt.close();
                            bluetoothGatt = null;
                        }
                        bluetoothGatt = device.connectGatt(APP, false, bluetoothGattCallback);
                    }
                }
            }
        }.start();
    }

    public boolean setMtu() {
        if(bluetoothGatt==null) return false;
        if (Build.VERSION.SDK_INT >= 21) {
            return bluetoothGatt.requestMtu(max_mtu);
        }
        return false;
    }

    // 解析服务:根据已知的设备uuid拿到他的读写特征值
    private void analysisGattService(BluetoothGatt gatt, List<BluetoothGattService> servicesList) {
        Iterator var3 = servicesList.iterator();
        while (var3.hasNext()) {
            BluetoothGattService service = (BluetoothGattService) var3.next();
            String uuid = service.getUuid().toString();
            // 如果以 "0000180" 开头就不要
            if (uuid.startsWith("0000180")) {
                continue;
            }
            Log.e(TAG, "服务UUID:" + uuid);
            List writes = (List) writeSC.get(uuid);
            List notifys = (List) notifySC.get(uuid);
            if (writes == null || notifys == null) {
                continue;
            }
            List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
            Iterator var9 = characteristics.iterator();
            while (var9.hasNext()) {
                BluetoothGattCharacteristic characteristic = (BluetoothGattCharacteristic) var9.next();
                String characteristicUUID = characteristic.getUuid().toString();
                Log.e(TAG, "特征值:" + characteristicUUID);
                int charaProp = characteristic.getProperties();
                // ((charaProp & 4) > 0 || (charaProp & 8) > 0) 用于判断这个特征值是否可写
                if (((charaProp & 4) > 0 || (charaProp & 8) > 0) && writes.contains(characteristicUUID) && writeCharacteristic == null) {
                    writeCharacteristic = characteristic;
                    Log.e(TAG, "可写特征值:" + characteristicUUID);
                }
                if (((charaProp & 16) > 0 || (charaProp & 32) > 0) && notifys.contains(characteristicUUID) && !isSetNotification) {
                    BluetoothGattDescriptor descriptor = characteristic.getDescriptor(ID);
                    if (descriptor != null) {
                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);  // 启用特征值的通知功能
                        gatt.writeDescriptor(descriptor);
                        gatt.setCharacteristicNotification(characteristic, true);
                        isSetNotification = true;
                        Log.e(TAG, "可通知特征值:" + characteristicUUID);
                    }
                }
            }
        }
    }

    // 下发北斗消息
    public void sendMessage(String targetCardNumber, int type, String content_str){
        ExecutorService executorService = DispatcherExecutor.INSTANCE.getIOExecutor();
        if(executorService!=null){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    write(BDProtocolUtils.CCTCQ(targetCardNumber,type,content_str));
                    // 开始倒计时
                    ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).startCountDown();
                }
            });
        }
    }

    public void write(byte[] data_bytes) {
        if(writeCharacteristic==null || bluetoothGatt==null) return;
        writeCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
        writeCharacteristic.setValue(data_bytes);
        bluetoothGatt.writeCharacteristic(writeCharacteristic);
        FileUtils3.recordBDLog(FileUtils.getLogFile(),"send_BD:"+ DataUtils.bytes2string(data_bytes));  // 记录日志文件
        Log.e(TAG, "Bluetooth 下发数据: " + DataUtils.bytes2string(data_bytes) );
    }

    public void write(String data_hex) {
        if (writeCharacteristic == null || bluetoothGatt == null) return;
        byte[] data_bytes = DataUtils.hex2bytes(data_hex);
        queue.offer(data_bytes);
        sendNext();
    }

    private synchronized void sendNext() {
        if (data_send_complete) {
            if (queue.size() == 0) return;
            data_send_complete = false;
            send((byte[]) queue.poll());
        }
    }

    protected synchronized void send(byte[] bytes) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (bytes != null) {
                    // 判断有没有超出最大的限制
                    if (bytes.length > max_size) {
                        List<byte[]> list = splitPackage(bytes, max_size);
                        remainQueue = new ConcurrentLinkedQueue();
                        remainQueue.addAll(list);
                        byte[] send_bytes = (byte[]) remainQueue.remove();
                        write(send_bytes);
                    } else {
                        write(bytes);
                        try {Thread.sleep(150L);} catch (Exception var4) {}
                        data_send_complete = true;
                        sendNext();
                    }
                }
            }
        }).start();

    }

    // 将字节数组按照指定的大小拆分成一个数组列表
    public static List<byte[]> splitPackage(byte[] src, int size) {
        List<byte[]> list = new ArrayList<>();
        int loop = (src.length + size - 1) / size;

        for (int i = 0; i < loop; i++) {
            int from = i * size;
            int to = Math.min(from + size, src.length);
            byte[] chunk = new byte[to - from];
            System.arraycopy(src, from, chunk, 0, to - from);
            list.add(chunk);
        }

        return list;
    }

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    private byte[] readBuffer = new byte[1024 * 2];  // 缓冲区
    // 这里处理接收到的数据
    private void receiveData(BluetoothGattCharacteristic characteristic) {
//        Log.e(TAG, "receiveData: 接收数据" );
        byte[] data = characteristic.getValue();
        baos.write(data,0,data.length);
        readBuffer = baos.toByteArray();
        // 根据需求设置停止位:由于我需要接收的是北斗指令,指令格式最后两位为 “回车换行(\r\n)” 所以我只需要判断数据末尾两位
        // 设置停止位,当最后两位为 \r\n 时就传出去
        if (readBuffer.length >= 2 && readBuffer[readBuffer.length - 2] == (byte)'\r' && readBuffer[readBuffer.length - 1] == (byte)'\n') {

            String data_str = DataUtils.bytes2string(readBuffer);
            String data_hex = DataUtils.bytes2Hex(readBuffer);
            Log.i(TAG, "收到蓝牙数据: " + data_str);

            String[] data_hex_array = data_hex.split("0d0a");  // 分割后处理
            for (String s : data_hex_array) {
                String s_str = DataUtils.hex2String(s);
                Pattern pattern = Pattern.compile("FKI|ICP|ICI|TCI|PWI|SNR|GGA|GLL|PRX|RNX|ZDX|TXR");
                Matcher matcher = pattern.matcher(s_str);
                if (matcher.find()) {
                    BDProtocolUtils.getInstance().parseData(s_str);
                }
            }
            baos.reset();  // 重置
        }

    }

    // 断开连接
    public void disconnectDevice(){
        try {
            if(remainQueue!=null){
                remainQueue.clear();
                remainQueue = null;
            }
            queue.clear();
            data_send_complete = true;
            isSetNotification = false;
            writeCharacteristic = null;
            devices.clear();
            if(bluetoothGatt!=null){
                bluetoothGatt.disconnect();
                bluetoothGatt.close();
                bluetoothGatt = null;
            }
            if (onBluetoothWork != null) {
                onBluetoothWork.onDisconnect();
            }
            FileUtils3.recordBDLog(FileUtils.getLogFile(),"****** 断开北斗设备连接 ******");
//            ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).isConnectDevice().postValue(false);
            ApplicationUtils.INSTANCE.getGlobalViewModel(MainVM.class).initDeviceParameter();  // 直接初始化所有连接参数
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void init_device(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Log.e(TAG, "设备初始化" );
                    sleep(500);  // 一定要延迟一点再下发!否则发送指令设备不接收导致登录失败
                    write(BDProtocolUtils.CCPWD());  // 登录
                    sleep(300);
                    write(BDProtocolUtils.CCICR(0,"00"));  // 查询ic信息
                    sleep(300);
                    write(BDProtocolUtils.CCRMO("PWI",2,5));  // 北三信号间隔 5
                    sleep(300);
                    write(BDProtocolUtils.CCZDC(5));  // 修改盒子信息输出频度
                    sleep(300);
                    write(BDProtocolUtils.CCPRS());  // 关闭盒子自带上报
                    sleep(300);
                    // 测试,先
                    write(BDProtocolUtils.CCRNS(5,0,5,0,5,0));  // rn输出频度,只用到GGA和GLL其它关闭减少蓝牙负荷
//                    write(BDProtocolUtils.CCRNS(0,0,0,0,0,0));
                    sleep(300);
                    write(BDProtocolUtils.CCRMO("MCH",1,0));  // 星宇关掉mch输出
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();

    }

    public void onDestroy() {
        stopDiscovery();
        disconnectDevice();
        bluetoothTransferUtil = null;
    }

    public void unregister() {
        try {
            if (APP != null && receiver != null) {
                APP.unregisterReceiver(receiver);
                Log.e(TAG, "注销广播" );
            }
        } catch (Exception var3) {
            var3.printStackTrace();
        }
    }

// 设备特征值兼容 --------------------------------------------------------------------
    private static Map<String, List<String>> writeSC = new HashMap();  // 写
    private static Map<String, List<String>> notifySC = new HashMap();  // 读
    // 键
    private static String FFE0 = "0000ffe0-0000-1000-8000-00805f9b34fb";
    private static String FFF0 = "0000fff0-0000-1000-8000-00805f9b34fb";
    private static String FFE5 = "0000ffe5-0000-1000-8000-00805f9b34fb";
    private static String A002 = "0000a002-0000-1000-8000-00805f9b34fb";
    private static String E400001 = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
    // 值
    private static String FFE1 = "0000ffe1-0000-1000-8000-00805f9b34fb";  // 写:FFE0  读:FFE0、FFE5
    private static String FFF2 = "0000fff2-0000-1000-8000-00805f9b34fb";  // 写:FFF0
    private static String FFF3 = "0000fff3-0000-1000-8000-00805f9b34fb";  // 写:FFF0
    private static String FFE9 = "0000ffe9-0000-1000-8000-00805f9b34fb";  // 写:FFE5
    private static String FFF1 = "0000fff1-0000-1000-8000-00805f9b34fb";  // 写:FFE5  读:FFF0
    private static String C302 = "0000c302-0000-1000-8000-00805f9b34fb";  // 写:A002
    private static String E400002 = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";  // 写:E400001
    private static String FFE2 = "0000ffe2-0000-1000-8000-00805f9b34fb";  // 读:FFE0
    private static String FFE4 = "0000ffe4-0000-1000-8000-00805f9b34fb";  // 读:FFE0
    private static String FFF4 = "0000fff4-0000-1000-8000-00805f9b34fb";  // 读:FFF0
    private static String C305 = "0000c305-0000-1000-8000-00805f9b34fb";  // 读:A002
    private static String E400003 = "6e400003-b5a3-f393-e0a9-e50e24dcca9e";  // 读:E400001
    static {
        List<String> w_ffe0 = new ArrayList();
        w_ffe0.add(FFE1);
        writeSC.put(FFE0, w_ffe0);
        List<String> w_fff0 = new ArrayList();
        w_fff0.add(FFF2);
        w_fff0.add(FFF3);
        writeSC.put(FFF0, w_fff0);
        List<String> w_ffe5 = new ArrayList();
        w_ffe5.add(FFE9);
        w_ffe5.add(FFE1);
        writeSC.put(FFE5, w_ffe5);
        List<String> w_a002 = new ArrayList();
        w_a002.add(C302);
        writeSC.put(A002, w_a002);
        List<String> w_6E400001 = new ArrayList();
        w_6E400001.add(E400002);
        writeSC.put(E400001, w_6E400001);

        List<String> r_ffe0 = new ArrayList();
        r_ffe0.add(FFE1);
        r_ffe0.add(FFE2);
        r_ffe0.add(FFE4);
        notifySC.put(FFE0, r_ffe0);
        List<String> r_fff0 = new ArrayList();
        r_fff0.add(FFF4);
        r_fff0.add(FFF1);
        notifySC.put(FFF0, r_fff0);
        List<String> r_6E400001 = new ArrayList();
        r_6E400001.add(E400003);
        notifySC.put(E400001, r_6E400001);
        List<String> r_a002 = new ArrayList();
        r_a002.add(C305);
        notifySC.put(A002, r_a002);
        List<String> r_ffe5 = new ArrayList();
        r_ffe5.add(FFE1);
        notifySC.put(FFE5, r_ffe5);
    }

// 接口 ---------------------------------------
    public onBluetoothWork onBluetoothWork;
    public interface onBluetoothWork {
        void onScanningResult(List<BluetoothDevice> devices, BluetoothDevice new_device);
        void onConnectSucceed();
        void onConnectError();
        void onDisconnect();
        void sendDataCallback(int var1);
        void onReceiveData(String data_hex);
    }
    public void setOnBluetoothWork(onBluetoothWork onBluetoothWork){
        this.onBluetoothWork = onBluetoothWork;
    }


}

3. 指令解析

根据通信链路中获取的 北斗/设备 指令解析出当前的卫星参数、定位信息和设备状况等。在这里正好可以使用我最近完成的指令解析SDK:https://blog.csdn.net/lxt1292352578/article/details/137019548

设置SDK解析数据反馈监听

ProtocolParser.getInstance().setParameterListener(object : ParameterListener{
            override fun OnBDParameterChange(p0: BD_Parameter?) {
                p0?.let {
                    Log.e(TAG, "北斗参数变化: 卡号-${p0.CardID} 等级-${p0.CardLevel} 频度-${p0.CardFrequency}")
                }
            }
            override fun OnDeviceAParameterChange(p0: XYParameter?) {
                p0?.let {
                    Log.e(TAG, "A型设备参数变化: ${p0.toString()}")
                }
            }
            override fun OnDeviceBParameterChange(p0: FDParameter?) {
                p0?.let {
                    Log.e(TAG, "B型设备参数变化: ${p0.toString()}")
                }
            }
            override fun OnCommandFeedback(p0: CommandFeedback?) {
                p0?.let {
                    Log.e(TAG, "北斗指令反馈: ${p0.toString()}")
                }
            }
            override fun OnMessageReceived(p0: ReceivedMessage?) {
                p0?.let {
                    Log.e(TAG, "接收卫星消息: ${p0.toString()}")
                }
            }
            override fun OnBDLocationChange(p0: BD_Location?) {
                p0?.let {
                    Log.e(TAG, "卫星定位改变: 经度-${p0.Longitude} 纬度-${p0.Latitude} 椭球高度-${p0.EllipsoidHeight}")
                }
            }
            override fun OnSatelliteStatusChange(p0: SatelliteStatus?) {
                p0?.let {
                    Log.e(TAG, "卫星状态参数变化: ${p0.toString()}")
                }
            }
            override fun OnCommandResponse(p0: ResponseCommand?) {
                p0?.let {
                    Log.e(TAG, "下发指令响应: ${p0.toString()}")
                }
            }
            override fun OnCommandUnresolved(p0: UnresolvedCommand?) {
                p0?.let {
                    Log.e(TAG, "未解析协议: ${p0.toString()}")
                }
            }
        })

数据解析类源码

public class ProtocolParser {

    private static String TAG = "ProtocolParser";

    private static ProtocolParser parser;
    public static ProtocolParser getInstance() {
        if(parser == null){
            parser = new ProtocolParser();
        }
        return parser;
    }

    private BD_Parameter bd_parameter;  // 北斗参数
    private XYParameter xyParameter;  // XY设备参数
    private FDParameter fdParameter;  // FD设备参数
    private CommandFeedback commandFeedback;  // FKI反馈信息
    private ReceivedMessage receivedMessage;  // 接收到的通信信息
    private BD_Location bd_location;  // 北斗位置
    private SatelliteStatus satelliteStatus;  // 卫星状态
    public ProtocolParser(){
        Init();
    }

    // 初始化数据,需要在每次断开连接时进行一次数据初始化
    public void Init(){
        bd_parameter = new BD_Parameter();
        xyParameter = new XYParameter();
        fdParameter = new FDParameter();
        commandFeedback = new CommandFeedback();
        receivedMessage = new ReceivedMessage();
        bd_location = new BD_Location();
        satelliteStatus = new SatelliteStatus();
    }

    // 执行回调传出参数
    private void BDParameterChange(){
        if(parameterListener!=null){parameterListener.OnBDParameterChange(bd_parameter);}
    }
    private void XYParameterChange(){
        if(parameterListener!=null){parameterListener.OnDeviceAParameterChange(xyParameter);}
    }
    private void FDParameterChange(){
        if(parameterListener!=null){parameterListener.OnDeviceBParameterChange(fdParameter);}
    }
    private void CommandFeedback(){
        if(parameterListener!=null){parameterListener.OnCommandFeedback(commandFeedback);}
    }
    private void ReceivedMessage(){
        if(parameterListener!=null){parameterListener.OnMessageReceived(receivedMessage);}
    }
    private void LocationChange(){
        if(parameterListener!=null){parameterListener.OnBDLocationChange(bd_location);}
    }
    private void SatelliteStatusChange(){
        if(parameterListener!=null){parameterListener.OnSatelliteStatusChange(satelliteStatus);}
    }
    private void ReceivedResponseCommand(ResponseCommand command){
        if(parameterListener!=null){parameterListener.OnCommandResponse(command);}
    }
    private void ReceivedUnresolvedCommand(UnresolvedCommand command){
        if(parameterListener!=null){parameterListener.OnCommandUnresolved(command);}
    }

    public void showLog(boolean show){Variable.showLog = show;}

    private void loge(String log){
        if(Variable.showLog){
            Log.e(TAG, log);
        }
    }
    private void logi(String log){
        if(Variable.showLog){
            Log.i(TAG, log);
        }
    }



    // 处理碎片化数据
    private StringBuilder dataBuilder = new StringBuilder();
    public void parseData_fragment(String str) {
        dataBuilder.append(str);
        int startIndex = dataBuilder.indexOf("$");
        if (startIndex < 0) {
            dataBuilder.setLength(0);  // 重置 StringBuilder
            return;
        }
        if (startIndex > 0) {
            dataBuilder.delete(0, startIndex);  // 删除 $ 之前的数据
        }
        if (dataBuilder.length() < 6) return;
        int endIndex = dataBuilder.indexOf("*");
        if (endIndex < 1) return;
        String intactData = dataBuilder.substring(0, endIndex + 3);
        dataBuilder.delete(0, endIndex + 3);
        parseData(intactData);
    }

    // 处理完整指令
    // 蓝牙收到数据时已经开启了线程池接收,已在子线程中
    public void parseData(String intactData) {
        loge("需要解析的原始数据:"+intactData);
        int xorIndex = intactData.indexOf("*");
        if (xorIndex == -1) return;
        String data_str = intactData.substring(0, xorIndex);
        if (!data_str.contains(",")) return;
        String[] value = data_str.split(",", -1);
        // 北斗标准
        if (data_str.contains("ICP")) BDICP(value);
        else if (data_str.contains("FKI")) BDFKI(value);
        else if (data_str.contains("PWI")) BDPWI(value);
        else if (data_str.contains("TCI") || data_str.startsWith("TXR")) BDMessage(value);
        // 其他自定义
        else if (data_str.contains("ICI")) BDICI(value);  // 北二
        else if (data_str.contains("SNR")) BDSNR(value);
        // XY
        else if (data_str.contains("ZDX")) BDZDX(value);
        else if (data_str.contains("BDVRX")) BDVRX(value);
        else if (data_str.contains("BDRSX")) BDRSX(value);
        else if (data_str.contains("BDDEF")) BDDEF(value);
        else if (data_str.contains("BDBTX")) BDBTX(value);
        else if (data_str.contains("BDUGX")) BDUGX(value);
        else if (data_str.contains("BDPRX")) BDPRX(value);
        else if (data_str.contains("BDYPX")) BDYPX(value);
        else if (data_str.contains("BDMDX")) BDMDX(value);
        else if (data_str.contains("BDHMX")) BDHMX(value);
        else if (data_str.contains("BDQDX")) BDQDX(value);
        else if (data_str.contains("BDTRA")) BDTRA(value);
        else if (data_str.contains("BDZTX")) BDZTX(value);
        else if (data_str.contains("BDRNX")) BDRNX(value);
        else if (data_str.contains("BDOKX")) BDOKX(value);
        else if (data_str.contains("BDPWX")) BDPWX(value);
        // FD
        else if (data_str.contains("DWZXX")) DWZXX(value);
        else if (data_str.contains("DBJXX")) DBJXX(value);
        else if (data_str.contains("DMSXX")) DMSXX(value);
        else if (data_str.contains("DDLXX")) DDLXX(value);
        else if (data_str.contains("DPWXX")) DPWXX(value);
        else if (data_str.contains("DBBXX")) DBBXX(value);
        else if (data_str.contains("BCZQXX")) BCZQXX(value);
        else if (data_str.contains("DLYXX")) DLYXX(value);
        else if (data_str.contains("DYJXX")) DYJXX(value);
        else if (data_str.contains("DLYRN")) DLYRN(value);
        else if (data_str.contains("BDWAX")) BDWAX(value);
        // RN
        else parseRNSS(value);
    }

    // 解析 RNSS 位置数据
    private void parseRNSS(String[] value){
        try {
            // 拆分数据
            String head = value[0];
            if (head.contains("GGA")){
                if (value[6].equals("0")) return;  // 0 就是无效定位,不要
                bd_location.Time = value[1];
                bd_location.Latitude = DataUtils.analysisLonlat(value[2]);
                bd_location.LatitudeIndication = value[3];
                bd_location.Longitude = DataUtils.analysisLonlat(value[4]);
                bd_location.LongitudeIndication = value[5];
                bd_location.Valid = Integer.parseInt(value[6]);
                bd_location.NoSV = Integer.parseInt(value[7]);
                bd_location.HDOP = Double.parseDouble(value[8]);
                bd_location.EllipsoidHeight = Double.parseDouble(value[9]);
                bd_location.UnitOfEH = value[10];
                bd_location.Altref = Double.parseDouble(value[11]);
                bd_location.UnitOfAltref = value[12];
                LocationChange();
            } else if (head.contains("GLL")){
                if (value[6].equals("V")) return;  // V - 无效  A - 有效
                bd_location.Latitude = DataUtils.analysisLonlat(value[1]);
                bd_location.LatitudeIndication = value[2];
                bd_location.Longitude = DataUtils.analysisLonlat(value[3]);
                bd_location.LongitudeIndication = value[4];
                bd_location.Time = value[5];
                bd_location.Valid = value[6].equals("A")? 1:0;
                LocationChange();
            } else if (head.contains("GSA")){
                // [$GNGSA, A, 1, , , , , , , , , , , , , , , , 1]
                satelliteStatus.Smode = value[1];
                satelliteStatus.FS = Integer.parseInt(value[2]);
                int[] Satellite = {0,0,0,0,0,0,0,0,0,0,0,0};
                for (int i = 3; i < 15; i++) {
                    if(value[i]==null||value[i].equals("")){
                        Satellite[i-3] = 0;
                    } else {
                        Satellite[i-3] = Integer.parseInt(value[i]);
                    }
                }
                satelliteStatus.PositioningSatellite = Satellite;
                if(!value[15].equals("")){satelliteStatus.PDOP = Double.parseDouble(value[15]);}
                if(!value[16].equals("")){satelliteStatus.HDOP = Double.parseDouble(value[16]);}
                if(!value[17].equals("")){satelliteStatus.VDOP = Double.parseDouble(value[17]);}
            } else if (head.contains("GSV")){
                // $GPGSV,3,1,10,01,,,41,02,38,034,42,03,36,113,47,07,38,195,25,1*5B
                // $GPGSV,3,3,10,20,25,214,,22,68,187,*7A
                satelliteStatus.NoMsg = Integer.parseInt(value[1]);
                satelliteStatus.MsgNo = Integer.parseInt(value[2]);
                satelliteStatus.NoSv = Integer.parseInt(value[3]);
                if(value.length>=7){
                    int[] sv1_4 = {0,0,0,0};
                    for (int i = 4; i < 8; i++) {
                        sv1_4[i-4] = (value[i]!=null&&!value[i].equals(""))? Integer.parseInt(value[i]):0;
                    }
                    satelliteStatus.SV1_4 = sv1_4;
                }
                if(value.length>=11){
                    int[] elv1_4 = {0,0,0,0};
                    for (int i = 8; i < 12; i++) {
                        elv1_4[i-8] = (value[i]!=null&&!value[i].equals(""))? Integer.parseInt(value[i]):0;
                    }
                    satelliteStatus.ELV1_4 = elv1_4;
                }
                if(value.length>=15){
                    int[] az1_4 = {0,0,0,0};
                    for (int i = 12; i < 16; i++) {
                        az1_4[i-12] = (value[i]!=null&&!value[i].equals(""))? Integer.parseInt(value[i]):0;
                    }
                    satelliteStatus.AZ1_4 = az1_4;
                }
                if(value.length>=19){
                    int[] cno1_4 = {0,0,0,0};
                    for (int i = 16; i < 20; i++) {
                        cno1_4[i-16] = (value[i]!=null&&!value[i].equals(""))? Integer.parseInt(value[i]):0;
                    }
                    satelliteStatus.CNO1_4 = cno1_4;
                }
                SatelliteStatusChange();
            } else if (head.contains("RMC")){
                // $GNRMC,070723.00,A,2309.30979,N,11330.00659,E,0.08,,070324,,,A,V*29
                //  [$GNRMC, 093021.000, A, 2309.32392, N, 11330.03365, E, 3.64, 354.48, 080324, , , A]
                if (value[2].equals("V")) return;  // V - 无效  A - 有效
                bd_location.Time = value[1];
                bd_location.Valid = value[2].equals("A")? 1:0;
                bd_location.Latitude = DataUtils.analysisLonlat(value[3]);
                bd_location.LatitudeIndication = value[4];
                bd_location.Longitude = DataUtils.analysisLonlat(value[5]);
                bd_location.LongitudeIndication = value[6];
                bd_location.Speed = Double.parseDouble(value[7]) * 1.852;
                if(!value[8].equals("")){bd_location.COG = Double.parseDouble(value[8]);}
                bd_location.Date = value[9];
                LocationChange();
            } else if (head.contains("ZDA")){
                // $GNZDA,070728.00,07,03,2024,00,00*72
                satelliteStatus.Time = value[1];
                String day = value[2];
                String month = value[3];
                String year = "24";
                if(value[4].length()>=2){
                    year = value[4].substring(value[4].length()-2);
                }
                satelliteStatus.Date = day+month+year;
                SatelliteStatusChange();
            } else {
                UnresolvedCommand command = new UnresolvedCommand();
                command.RawCommand = value;
                ReceivedUnresolvedCommand(command);
                loge("收到其他指令: "+ Arrays.toString(value));
                return;
            }
        }catch (Exception e){
            loge("parseRNSS 解析错误: "+ Arrays.toString(value));
            e.printStackTrace();
            return;
        }

    }

// 解析协议 -------------------------------------------------------------------------------
    // 标准北斗 -----------------------------------------------------
    // ICP 卡号、频度等
    // $BDICP,15240507,2032472,1,3,0,N,1,Y,1,0,0,3,1,1,5,1,1,2,2,500,100,10,10*65
    private void BDICP(String[] value){
        try {
            String cardId = value[1];
            int cardFre = Integer.parseInt(value[14]);
            int cardLevel = -1;
            if(Integer.parseInt(value[15]) == 0){
                cardLevel = 5;  // 0就是5级卡
            } else {
                cardLevel = Integer.parseInt(value[15]);
            }
            loge("BDICP 解析设备信息: 卡号-"+cardId+" 频度-"+cardFre+" 等级-"+cardLevel );
            bd_parameter.CardID = cardId;
            bd_parameter.CardLevel = cardLevel;
            bd_parameter.CardFrequency = cardFre;
            bd_parameter.BroadcastID = value[2];
            bd_parameter.AuthorizationType = Integer.parseInt(value[3]);
            bd_parameter.UserIdentity = Integer.parseInt(value[4]);
            bd_parameter.ServiceIdentity = Integer.parseInt(value[5]);
            bd_parameter.EnableConfidentiality = value[6];
            bd_parameter.DeviceType = Integer.parseInt(value[7]);
            bd_parameter.IsRescue = value[8];
            bd_parameter.Military = Integer.parseInt(value[9]);
            bd_parameter.Civil_Military = Integer.parseInt(value[10]);
            bd_parameter.Allies = Integer.parseInt(value[11]);
            bd_parameter.UserPriority = Integer.parseInt(value[12]);
            bd_parameter.ServiceCapabilities = Integer.parseInt(value[13]);
            bd_parameter.GlobalIdentity = Integer.parseInt(value[16]);
            bd_parameter.GlobalMessageInteractionPermissions = Integer.parseInt(value[17]);
            bd_parameter.GlobalMessageFrequency = Integer.parseInt(value[18]);
            bd_parameter.GlobalMessageLevel = Integer.parseInt(value[19]);
            bd_parameter.NumberOfSubordinateUsers = Integer.parseInt(value[20]);
            bd_parameter.GroupPermissions = Integer.parseInt(value[21]);
            bd_parameter.NumberOfSelfBuiltGroups = Integer.parseInt(value[22]);
            bd_parameter.NumberOfGroupsToJoin = Integer.parseInt(value[23]);
            BDParameterChange();
        }catch (Exception e){
            loge("BDICP: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // 通信申请后的反馈信息
    // [$BDFKI, TXA, N, Y, 4, 0000]  北二
    // [$BDFKI, 080432, TCQ, Y, 0, 0]  北三
    private void BDFKI(String[] value){
        try {
            String time = value[1];
            String type = value[2];  // 指令类型
            boolean message_result = false;
            String result = value[3];  // 反馈结果
            String reason = value[4];  // 失败原因
            String reason_str = "";
            int remain = Integer.parseInt(value[5]);
            if(result.equals("Y")){
                message_result = true;
            } else {
                message_result = false;
                switch ( reason ){
                    case "1":
                        reason_str = "频率未到";break;
                    case "2":
                        reason_str = "发射抑制";break;
                    case "3":
                        reason_str = "发射静默";break;
                    case "4":
                        reason_str = "功率未锁定";break;
                    case "5":
                        reason_str = "未检测到IC模块信息";break;
                    case "6":
                        reason_str = "权限不足";break;
                    default:
                        reason_str = "未知原因";
                        break;
                }
            }
            commandFeedback.Time = time;
            commandFeedback.Type = type;
            commandFeedback.Result = message_result;
            commandFeedback.Reason = reason;
            commandFeedback.Reason_str = reason_str;
            commandFeedback.Remain = remain;
            CommandFeedback();
        }catch (Exception e){
            loge("BDFKI: 解析错误");
            e.printStackTrace();
        }
    }

    // PWI 功率信息
    // $BDPWI,000000.00,00,01,51,40,33,0*7B
    private void BDPWI(String[] value){
        try {
            int rdss2Count1 = Integer.parseInt(value[2]);
            int index = 2+rdss2Count1*3+1;
            int rdss3Count = Integer.parseInt(value[index]);
            index++;
            int s21[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
            for (int i =0 ;i < rdss3Count; i++){
                int id = Integer.parseInt(value[index]);
                if (id > 21) continue;
                int number = Integer.parseInt(value[index+1]);
                s21[id-1] = number;
                if(value.length>index+3 && (value[index+3].equals("0") || value[index+3].equals(""))){
                    index += 4;
                } else {
                    index += 3;
                }
            }
            loge("BDPWI 解析设备信号: "+Arrays.toString(s21) );
            bd_parameter.Signal = s21;
            BDParameterChange();
        } catch (Exception e){
            loge("BDPWI: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // [$BDTCI, 04207733, 4207733, 2, 023242, 2, 0, 90000000000065BEF749B2E2CAD4]
    // $BDTXR,1,4207733,1,2337,90000000000065C1FAF4B2E2CAD4*3F
    private void BDMessage(String[] value){
        try {
            int from = 0;  // 带了个0,先转化为int
            String to = "0";
            int frequency_point = 0;
            String time = "000000";
            int encode_type = 0;
            String content = "";
            if(value[0].contains("TCI")){
                from = Integer.parseInt(value[1]);
                to = value[2];
                frequency_point = Integer.parseInt(value[3]);
                time = value[4];
                encode_type = Integer.parseInt(value[5]);
                content = value[7];
            }else if(value[0].contains("TXR")){
                from = Integer.parseInt(value[2]);
                encode_type = Integer.parseInt(value[3]);
                content = value[5];
            }
            receivedMessage.SendID = from+"";
            receivedMessage.ReceiveID = to;
            receivedMessage.FrequencyPoint = frequency_point;
            receivedMessage.Time = time;
            receivedMessage.EncodeType = encode_type;
            receivedMessage.Content = content;
            loge("解析北斗消息: "+receivedMessage.toString());
            ReceivedMessage();
        } catch (Exception  e){
            loge("BDTCI: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // XY -----------------------------------------------------
    // 收到了 ZDX 盒子信息
    // [$BDZDX, 4207733, 036, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 2, 1835, 0, 68.06, 52, 102706]
    private void BDZDX(String[] value){
        try {
            String cardId = value[1];
            int cardFre = Integer.parseInt(value[24]);
            int cardLevel = Integer.parseInt(value[25]);
            int batteryLevel = Integer.parseInt(value[2]);
            loge("BDZDX 解析设备信息: 卡号-"+cardId+" 频度-"+cardFre+" 等级-"+cardLevel+" 电量-"+batteryLevel );
            int s21[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
            s21[0] = Integer.parseInt(value[3]);
            s21[1] = Integer.parseInt(value[4]);
            s21[2] = Integer.parseInt(value[5]);
            s21[3] = Integer.parseInt(value[6]);
            s21[4] = Integer.parseInt(value[7]);
            s21[5] = Integer.parseInt(value[8]);
            s21[6] = Integer.parseInt(value[9]);
            s21[7] = Integer.parseInt(value[10]);
            s21[8] = Integer.parseInt(value[11]);
            s21[9] = Integer.parseInt(value[12]);
            s21[10] = Integer.parseInt(value[13]);
            s21[11] = Integer.parseInt(value[14]);
            s21[12] = Integer.parseInt(value[15]);
            s21[13] = Integer.parseInt(value[16]);
            s21[14] = Integer.parseInt(value[17]);
            s21[15] = Integer.parseInt(value[18]);
            s21[16] = Integer.parseInt(value[19]);
            s21[17] = Integer.parseInt(value[20]);
            s21[18] = Integer.parseInt(value[21]);
            s21[19] = Integer.parseInt(value[22]);
            s21[20] = Integer.parseInt(value[23]);
            loge("BDZDX 解析设备信号: "+Arrays.toString(s21) );
            bd_parameter.CardID = cardId;
            bd_parameter.CardLevel = cardLevel;
            bd_parameter.CardFrequency = cardFre;
            xyParameter.BatteryLevel = batteryLevel;
            bd_parameter.Signal = s21;
            xyParameter.ContentLength = Integer.parseInt(value[26]);
            xyParameter.Temperature = Double.parseDouble(value[28]);
            xyParameter.Humidity = Integer.parseInt(value[29]);
            xyParameter.Pressure = Integer.parseInt(value[30]);
            BDParameterChange();
            XYParameterChange();
        }catch(Exception  e){
            loge("BDZDX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // ICI 卡号、频度等
    // $BDICI,4207733,0,0,3,60,2,N,22*3A
    private void BDICI(String[] value){
        try {
            String cardId = value[1];
            int cardFre = Integer.parseInt(value[5]);
            int cardLevel = -1;
            if(Integer.parseInt(value[6]) == 0){
                cardLevel = 5;  // 0就是5级卡
            }else {
                cardLevel = Integer.parseInt(value[6]);
            }
            loge("BDICI 解析设备信息: 卡号-"+cardId+" 频度-"+cardFre+" 等级-"+cardLevel );
            bd_parameter.CardID = cardId;
            bd_parameter.CardLevel = cardLevel;
            bd_parameter.CardFrequency = cardFre;
            bd_parameter.BroadcastID = value[3];
            bd_parameter.UserIdentity = Integer.parseInt(value[4]);
            bd_parameter.EnableConfidentiality = value[7];
            bd_parameter.NumberOfSubordinateUsers = Integer.parseInt(value[8]);
            BDParameterChange();
        }catch (Exception e){
            loge("BDICI: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDSNR,0,0,0,0,0,0,48,0,0,0,0,0,41,0,0,0,44,44,0,0,0*5C
    // SNR 功率信息
    private void BDSNR(String[] value){
        try {
            int s21[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
            for (int i = 0; i < value.length; i++) {
                if(i==0){continue;}
                s21[i-1] = Integer.parseInt(value[i]);
            }
            loge("BDSNR 解析设备信号: "+Arrays.toString(s21) );
            bd_parameter.Signal = s21;
            BDParameterChange();
        }catch (Exception e){
            loge("BDSNR: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDVRX,c--c
    private void BDVRX(String[] value){
        try {
            xyParameter.Version = value[1];
            XYParameterChange();
        }catch (Exception e){
            loge("BDVRX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDRSX,x*hh
    private void BDRSX(String[] value){
        try {
            ResponseCommand command = new ResponseCommand();
            command.Raw = value;
            command.Command = value[0].replace("$","");
            command.Result = true;
            command.Content = value[1];
            command.Remark = "设备复位信息反馈";
            ReceivedResponseCommand(command);
            xyParameter.RestartMode = Integer.parseInt(value[1]);
            XYParameterChange();
        }catch (Exception e){
            loge("BDRSX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDDEF,OK*69
    private void BDDEF(String[] value){
        try {
            ResponseCommand command = new ResponseCommand();
            command.Raw = value;
            command.Command = value[0].replace("$","");
            command.Result = true;
            command.Content = value[1];
            command.Remark = "设备恢复出厂设置反馈";
            ReceivedResponseCommand(command);
        }catch (Exception e){
            loge("BDDEF: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDBTX,1*55
    private void BDBTX(String[] value){
        try {
            ResponseCommand command = new ResponseCommand();
            command.Raw = value;
            command.Command = value[0].replace("$","");
            command.Result = true;
            command.Content = value[1];
            command.Remark = "设置蓝牙名称";
            ReceivedResponseCommand(command);
        }catch (Exception e){
            loge("BDBTX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDUGX,x,x,xxxxxxxxxxx,…,xxxxxxxxxxx*hh
    private void BDUGX(String[] value){
        try {
            ResponseCommand command = new ResponseCommand();
            command.Raw = value;
            command.Command = value[0].replace("$","");
            command.Result = true;
            command.Remark = "用户组信息反馈";
            ReceivedResponseCommand(command);
        }catch (Exception e){
            loge("BDUGX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDPRX,xxxxxxxxxxx<1>,x<2>,xxxxx<3>,xx<4>,x<5>*hh
    private void BDPRX(String[] value){
        try {
            xyParameter.LocationReportID = value[1];
            xyParameter.PositionMode = Integer.parseInt(value[2]);
            xyParameter.CollectionFrequency = Integer.parseInt(value[3]);
            xyParameter.PositionCount = Integer.parseInt(value[4]);
            xyParameter.ReportType = Integer.parseInt(value[5]);
            XYParameterChange();
        }catch (Exception e){
            loge("BDPRX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDYPX,x<1>,x<2>*hh
    private void BDYPX(String[] value){
        try {
            xyParameter.RDSSProtocolVersion = Integer.parseInt(value[1]);
            xyParameter.RNSSProtocolVersion = Integer.parseInt(value[2]);
            XYParameterChange();
        }catch (Exception e){
            loge("BDYPX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDMDX,x<1>,x<2>,x<3>,x<4>*hh
    private void BDMDX(String[] value){
        try {
            xyParameter.RDSSMode = Integer.parseInt(value[1]);
            xyParameter.RNSSMode = Integer.parseInt(value[2]);
            xyParameter.BLEMode = Integer.parseInt(value[3]);
            xyParameter.NETMode = Integer.parseInt(value[4]);
            XYParameterChange();
        }catch (Exception e){
            loge("BDMDX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDHMX,xxxxxxxxxxx<1>,xxxxx<2>*hh
    private void BDHMX(String[] value){
        try {
            xyParameter.SOSID = value[1];
            xyParameter.SOSFrequency = Integer.parseInt(value[2]);
            XYParameterChange();
        }catch (Exception e){
            loge("BDHMX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDQDX,x<1>*hh
    private void BDQDX(String[] value){
        try {
            ResponseCommand command = new ResponseCommand();
            command.Raw = value;
            command.Command = value[0].replace("$","");
            command.Content = value[1];
            if(value[1].equals("0")){
                command.Result = true;
            } else {
                command.Result = false;
            }
            command.Remark = "启动救援命令反馈";
            ReceivedResponseCommand(command);
        }catch (Exception e){
            loge("BDQDX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDTRA,x<1>*hh
    private void BDTRA(String[] value){
        try {
            xyParameter.WorkMode = Integer.parseInt(value[1]);
            XYParameterChange();
        }catch (Exception e){
            loge("BDTRA: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDZTX,*7C
    private void BDZTX(String[] value){
        try {
            ResponseCommand command = new ResponseCommand();
            command.Raw = value;
            command.Command = value[0].replace("$","");
            command.Result = true;
            command.Remark = "软件关机命令反馈";
            ReceivedResponseCommand(command);
        }catch (Exception e){
            loge("BDZTX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDRNX,5,5,5,5,5,5,8*56
    private void BDRNX(String[] value){
        try {
            xyParameter.GGAFrequency = Integer.parseInt(value[1]);
            xyParameter.GSVFrequency = Integer.parseInt(value[2]);
            xyParameter.GLLFrequency = Integer.parseInt(value[3]);
            xyParameter.GSAFrequency = Integer.parseInt(value[4]);
            xyParameter.RMCFrequency = Integer.parseInt(value[5]);
            xyParameter.ZDAFrequency = Integer.parseInt(value[6]);
            xyParameter.TimeZone = Integer.parseInt(value[7]);
            XYParameterChange();
        }catch (Exception e){
            loge("BDRNX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDOKX,15950044,我已安全,请放心,*7C
    private void BDOKX(String[] value){
        try {
            xyParameter.OKID = value[1];
            xyParameter.OKContent = value[2];
            XYParameterChange();
        }catch (Exception e){
            loge("BDOKX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDPWX,5,1*hh
    private void BDPWX(String[] value){
        try {
            ResponseCommand command = new ResponseCommand();
            command.Raw = value;
            command.Command = value[0].replace("$","");
            command.Content = value[1];
            command.Result = value[2].equals("1");
            command.Remark = "终端密码设置/登录反馈";
            ReceivedResponseCommand(command);
        }catch (Exception e){
            loge("BDPWX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // FD -----------------------------------------------------
    // $DWZXX, INT, INT*XX
    private void DWZXX(String[] value){
        try {
            fdParameter.LocationReportID = value[1];
            fdParameter.LocationReportFrequency = Integer.parseInt(value[2]);
            FDParameterChange();
        }catch (Exception e){
            loge("DWZXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DBJXX,X,X,STR*XX
    private void DBJXX(String[] value){
        try {
            fdParameter.SOSID = value[1];
            fdParameter.SOSFrequency = Integer.parseInt(value[2]);
            fdParameter.SOSContent = value[3];
            FDParameterChange();
        }catch (Exception e){
            loge("DBJXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DMSXX,X*XX
    private void DMSXX(String[] value){
        try {
            fdParameter.WorkMode = Integer.parseInt(value[1]);
            FDParameterChange();
        }catch (Exception e){
            loge("DMSXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DDLXX, INT,INT*XX
    private void DDLXX(String[] value){
        try {
            fdParameter.BatteryVoltage = Integer.parseInt(value[1]);
            fdParameter.BatteryLevel = Integer.parseInt(value[2]);
            FDParameterChange();
        }catch (Exception e){
            loge("DDLXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DPWXX,X,X*XX
    private void DPWXX(String[] value){
        try {
            fdParameter.PositioningModuleStatus = Integer.parseInt(value[1]);
            fdParameter.BDModuleStatus = Integer.parseInt(value[2]);
            FDParameterChange();
        }catch (Exception e){
            loge("DPWXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DBBXX,STR,STR*XX
    private void DBBXX(String[] value){
        try {
            fdParameter.SoftwareVersion = value[1];
            fdParameter.HardwareVersion = value[2];
            FDParameterChange();
        }catch (Exception e){
            loge("DBBXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BCZQXX,INT,INT*XX
    private void BCZQXX(String[] value){
        try {
            fdParameter.LocationStoragePeriod = Integer.parseInt(value[1]);
            FDParameterChange();
        }catch (Exception e){
            loge("BCZQXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DLYXX,STR*XX
    private void DLYXX(String[] value){
        try {
            fdParameter.BluetoothName = value[1];
            FDParameterChange();
        }catch (Exception e){
            loge("DLYXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DYJXX,STR,STR,INT,FLOAT,FLOAT,INT,INT,STR,INT,INT,INT,STR,INT,INT,INT,STR*XX
    private void DYJXX(String[] value){
        try {
            fdParameter.SoftwareVersion = value[1];
            fdParameter.HardwareVersion = value[2];
            fdParameter.WorkMode = Integer.parseInt(value[3]);
            fdParameter.ExternalVoltage = Integer.parseInt(value[4]);
            fdParameter.InternalVoltage = Integer.parseInt(value[5]);
            fdParameter.Temperature = Double.parseDouble(value[6]);
            fdParameter.Humidity = Double.parseDouble(value[7]);
            fdParameter.BluetoothName = value[8];
            fdParameter.SOSFrequency = Integer.parseInt(value[9]);
            fdParameter.LocationReportFrequency = Integer.parseInt(value[10]);
            fdParameter.LocationStoragePeriod = Integer.parseInt(value[11]);
            fdParameter.SOSContent = value[12];
            fdParameter.LocationsCount = Integer.parseInt(value[13]);
            fdParameter.CardID = value[14];
            fdParameter.NumberOfResets = Integer.parseInt(value[15]);
            FDParameterChange();
            bd_parameter.CardID = value[14];
            // 信号?
            BDParameterChange();
        }catch (Exception e){
            loge("DYJXX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $DLYRN,X*XX
    private void DLYRN(String[] value){
        try {
            fdParameter.RNBleFeedback = Integer.parseInt(value[1]);
            FDParameterChange();
        } catch (Exception e){
            loge("DLYRN: 解析错误");
            e.printStackTrace();
            return;
        }
    }

    // $BDWAX,INT,INT,INT,INT,STR*XX
    private void BDWAX(String[] value){
        try {
            fdParameter.OverboardID = value[1];
            fdParameter.OverboardFrequency = Integer.parseInt(value[2]);
            fdParameter.OverboardContent = value[5];
            FDParameterChange();
        } catch (Exception e){
            loge("BDWAX: 解析错误");
            e.printStackTrace();
            return;
        }
    }

// 接口 ---------------------------------------
    private ParameterListener parameterListener;
    public void setParameterListener(ParameterListener parameterListener){
        this.parameterListener = parameterListener;
    }

}

未完待续

  • 27
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
非常抱歉,我无法提供手把手项目教学,但是我可以给你一些关于使用Android Studio的基本指导。 首先,你需要安装Android Studio。你可以在官方网站(https://developer.android.com/studio)上下载相应的安装程序,并按照提示进行安装。 安装完成后,打开Android Studio。接下来,你可以选择创建一个项目或导入一个已有的项目。 如果选择创建新项目,你需要填写一些基本信息,例如项目名称、包名和保存路径。然后,你可以选择使用空白活动模板或其他模板来创建项目的初始结构。 一旦项目创建完成,你就可以开始编写代码了。Android Studio提供了一个强大的代码编辑器,支持自动完成、代码格式化和错误检查等功能。你可以使用JavaKotlin语言来编写Android应用程序。 在编写代码的过程中,你可以使用Android Studio提供的布局编辑器来设计应用程序的用户界面。它允许你通过拖放组件来构建界面,并提供实时预览功能。 除此之外,Android Studio还提供了丰富的调试工具,例如调试器和日志输出。这些工具可以帮助你查找和解决应用程序中的错误和异常。 最后,在完成应用程序的开发后,你可以使用Android Studio提供的构建工具来生成APK文件,以便在真机或模拟器上测试和部署应用程序。 这只是Android Studio的一些基本用法,希望对你有所帮助。如果你有更具体的问题或需要进一步的指导,请随时提问。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值