diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/end.png b/app/src/main/res/mipmap-xhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/end.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/end.png b/app/src/main/res/mipmap-xhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/start.png b/app/src/main/res/mipmap-xhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/start.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/end.png b/app/src/main/res/mipmap-xhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/start.png b/app/src/main/res/mipmap-xhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/end.png b/app/src/main/res/mipmap-xxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/end.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/end.png b/app/src/main/res/mipmap-xhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/start.png b/app/src/main/res/mipmap-xhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/end.png b/app/src/main/res/mipmap-xxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/start.png b/app/src/main/res/mipmap-xxhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/start.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/end.png b/app/src/main/res/mipmap-xhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/start.png b/app/src/main/res/mipmap-xhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/end.png b/app/src/main/res/mipmap-xxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/start.png b/app/src/main/res/mipmap-xxhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxxhdpi/end.png b/app/src/main/res/mipmap-xxxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/end.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/end.png b/app/src/main/res/mipmap-xhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/start.png b/app/src/main/res/mipmap-xhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/end.png b/app/src/main/res/mipmap-xxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/start.png b/app/src/main/res/mipmap-xxhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxxhdpi/end.png b/app/src/main/res/mipmap-xxxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xxxhdpi/start.png b/app/src/main/res/mipmap-xxxhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/start.png Binary files differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e822df6..d72c1a5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -7,6 +7,9 @@ + + + @@ -46,5 +49,8 @@ + + + \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt new file mode 100644 index 0000000..2dac61c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/base/KotlinBaseFragment.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.base + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.fragment.app.Fragment + +abstract class KotlinBaseFragment : Fragment() { + + lateinit var bv: View + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + bv = inflater.inflate(initLayoutRes(), container, false) + initView(savedInstanceState) + setupTopBarLayout() + return bv + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + observeRequestState() + initEvent() + } + + @LayoutRes + abstract fun initLayoutRes(): Int + + /** + * 沉浸式状态栏 + */ + abstract fun setupTopBarLayout() + + /** + * 初始化布局以及控件 + */ + abstract fun initView(savedInstanceState: Bundle?) + + /** + * 网络请求状态监听 + */ + abstract fun observeRequestState() + + /** + * 业务逻辑,按钮等事件 + */ + abstract fun initEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt index 6c98a3b..9418169 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/AlarmPageFragment.kt @@ -2,28 +2,29 @@ import android.os.Bundle import com.casic.br.ktd.R +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_alarm.view.* class AlarmPageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { override fun onDateRangeSelected(startDate: String, endDate: String) { - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } } - override fun initLayoutView(): Int = R.layout.fragment_alarm + override fun initLayoutRes(): Int = R.layout.fragment_alarm override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt index 9961935..f3eba5c 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/HomePageFragment.kt @@ -1,32 +1,62 @@ package com.casic.br.ktd.fragment +import android.graphics.Color import android.os.Bundle import com.amap.api.maps.AMap import com.amap.api.maps.AMapOptions +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.model.* import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment import kotlinx.android.synthetic.main.fragment_home.view.* class HomePageFragment : KotlinBaseFragment() { private lateinit var aMap: AMap - override fun initData(savedInstanceState: Bundle?) { - baseView.mapView.onCreate(savedInstanceState) - aMap = baseView.mapView.map + override fun initView(savedInstanceState: Bundle?) { + bv.mapView.onCreate(savedInstanceState) + aMap = bv.mapView.map val uiSettings = aMap.uiSettings uiSettings.isCompassEnabled = true - uiSettings.isMyLocationButtonEnabled = true + uiSettings.isMyLocationButtonEnabled = false uiSettings.isScaleControlsEnabled = true uiSettings.zoomPosition = AMapOptions.ZOOM_POSITION_RIGHT_CENTER uiSettings.isRotateGesturesEnabled = false//不许地图随手势旋转角度 } override fun initEvent() { - + //移动到巡检起始经纬度 + val cameraPosition = CameraPosition(LatLng(39.914199, 116.265785), 16f, 0f, 0f) + val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) + aMap.animateCamera(cameraUpdate, 1500, null) + //设置起点 + addMarker(LatLng(39.914885, 116.272312), R.mipmap.start) + //设置终点 + addMarker(LatLng(39.914716, 116.265097), R.mipmap.end) + //瞄点画线 + val latLngPoints = ArrayList() + latLngPoints.add(LatLng(39.914885, 116.272312)) + latLngPoints.add(LatLng(39.915148, 116.272312)) + latLngPoints.add(LatLng(39.915399, 116.272302)) + latLngPoints.add(LatLng(39.915436, 116.271787)) + latLngPoints.add(LatLng(39.915477, 116.270886)) + latLngPoints.add(LatLng(39.915477, 116.269968)) + latLngPoints.add(LatLng(39.915469, 116.269544)) + latLngPoints.add(LatLng(39.915481, 116.269196)) + latLngPoints.add(LatLng(39.915452, 116.268472)) + latLngPoints.add(LatLng(39.915444, 116.267722)) + latLngPoints.add(LatLng(39.915415, 116.266755)) + latLngPoints.add(LatLng(39.915403, 116.266165)) + latLngPoints.add(LatLng(39.915395, 116.265703)) + latLngPoints.add(LatLng(39.915366, 116.265119)) + latLngPoints.add(LatLng(39.914716, 116.265097)) + aMap.addPolyline( + PolylineOptions().addAll(latLngPoints).width(10.toFloat()).color(Color.RED) + ) } - override fun initLayoutView(): Int = R.layout.fragment_home + override fun initLayoutRes(): Int = R.layout.fragment_home override fun observeRequestState() { @@ -36,25 +66,32 @@ } + private fun addMarker(point: LatLng, res: Int) { + val markerOption = MarkerOptions() + markerOption.position(point) + markerOption.icon(BitmapDescriptorFactory.fromResource(res)) + aMap.addMarker(markerOption) + } + /***以下是地图生命周期管理************************************************************************/ override fun onResume() { super.onResume() - baseView.mapView.onResume() + bv.mapView.onResume() } override fun onPause() { - baseView.mapView.onPause() super.onPause() + bv.mapView.onPause() } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) - baseView.mapView.onSaveInstanceState(outState) + bv.mapView.onSaveInstanceState(outState) } override fun onDestroy() { - baseView.mapView.onDestroy() super.onDestroy() + bv.mapView.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt index 90313ac..3212794 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/MinePageFragment.kt @@ -2,10 +2,11 @@ import android.os.Bundle import com.casic.br.ktd.R -import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.casic.br.ktd.base.KotlinBaseFragment class MinePageFragment : KotlinBaseFragment() { - override fun initData(savedInstanceState: Bundle?) { + + override fun initView(savedInstanceState: Bundle?) { } @@ -13,7 +14,7 @@ } - override fun initLayoutView(): Int = R.layout.fragment_mine + override fun initLayoutRes(): Int = R.layout.fragment_mine override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt index 8f6abf0..6b82a54 100644 --- a/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt +++ b/app/src/main/java/com/casic/br/ktd/fragment/TaskPageFragment.kt @@ -8,6 +8,7 @@ import androidx.recyclerview.widget.RecyclerView import com.casic.br.ktd.R import com.casic.br.ktd.adapter.TaskAdapter +import com.casic.br.ktd.base.KotlinBaseFragment import com.casic.br.ktd.holder.SwipeViewHolder import com.casic.br.ktd.model.TaskListModel import com.casic.br.ktd.view.InspectionActivity @@ -15,7 +16,6 @@ import com.casic.br.ktd.widgets.AlertControlDialog import com.casic.br.ktd.widgets.AlertInputDialog import com.casic.br.ktd.widgets.DateRangeActionSheet -import com.pengxh.kt.lite.base.KotlinBaseFragment import com.pengxh.kt.lite.extensions.navigatePageTo import com.pengxh.kt.lite.extensions.show import com.pengxh.kt.lite.utils.WeakReferenceHandler @@ -36,7 +36,7 @@ private var startTime = "" private var endTime = "" - override fun initData(savedInstanceState: Bundle?) { + override fun initView(savedInstanceState: Bundle?) { weakReferenceHandler = WeakReferenceHandler(this) taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java] taskViewModel.taskList.observe(this) { @@ -45,7 +45,7 @@ when { isRefresh -> { taskAdapter.setRefreshData(dataRows) - baseView.taskLayout.finishRefresh() + bv.taskLayout.finishRefresh() isRefresh = false } isLoadMore -> { @@ -53,7 +53,7 @@ "到底了,别拉了".show(requireContext()) } taskAdapter.setLoadMoreData(dataRows) - baseView.taskLayout.finishLoadMore() + bv.taskLayout.finishLoadMore() isLoadMore = false } else -> { @@ -76,9 +76,9 @@ when (msg.what) { 2023070501 -> { if (dataBeans.size == 0) { - baseView.emptyLayout.visibility = View.VISIBLE + bv.emptyLayout.visibility = View.VISIBLE } else { - baseView.emptyLayout.visibility = View.GONE + bv.emptyLayout.visibility = View.GONE taskAdapter = object : TaskAdapter( requireContext(), R.layout.item_task_rv_l, dataBeans ) { @@ -97,8 +97,8 @@ } } //绑定侧滑事件 - swipeAction.attachToRecyclerView(baseView.taskRecyclerView) - baseView.taskRecyclerView.adapter = taskAdapter + swipeAction.attachToRecyclerView(bv.taskRecyclerView) + bv.taskRecyclerView.adapter = taskAdapter taskAdapter.setOnItemCheckedListener(object : TaskAdapter.OnItemCheckedListener { override fun onItemChecked(items: ArrayList) { @@ -168,7 +168,7 @@ override fun initEvent() { - baseView.calendarView.setOnClickListener { + bv.calendarView.setOnClickListener { DateRangeActionSheet.Builder().setContext(requireContext()) .setOnActionSheetListener(object : DateRangeActionSheet.OnDateRangeSelectedListener { @@ -177,12 +177,12 @@ endTime = endDate //显示 - baseView.selectedDateView.text = "$startDate ~ $endDate" + bv.selectedDateView.text = "$startDate ~ $endDate" } }).build().show() } - baseView.addTaskButton.setOnClickListener { + bv.addTaskButton.setOnClickListener { // AlertInputDialog.Builder() // .setContext(requireContext()) // .setTitle("新建巡检任务") @@ -200,11 +200,11 @@ requireContext().navigatePageTo() } - baseView.deleteTaskButton.setOnClickListener { + bv.deleteTaskButton.setOnClickListener { selectedItems.size.toString().show(requireContext()) } - baseView.taskSettingsButton.setOnClickListener { + bv.taskSettingsButton.setOnClickListener { AlertInputDialog.Builder() .setContext(requireContext()) .setTitle("设置阈值") @@ -221,14 +221,14 @@ }).build().show() } - baseView.taskLayout.setOnRefreshListener { + bv.taskLayout.setOnRefreshListener { isRefresh = true //刷新之后页码重置 pageIndex = 1 getTaskList() } - baseView.taskLayout.setOnLoadMoreListener { + bv.taskLayout.setOnLoadMoreListener { isLoadMore = true pageIndex++ getTaskList() @@ -239,13 +239,13 @@ taskViewModel.getTaskList( startTime, endTime, - baseView.taskNameView.text.toString(), - baseView.taskCodeView.text.toString(), + bv.taskNameView.text.toString(), + bv.taskCodeView.text.toString(), pageIndex ) } - override fun initLayoutView(): Int = R.layout.fragment_task + override fun initLayoutRes(): Int = R.layout.fragment_task override fun observeRequestState() { diff --git a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt deleted file mode 100644 index dab98e1..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/ISocketListener.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.casic.br.ktd.netty - -interface ISocketListener { - companion object { - const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 - const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 - const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 - } - - /** - * 当接收到系统消息 - */ - fun onMessageResponse(data: ByteArray?) - - /** - * 当连接状态发生变化时调用 - */ - fun onServiceStatusConnectChanged(statusCode: Byte) -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt deleted file mode 100644 index 42eb310..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketChannelHandle.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import io.netty.channel.ChannelHandlerContext -import io.netty.channel.SimpleChannelInboundHandler -import io.netty.handler.timeout.IdleState -import io.netty.handler.timeout.IdleStateEvent -import java.nio.charset.StandardCharsets - -class SocketChannelHandle(private val listener: ISocketListener?) : - SimpleChannelInboundHandler() { - - private val kTag = "SocketChannelHandle" - - override fun channelActive(ctx: ChannelHandlerContext) { - super.channelActive(ctx) - Log.d(kTag, "channelActive ===> 连接成功") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) - } - - override fun channelInactive(ctx: ChannelHandlerContext) { - super.channelInactive(ctx) - Log.e(kTag, "channelInactive: 连接断开") - } - - override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { - super.userEventTriggered(ctx, evt) - if (evt is IdleStateEvent) { - if (evt.state() == IdleState.WRITER_IDLE) { - //写超时,此时可以发送心跳数据给服务器 - val temp = "FF" - ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) - } else if (evt.state() == IdleState.READER_IDLE) { - //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 - ctx.close() - } - } - } - - override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { - listener?.onMessageResponse(data) - } - - override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { - super.exceptionCaught(ctx, cause) - Log.d(kTag, "exceptionCaught ===> $cause") - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) - cause.printStackTrace() - ctx.close() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt deleted file mode 100644 index d05d335..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketClient.kt +++ /dev/null @@ -1,153 +0,0 @@ -package com.casic.br.ktd.netty - -import android.os.SystemClock -import android.util.Log -import io.netty.bootstrap.Bootstrap -import io.netty.channel.* -import io.netty.channel.nio.NioEventLoopGroup -import io.netty.channel.socket.SocketChannel -import io.netty.channel.socket.nio.NioSocketChannel -import io.netty.handler.codec.bytes.ByteArrayDecoder -import io.netty.handler.codec.bytes.ByteArrayEncoder -import io.netty.handler.timeout.IdleStateHandler - -class SocketClient { - - private val kTag = "SocketClient" - - private var host: String? = null - private var port = 8000 - private var nioEventLoopGroup: NioEventLoopGroup? = null - private var channel: Channel? = null - private var listener: ISocketListener? = null - - //现在连接的状态 - var connectStatus = false //判断是否已连接 - private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 - private var isNeedReconnect = true //是否需要重连 - var isConnecting = false //是否正在连接 - private set - private var reconnectIntervalTime: Long = 15000 //重连的时间 - - //重连时间 - fun setReconnectNum(reconnectNum: Int) { - this.reconnectNum = reconnectNum - } - - fun setReconnectIntervalTime(reconnectIntervalTime: Long) { - this.reconnectIntervalTime = reconnectIntervalTime - } - - fun setSocketListener(listener: ISocketListener?) { - this.listener = listener - } - - fun connect(host: String, port: Int) { - this.host = host - this.port = port - Log.d(kTag, "connect ===> 开始连接TCP服务器") - if (isConnecting) { - return - } - //起个线程 - val clientThread: Thread = object : Thread("client-Netty") { - override fun run() { - super.run() - isNeedReconnect = true - reconnectNum = Int.MAX_VALUE - connectServer() - } - } - clientThread.start() - } - - private fun connectServer() { - synchronized(this@SocketClient) { - var channelFuture: ChannelFuture? = null //连接管理对象 - if (!connectStatus) { - isConnecting = true - nioEventLoopGroup = NioEventLoopGroup() //设置的连接group - val bootstrap = Bootstrap() - bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 - .channel(NioSocketChannel::class.java) - .option(ChannelOption.TCP_NODELAY, true) //无阻塞 - .option(ChannelOption.SO_KEEPALIVE, true) //长连接 - .option( - ChannelOption.RCVBUF_ALLOCATOR, - AdaptiveRecvByteBufAllocator(5000, 5000, 8000) - ) //接收缓冲区 最小值太小时数据接收不全 - .handler(object : ChannelInitializer() { - override fun initChannel(channel: SocketChannel) { - val pipeline = channel.pipeline() - //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; - //参数2:代表写套接字超时时间,没进行写会触发写超时回调; - //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; - //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 - pipeline.addLast(IdleStateHandler(60, 10, 0)) - pipeline.addLast(ByteArrayDecoder()) - pipeline.addLast(ByteArrayEncoder()) - pipeline.addLast(SocketChannelHandle(listener)) - } - }) - try { - //连接监听 - channelFuture = bootstrap.connect(host, port) - .addListener(object : ChannelFutureListener { - override fun operationComplete(channelFuture: ChannelFuture) { - if (channelFuture.isSuccess) { - connectStatus = true - channel = channelFuture.channel() - } else { - Log.e(kTag, "operationComplete: 连接失败") - connectStatus = false - } - isConnecting = false - } - }).sync() - // 等待连接关闭 - channelFuture.channel().closeFuture().sync() - } catch (e: Exception) { - e.printStackTrace() - } finally { - connectStatus = false - listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 - if (null != channelFuture) { - if (channelFuture.channel() != null && channelFuture.channel().isOpen) { - channelFuture.channel().close() - } - } - nioEventLoopGroup?.shutdownGracefully() - reconnect() //重新连接 - } - } - } - } - - //断开连接 - fun disconnect() { - Log.d(kTag, "disconnect ===> 断开连接") - isNeedReconnect = false - nioEventLoopGroup?.shutdownGracefully() - } - - //重新连接 - private fun reconnect() { - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - reconnectNum-- - SystemClock.sleep(reconnectIntervalTime) - if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { - Log.d(kTag, "reconnect ===> 重新连接") - connectServer() - } - } - } - - fun sendData(bytes: ByteArray) { - channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> - if (!future.isSuccess) { - future.channel().close() - nioEventLoopGroup!!.shutdownGracefully() - } - }) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt deleted file mode 100644 index 17daf35..0000000 --- a/app/src/main/java/com/casic/br/ktd/netty/SocketManager.kt +++ /dev/null @@ -1,113 +0,0 @@ -package com.casic.br.ktd.netty - -import android.util.Log -import com.casic.br.ktd.base.BaseApplication -import com.casic.br.ktd.extensions.covertAngleValue -import com.casic.br.ktd.extensions.covertDataValue -import com.casic.br.ktd.extensions.toUnsignedByteArray -import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.utils.LocaleConstant -import com.pengxh.kt.lite.extensions.toJson -import com.pengxh.kt.lite.utils.BroadcastManager - -class SocketManager private constructor() : ISocketListener { - - private val kTag = "SocketManager" - private var nettyClient: SocketClient = SocketClient() - - companion object { - val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } - } - - fun connectNetty(hostname: String, port: Int) { - Thread { - if (!nettyClient.connectStatus) { - nettyClient.setSocketListener(this) - nettyClient.connect(hostname, port) - } else { - nettyClient.disconnect() - } - }.start() - } - - override fun onMessageResponse(data: ByteArray?) { -// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) - /** - * 0xFF,0x01, - * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) - * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) - * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) - * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) - * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) - * 0xC1 - * - * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] - * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; - * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; - * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; - * - * ========================================================================================= - * 实际数据(1800-1950左右) - * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] - * */ - - if (data == null) { - return - } - val bytes = data.toUnsignedByteArray() - if (bytes.size == 14) { - val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) - - val methaneBytes = IntArray(3) - System.arraycopy(bytes, 2, methaneBytes, 0, 3) - dataModel.methane = methaneBytes.covertDataValue() - - dataModel.methaneState = when (bytes[5]) { - 1 -> "温控故障" - 2 -> "激光未打开" - else -> "正常" - } - - val laserBytes = IntArray(3) - System.arraycopy(bytes, 6, laserBytes, 0, 3) - dataModel.laser = laserBytes.covertDataValue() - - /** - * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° - * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° - * */ - val horizontalBytes = IntArray(2) - System.arraycopy(bytes, 9, horizontalBytes, 0, 2) - dataModel.horizontal = horizontalBytes.covertAngleValue() - - val verticalBytes = IntArray(2) - System.arraycopy(bytes, 11, verticalBytes, 0, 2) - dataModel.vertical = verticalBytes.covertAngleValue() - - //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} - BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( - LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() - ) - } - } - - override fun onServiceStatusConnectChanged(statusCode: Byte) { - if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { - if (nettyClient.connectStatus) { - Log.d(kTag, "连接成功") - } - } else { - if (!nettyClient.connectStatus) { - Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") - } - } - } - - fun sendData(data: ByteArray) { - nettyClient.sendData(data) - } - - fun close() { - nettyClient.disconnect() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt new file mode 100644 index 0000000..13c3084 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/ISocketListener.kt @@ -0,0 +1,19 @@ +package com.casic.br.ktd.netty.tcp + +interface ISocketListener { + companion object { + const val STATUS_CONNECT_SUCCESS: Byte = 1 //连接成功 + const val STATUS_CONNECT_CLOSED: Byte = 0 //关闭连接 + const val STATUS_CONNECT_ERROR: Byte = 0 //连接失败 + } + + /** + * 当接收到系统消息 + */ + fun onMessageResponse(data: ByteArray?) + + /** + * 当连接状态发生变化时调用 + */ + fun onServiceStatusConnectChanged(statusCode: Byte) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt new file mode 100644 index 0000000..8e2997f --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketChannelHandle.kt @@ -0,0 +1,51 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.handler.timeout.IdleState +import io.netty.handler.timeout.IdleStateEvent +import java.nio.charset.StandardCharsets + +class SocketChannelHandle(private val listener: ISocketListener?) : + SimpleChannelInboundHandler() { + + private val kTag = "SocketChannelHandle" + + override fun channelActive(ctx: ChannelHandlerContext) { + super.channelActive(ctx) + Log.d(kTag, "channelActive ===> 连接成功") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_SUCCESS) + } + + override fun channelInactive(ctx: ChannelHandlerContext) { + super.channelInactive(ctx) + Log.e(kTag, "channelInactive: 连接断开") + } + + override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { + super.userEventTriggered(ctx, evt) + if (evt is IdleStateEvent) { + if (evt.state() == IdleState.WRITER_IDLE) { + //写超时,此时可以发送心跳数据给服务器 + val temp = "FF" + ctx.writeAndFlush(temp.toByteArray(StandardCharsets.UTF_8)) + } else if (evt.state() == IdleState.READER_IDLE) { + //读超时,此时代表没有收到心跳返回可以关闭当前连接进行重连 + ctx.close() + } + } + } + + override fun channelRead0(ctx: ChannelHandlerContext, data: ByteArray?) { + listener?.onMessageResponse(data) + } + + override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { + super.exceptionCaught(ctx, cause) + Log.d(kTag, "exceptionCaught ===> $cause") + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_ERROR) + cause.printStackTrace() + ctx.close() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt new file mode 100644 index 0000000..34960a0 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketClient.kt @@ -0,0 +1,153 @@ +package com.casic.br.ktd.netty.tcp + +import android.os.SystemClock +import android.util.Log +import io.netty.bootstrap.Bootstrap +import io.netty.channel.* +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.SocketChannel +import io.netty.channel.socket.nio.NioSocketChannel +import io.netty.handler.codec.bytes.ByteArrayDecoder +import io.netty.handler.codec.bytes.ByteArrayEncoder +import io.netty.handler.timeout.IdleStateHandler + +class SocketClient { + + private val kTag = "SocketClient" + + private var host: String? = null + private var port = 8000 + private var nioEventLoopGroup: NioEventLoopGroup? = null + private var channel: Channel? = null + private var listener: ISocketListener? = null + + //现在连接的状态 + var connectStatus = false //判断是否已连接 + private var reconnectNum = Int.MAX_VALUE //定义的重连到时候用 + private var isNeedReconnect = true //是否需要重连 + var isConnecting = false //是否正在连接 + private set + private var reconnectIntervalTime: Long = 15000 //重连的时间 + + //重连时间 + fun setReconnectNum(reconnectNum: Int) { + this.reconnectNum = reconnectNum + } + + fun setReconnectIntervalTime(reconnectIntervalTime: Long) { + this.reconnectIntervalTime = reconnectIntervalTime + } + + fun setSocketListener(listener: ISocketListener?) { + this.listener = listener + } + + fun connect(host: String, port: Int) { + this.host = host + this.port = port + Log.d(kTag, "connect ===> 开始连接TCP服务器") + if (isConnecting) { + return + } + //起个线程 + val clientThread: Thread = object : Thread("client-Netty") { + override fun run() { + super.run() + isNeedReconnect = true + reconnectNum = Int.MAX_VALUE + connectServer() + } + } + clientThread.start() + } + + private fun connectServer() { + synchronized(this@SocketClient) { + var channelFuture: ChannelFuture? = null //连接管理对象 + if (!connectStatus) { + isConnecting = true + nioEventLoopGroup = NioEventLoopGroup() //设置的连接group + val bootstrap = Bootstrap() + bootstrap.group(nioEventLoopGroup) //设置的一系列连接参数操作等 + .channel(NioSocketChannel::class.java) + .option(ChannelOption.TCP_NODELAY, true) //无阻塞 + .option(ChannelOption.SO_KEEPALIVE, true) //长连接 + .option( + ChannelOption.RCVBUF_ALLOCATOR, + AdaptiveRecvByteBufAllocator(5000, 5000, 8000) + ) //接收缓冲区 最小值太小时数据接收不全 + .handler(object : ChannelInitializer() { + override fun initChannel(channel: SocketChannel) { + val pipeline = channel.pipeline() + //参数1:代表读套接字超时的时间,没收到数据会触发读超时回调; + //参数2:代表写套接字超时时间,没进行写会触发写超时回调; + //参数3:将在未执行读取或写入时触发超时回调,0代表不处理; + //读超时尽量设置大于写超时,代表多次写超时时写心跳包,多次写了心跳数据仍然读超时代表当前连接错误,即可断开连接重新连接 + pipeline.addLast(IdleStateHandler(60, 10, 0)) + pipeline.addLast(ByteArrayDecoder()) + pipeline.addLast(ByteArrayEncoder()) + pipeline.addLast(SocketChannelHandle(listener)) + } + }) + try { + //连接监听 + channelFuture = bootstrap.connect(host, port) + .addListener(object : ChannelFutureListener { + override fun operationComplete(channelFuture: ChannelFuture) { + if (channelFuture.isSuccess) { + connectStatus = true + channel = channelFuture.channel() + } else { + Log.e(kTag, "operationComplete: 连接失败") + connectStatus = false + } + isConnecting = false + } + }).sync() + // 等待连接关闭 + channelFuture.channel().closeFuture().sync() + } catch (e: Exception) { + e.printStackTrace() + } finally { + connectStatus = false + listener?.onServiceStatusConnectChanged(ISocketListener.STATUS_CONNECT_CLOSED) //STATUS_CONNECT_CLOSED 这我自己定义的 接口标识 + if (null != channelFuture) { + if (channelFuture.channel() != null && channelFuture.channel().isOpen) { + channelFuture.channel().close() + } + } + nioEventLoopGroup?.shutdownGracefully() + reconnect() //重新连接 + } + } + } + } + + //断开连接 + fun disconnect() { + Log.d(kTag, "disconnect ===> 断开连接") + isNeedReconnect = false + nioEventLoopGroup?.shutdownGracefully() + } + + //重新连接 + private fun reconnect() { + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + reconnectNum-- + SystemClock.sleep(reconnectIntervalTime) + if (isNeedReconnect && reconnectNum > 0 && !connectStatus) { + Log.d(kTag, "reconnect ===> 重新连接") + connectServer() + } + } + } + + fun sendData(bytes: ByteArray) { + channel?.writeAndFlush(bytes)?.addListener(ChannelFutureListener { future -> + if (!future.isSuccess) { + future.channel().close() + nioEventLoopGroup!!.shutdownGracefully() + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt new file mode 100644 index 0000000..35980b9 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/tcp/SocketManager.kt @@ -0,0 +1,113 @@ +package com.casic.br.ktd.netty.tcp + +import android.util.Log +import com.casic.br.ktd.base.BaseApplication +import com.casic.br.ktd.extensions.covertAngleValue +import com.casic.br.ktd.extensions.covertDataValue +import com.casic.br.ktd.extensions.toUnsignedByteArray +import com.casic.br.ktd.model.SensorDataModel +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.extensions.toJson +import com.pengxh.kt.lite.utils.BroadcastManager + +class SocketManager private constructor() : ISocketListener { + + private val kTag = "SocketManager" + private var nettyClient: SocketClient = SocketClient() + + companion object { + val instance: SocketManager by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { SocketManager() } + } + + fun connectNetty(hostname: String, port: Int) { + Thread { + if (!nettyClient.connectStatus) { + nettyClient.setSocketListener(this) + nettyClient.connect(hostname, port) + } else { + nettyClient.disconnect() + } + }.start() + } + + override fun onMessageResponse(data: ByteArray?) { +// Log.d(kTag, "onMessageResponse ===> " + Arrays.toString(data)) + /** + * 0xFF,0x01, + * 0x01,0x37,0xE6, 甲烷浓度值(数据码1* 65536 + 数据码2 * 256 + 数据码3) + * 0x00, 激光甲烷模块工作状态(00表示设备正常,01表示温控故障,02表示设备激光未打开) + * 0x00,0xB2,0x35, 激光强度值(数据码5* 65536 + 数据码6 * 256 + 数据码7) + * 0x0A,0x3D, 云台水平角度([数据码8 * 256 + 数据码9]/100,单位为°,精确到0.01) + * 0x05,0x6F, 云台垂直角度(首先计算Tangle=[数据码10 * 256 + 数据码11]/100,单位为°,精确到0.01。若Tangle在0~90范围内,则垂直角度值=Tangle;若Tangle在-1~-90范围内,则垂直角度值=Tangle-360) + * 0xC1 + * + * [-1, 1, 1, 55, -26, 0, 0, -78, 53, 10, 61, 5, 111, -63] + * 甲烷浓度值为79638,计算为79638=0x01*65536+0x37*256+0xE6[0x01为数据码1,0x37为数据码2,0xE6为数据码3]; + * 激光甲烷设备状态值为0,表示状态正常,[0x00为数据码4]; + * 激光强度值为45621,计算为45621=0x00*65536+0xB2*256+0x35[0x00为数据码5,0xB2为数据码6,0x35为数据码7]; + * + * ========================================================================================= + * 实际数据(1800-1950左右) + * [-86, 1, 0, 7, 71, 0, 0, 62, -92, 57, 59, -118, -111, -64] + * */ + + if (data == null) { + return + } + val bytes = data.toUnsignedByteArray() + if (bytes.size == 14) { + val dataModel = SensorDataModel(0, "激光未打开", 0, 0.0, 0.0) + + val methaneBytes = IntArray(3) + System.arraycopy(bytes, 2, methaneBytes, 0, 3) + dataModel.methane = methaneBytes.covertDataValue() + + dataModel.methaneState = when (bytes[5]) { + 1 -> "温控故障" + 2 -> "激光未打开" + else -> "正常" + } + + val laserBytes = IntArray(3) + System.arraycopy(bytes, 6, laserBytes, 0, 3) + dataModel.laser = laserBytes.covertDataValue() + + /** + * 水平角度的实际范围为从水平零点开始,顺时针方向(从上往下看),0~360° + * 垂直角度的实际范围为从垂直零点开始,以向上为正,-90°~90°,控制精度为0.01° + * */ + val horizontalBytes = IntArray(2) + System.arraycopy(bytes, 9, horizontalBytes, 0, 2) + dataModel.horizontal = horizontalBytes.covertAngleValue() + + val verticalBytes = IntArray(2) + System.arraycopy(bytes, 11, verticalBytes, 0, 2) + dataModel.vertical = verticalBytes.covertAngleValue() + + //{"horizontal":26.21,"laser":45621,"methane":79846,"methaneState":"正常","vertical":13.91} + BroadcastManager.obtainInstance(BaseApplication.get()).sendBroadcast( + LocaleConstant.ACTION_UPDATE_DATA, dataModel.toJson() + ) + } + } + + override fun onServiceStatusConnectChanged(statusCode: Byte) { + if (statusCode == ISocketListener.STATUS_CONNECT_SUCCESS) { + if (nettyClient.connectStatus) { + Log.d(kTag, "连接成功") + } + } else { + if (!nettyClient.connectStatus) { + Log.e(kTag, "onServiceStatusConnectChanged:$statusCode,连接断开,正在重连") + } + } + } + + fun sendData(data: ByteArray) { + nettyClient.sendData(data) + } + + fun close() { + nettyClient.disconnect() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt new file mode 100644 index 0000000..4d6f97c --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInboundHandler.kt @@ -0,0 +1,36 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelHandlerContext +import io.netty.channel.SimpleChannelInboundHandler +import io.netty.channel.socket.DatagramPacket +import io.netty.util.CharsetUtil + + +abstract class UdpChannelInboundHandler : SimpleChannelInboundHandler() { + + private var handlerContext: ChannelHandlerContext? = null + + override fun channelActive(ctx: ChannelHandlerContext?) { + super.channelActive(ctx) + handlerContext = ctx + } + + override fun channelInactive(ctx: ChannelHandlerContext?) { + super.channelInactive(ctx) + handlerContext?.close() + } + + fun sendDatagramPacket(obj: Any) { + handlerContext?.writeAndFlush(obj) + } + + fun releasePort() { + handlerContext?.close() + } + + override fun channelRead0(ctx: ChannelHandlerContext, datagramPacket: DatagramPacket) { + receivedMessage(datagramPacket.content().toString(CharsetUtil.UTF_8)) + } + + abstract fun receivedMessage(data: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt new file mode 100644 index 0000000..416c5a1 --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpChannelInitializer.kt @@ -0,0 +1,17 @@ +package com.casic.br.ktd.netty.udp + +import io.netty.channel.ChannelInitializer +import io.netty.channel.socket.DatagramChannel +import io.netty.handler.timeout.IdleStateHandler + + +open class UdpChannelInitializer(private val handler: UdpChannelInboundHandler) : + ChannelInitializer() { + + override fun initChannel(datagramChannel: DatagramChannel) { + val pipeline = datagramChannel.pipeline() + pipeline.addLast( + IdleStateHandler(12, 15, 0) + ).addLast(handler) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt new file mode 100644 index 0000000..3893afa --- /dev/null +++ b/app/src/main/java/com/casic/br/ktd/netty/udp/UdpClient.kt @@ -0,0 +1,103 @@ +package com.casic.br.ktd.netty.udp + +import com.casic.br.ktd.utils.LocaleConstant +import com.pengxh.kt.lite.utils.SaveKeyValues +import io.netty.bootstrap.Bootstrap +import io.netty.buffer.ByteBuf +import io.netty.buffer.Unpooled +import io.netty.channel.ChannelOption +import io.netty.channel.nio.NioEventLoopGroup +import io.netty.channel.socket.DatagramPacket +import io.netty.channel.socket.nio.NioDatagramChannel +import io.netty.util.CharsetUtil +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.net.InetSocketAddress +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + + +class UdpClient : UdpChannelInboundHandler(), Runnable { + + private val bootStrap by lazy { Bootstrap() } + private val eventLoopGroup by lazy { NioEventLoopGroup() } + private val udpChannelInitializer by lazy { UdpChannelInitializer(this) } + private var executorService: ExecutorService + + init { + bootStrap.group(eventLoopGroup) + bootStrap.channel(NioDatagramChannel::class.java) + .option(ChannelOption.SO_RCVBUF, 1024) + .option(ChannelOption.SO_SNDBUF, 1024) + bootStrap.handler(udpChannelInitializer) + + executorService = Executors.newSingleThreadExecutor() + executorService.execute(this) + } + + override fun run() { + try { + val channelFuture = bootStrap.bind(LocaleConstant.LOCATION_UDP_PORT).sync() + channelFuture.channel().closeFuture().sync() + } catch (e: InterruptedException) { + e.printStackTrace() + } finally { + eventLoopGroup.shutdownGracefully() + } + } + + fun send(value: String) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value, CharsetUtil.UTF_8), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteArray) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun send(value: ByteBuf) { + CoroutineScope(Dispatchers.Main).launch { + withContext(Dispatchers.IO) { + val host = SaveKeyValues.getValue( + LocaleConstant.LOCATION_UDP_SERVER, "192.168.149.213" + ).toString() + val datagramPacket = DatagramPacket( + Unpooled.copiedBuffer(value), + InetSocketAddress(host, LocaleConstant.LOCATION_UDP_PORT) + ) + sendDatagramPacket(datagramPacket) + } + } + } + + fun release() { + releasePort() + } + + override fun receivedMessage(data: String) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt index 0dd119e..4c675df 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocaleConstant.kt @@ -50,6 +50,10 @@ const val DEV_NET_IP = "192.168.1.21" const val DEV_NET_PORT = "8000" + //位置信息UDP端口 + const val LOCATION_UDP_SERVER = "udpServer" + const val LOCATION_UDP_PORT = 7777 + //BroadcastReceiver Action const val ACTION_UPDATE_DATA = "update" diff --git a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt index 8e31d3f..df6adb5 100644 --- a/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt +++ b/app/src/main/java/com/casic/br/ktd/utils/LocationHelper.kt @@ -7,19 +7,40 @@ import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.util.Log import androidx.core.app.ActivityCompat +import com.amap.api.location.AMapLocation +import com.amap.api.location.AMapLocationClient +import com.amap.api.location.AMapLocationClientOption -object LocationHelper { - fun obtainCurrentLocation(context: Context, listener: ILocationListener) { +class LocationHelper private constructor() { + + private val kTag = "LocationHelper" + + companion object { + //Kotlin委托模式双重锁单例 + val get: LocationHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LocationHelper() + } + } + + private val locationOption = AMapLocationClientOption() + private lateinit var locationClient: AMapLocationClient + + init { + //设置定位模式为高精度模式,AMapLocationMode.Battery_Saving为低功耗模式,AMapLocationMode.Device_Sensors是仅设备模式 + locationOption.locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy + locationOption.isNeedAddress = true //设置是否返回地址信息(默认返回地址信息) + locationOption.isLocationCacheEnable = true //可选,设置是否使用缓存定位,默认为true + } + + fun obtainLocation(context: Context, listener: ILocationListener) { if (ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS + context, Manifest.permission.ACCESS_LOCATION_EXTRA_COMMANDS ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_FINE_LOCATION + context, Manifest.permission.ACCESS_FINE_LOCATION ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( - context, - Manifest.permission.ACCESS_COARSE_LOCATION + context, Manifest.permission.ACCESS_COARSE_LOCATION ) != PackageManager.PERMISSION_GRANTED ) { return @@ -38,7 +59,37 @@ }) } + /** + * 高德sdk定位 + */ + fun obtainLocationByGD(context: Context?, listener: ILocationListener) { + locationClient = AMapLocationClient(context) + //给定位客户端对象设置定位参数 + locationClient.setLocationOption(locationOption) + //设置定位回调监听 + locationClient.setLocationListener { aMapLocation -> + if (aMapLocation != null) { + if (aMapLocation.errorCode == 0) { + listener.onAMapLocationGet(aMapLocation) + } else { + Log.e( + kTag, + "ErrCode: ${aMapLocation.errorCode}, errInfo: ${aMapLocation.errorInfo}" + ) + } + } + } + //启动定位 + locationClient.startLocation() + } + + fun stopLocation() { + locationClient.stopLocation()//停止定位 + } + interface ILocationListener { - fun onLocationGet(location: Location?) //高德定位数据 + fun onLocationGet(location: Location?) //GPS定位数据 + + fun onAMapLocationGet(aMapLocation: AMapLocation?) //高德定位数据 } } \ No newline at end of file diff --git a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt index 02c47f8..c893092 100644 --- a/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt +++ b/app/src/main/java/com/casic/br/ktd/view/InspectionActivity.kt @@ -15,16 +15,21 @@ import android.view.SurfaceHolder import android.view.View import android.widget.AdapterView -import com.amap.api.maps.* +import com.amap.api.location.AMapLocation +import com.amap.api.maps.AMap +import com.amap.api.maps.CameraUpdateFactory +import com.amap.api.maps.UiSettings import com.amap.api.maps.model.CameraPosition import com.amap.api.maps.model.LatLng +import com.amap.api.maps.model.MyLocationStyle import com.amap.api.maps.model.PolylineOptions import com.casic.br.ktd.R import com.casic.br.ktd.extensions.* import com.casic.br.ktd.model.ChartPointModel import com.casic.br.ktd.model.RouteModel import com.casic.br.ktd.model.SensorDataModel -import com.casic.br.ktd.netty.SocketManager +import com.casic.br.ktd.netty.tcp.SocketManager +import com.casic.br.ktd.netty.udp.UdpClient import com.casic.br.ktd.utils.LocaleConstant import com.casic.br.ktd.utils.LocationHelper import com.casic.br.ktd.widgets.AlertControlDialog @@ -49,6 +54,10 @@ import hcnetsdk.sdkhub.MessageCodeHub import hcnetsdk.sdkhub.SDKGuider import kotlinx.android.synthetic.main.activity_inspection.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import java.text.DecimalFormat import java.text.SimpleDateFormat import java.util.* @@ -60,10 +69,10 @@ private val hkSDK by lazy { HCNetSDK.getInstance() } private val timeFormat by lazy { SimpleDateFormat("yyyyMMddHHmmss", Locale.CHINA) } private val latlngs = LinkedList() - private val speedTimer by lazy { Timer() } private val tcpTimer by lazy { Timer() } private val decimalFormat by lazy { DecimalFormat("##0.0") } private val gson by lazy { Gson() } + private val udpClient by lazy { UdpClient() } private var isLoginSuccess = false private var previewHandle = -1 private var selectChannel = -1 @@ -103,28 +112,32 @@ private lateinit var aMap: AMap private lateinit var uiSettings: UiSettings + /** + * 协程配置云台设备,缓解新进页面较卡的问题 + * */ private fun setDeviceConfig() { - Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") - val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() - device.m_szDevName = "" - device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( - LocaleConstant.HK_NET_IP, - LocaleConstant.HK_NET_PORT, - LocaleConstant.HK_NET_USERNAME, - LocaleConstant.HK_NET_PASSWORD - ) - if (device.m_szDevName.isEmpty()) { - device.m_szDevName = device.m_struNetInfo.m_szIp - } - isLoginSuccess = if ( - SDKGuider.sdkGuider.devManageGuider.login_v40_jna( - device.m_szDevName, device.m_struNetInfo - ) - ) { - "设备连接成功".show(this) - true - } else { - false + CoroutineScope(Dispatchers.Main).launch { + isLoginSuccess = withContext(Dispatchers.IO) { + Log.d(kTag, "setDeviceConfig => 配置设备IP和端口") + val device = SDKGuider.sdkGuider.devManageGuider.DeviceItem() + device.m_szDevName = "" + device.m_struNetInfo = SDKGuider.sdkGuider.devManageGuider.DevNetInfo( + LocaleConstant.HK_NET_IP, + LocaleConstant.HK_NET_PORT, + LocaleConstant.HK_NET_USERNAME, + LocaleConstant.HK_NET_PASSWORD + ) + if (device.m_szDevName.isEmpty()) { + device.m_szDevName = device.m_struNetInfo.m_szIp + } + + SDKGuider.sdkGuider.devManageGuider.login_v40_jna( + device.m_szDevName, device.m_struNetInfo + ) + } + if (isLoginSuccess) { + "设备连接成功".show(context) + } } } @@ -148,17 +161,74 @@ /** * 手机GPS定位 * */ - val converter = CoordinateConverter(this) - LocationHelper.obtainCurrentLocation(this, object : LocationHelper.ILocationListener { +// val converter = CoordinateConverter(this) +// LocationHelper.get.obtainLocation(this, object : LocationHelper.ILocationListener { +// override fun onLocationGet(location: Location?) { +// if (location == null) { +// "当前信号弱,无法定位".show(context) +// return +// } +// +// //发送位置信息给UDP服务端 +// udpClient.send("${location.longitude}, ${location.latitude}") +// Log.d(kTag, "onLocationGet => 速度:${location.speed}") +// +// //WGS-84要转为高德坐标系 +// converter.from(CoordinateConverter.CoordType.GPS) +// converter.coord(LatLng(location.latitude, location.longitude)) +// val latLng = converter.convert() +// latlngs.add(latLng) +// +// //移动到指定经纬度 +// val cameraPosition = CameraPosition(latLng, 13f, 0f, 0f) +// val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition) +// aMap.animateCamera(cameraUpdate, 1500, null) +// +// //绘制线 +// aMap.addPolyline( +// PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) +// ) +// +// if (isStartInspect) { +// val route = LinkedList() +// latlngs.forEach { +// route.add(RouteModel(it.latitude, it.longitude)) +// } +// } +// } +// +// override fun onAMapLocationGet(aMapLocation: AMapLocation?) { +// +// } +// }) + + /** + * 高德定位 + * */ + LocationHelper.get.obtainLocationByGD(this, object : LocationHelper.ILocationListener { override fun onLocationGet(location: Location?) { - if (location == null) { + + } + + override fun onAMapLocationGet(aMapLocation: AMapLocation?) { + if (aMapLocation == null) { "当前信号弱,无法定位".show(context) return } + + //发送位置信息给UDP服务端 + udpClient.send( + "${aMapLocation.longitude}, ${aMapLocation.latitude}, ${ + System.currentTimeMillis().timestampToCompleteDate() + }" + ) + //速度 转为 km/h + Log.d(kTag, "onLocationGet => 速度:${aMapLocation.speed}") + speed = decimalFormat.format(aMapLocation.speed * 3.6).toFloat() + carSpeedView.text = String.format("${speed}Km/h") + //WGS-84要转为高德坐标系 - converter.from(CoordinateConverter.CoordType.GPS) - converter.coord(LatLng(location.latitude, location.longitude)) - val latLng = converter.convert() + val latLng = LatLng(aMapLocation.latitude, aMapLocation.longitude) latlngs.add(latLng) //移动到指定经纬度 @@ -168,7 +238,7 @@ //绘制线 aMap.addPolyline( - PolylineOptions().addAll(latlngs).width(12f).color(Color.RED) + PolylineOptions().addAll(latlngs).width(10f).color(Color.RED) ) if (isStartInspect) { @@ -181,27 +251,6 @@ }) /** - * 计算速度,微分原理,3s计算一次速度 - * */ - speedTimer.schedule(object : TimerTask() { - override fun run() { - //经纬度链表数据小于2,无法计算速度,默认为0 - speed = if (latlngs.size < 2) 0.0f else { - val temp = AMapUtils.calculateLineDistance( - latlngs.last(), latlngs[latlngs.size - 2] - ) / 3 - //转为 km/h - decimalFormat.format(temp * 3.6).toFloat() - } - - //切换到UI线程更新界面数据 - runOnUiThread { - carSpeedView.text = String.format("${speed}Km/h") - } - } - }, 0, 3000) - - /** * TCP初始化 * ***/ SocketManager.instance.connectNetty( @@ -632,6 +681,8 @@ stopPreview() BroadcastManager.obtainInstance(this).destroy(LocaleConstant.ACTION_UPDATE_DATA) mapView.onDestroy() + udpClient.release() + LocationHelper.get.stopLocation() } override fun handleMessage(msg: Message): Boolean { @@ -716,38 +767,13 @@ uiSettings.isZoomControlsEnabled = true uiSettings.isTiltGesturesEnabled = false//不许地图随手势倾斜角度 -// val locationStyle = MyLocationStyle() -// locationStyle.interval(2000) -// locationStyle.showMyLocation(true) -// locationStyle.myLocationType(MyLocationStyle.LOCATION_TYPE_LOCATION_ROTATE) -// aMap.myLocationStyle = locationStyle -// aMap.isMyLocationEnabled = true -// aMap.animateCamera(CameraUpdateFactory.zoomTo(13f)) -// aMap.setOnMyLocationChangeListener(this) + //显示定位小蓝点 + val locationStyle = MyLocationStyle() + locationStyle.showMyLocation(true)//设置是否显示定位小蓝点 + aMap.myLocationStyle = locationStyle + aMap.isMyLocationEnabled = true } -// override fun onMyLocationChange(aMapLocation: Location?) { -// if (aMapLocation == null) { -// "当前信号弱,无法定位".show(context) -// return -// } -// val latitude = aMapLocation.latitude -// val longitude = aMapLocation.longitude -// latlngs.add(LatLng(latitude, longitude)) -// -// //绘制线 -// aMap.addPolyline( -// PolylineOptions().addAll(latlngs).width(15f).color(Color.RED) -// ) -// -// if (isStartInspect) { -// val route = LinkedList() -// latlngs.forEach { -// route.add(RouteModel(it.latitude, it.longitude)) -// } -// } -// } - /***以下是地图生命周期管理************************************************************************/ override fun onResume() { diff --git a/app/src/main/res/layout/fragment_alarm.xml b/app/src/main/res/layout/fragment_alarm.xml index a3ebc88..929529f 100644 --- a/app/src/main/res/layout/fragment_alarm.xml +++ b/app/src/main/res/layout/fragment_alarm.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/layout/fragment_task.xml b/app/src/main/res/layout/fragment_task.xml index 35cb1a3..58d852e 100644 --- a/app/src/main/res/layout/fragment_task.xml +++ b/app/src/main/res/layout/fragment_task.xml @@ -31,7 +31,7 @@ diff --git a/app/src/main/res/mipmap-hdpi/end.png b/app/src/main/res/mipmap-hdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-hdpi/start.png b/app/src/main/res/mipmap-hdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-hdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/end.png b/app/src/main/res/mipmap-mdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-mdpi/start.png b/app/src/main/res/mipmap-mdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-mdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/end.png b/app/src/main/res/mipmap-xhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xhdpi/start.png b/app/src/main/res/mipmap-xhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/end.png b/app/src/main/res/mipmap-xxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xxhdpi/start.png b/app/src/main/res/mipmap-xxhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xxhdpi/start.png Binary files differ diff --git a/app/src/main/res/mipmap-xxxhdpi/end.png b/app/src/main/res/mipmap-xxxhdpi/end.png new file mode 100644 index 0000000..f31385b --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/end.png Binary files differ diff --git a/app/src/main/res/mipmap-xxxhdpi/start.png b/app/src/main/res/mipmap-xxxhdpi/start.png new file mode 100644 index 0000000..04839dc --- /dev/null +++ b/app/src/main/res/mipmap-xxxhdpi/start.png Binary files differ diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d3dc8c6..289128c 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -36,6 +36,7 @@ 125dp 150dp 200dp + 275dp 1dp