保姆级教程:用Android Studio + Paho MQTT库,30分钟搞定STM32环境监测APP
2026/6/6 17:07:41 网站建设 项目流程

30分钟实战:用Android Studio与Paho MQTT构建STM32环境监测APP

在物联网设备爆发式增长的今天,STM32+ESP8266的组合已成为环境监测领域的经典硬件方案。但许多开发者面临一个共同困境:硬件数据采集完成后,如何快速构建一个稳定可靠的手机端数据可视化应用?本文将带你用Android Studio和Paho MQTT库,在30分钟内完成从零到可用的环境监测APP开发。

1. 开发环境快速配置

1.1 创建基础工程

启动Android Studio后选择"Empty Activity"模板,建议将最低API级别设置为21(Android 5.0)以兼顾大多数设备。在build.gradle(Module:app)中添加以下关键依赖:

dependencies { implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.1.0' implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1' implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' }

注意:Paho Android Service 1.1.1版本存在已知的内存泄漏问题,建议在Application类中初始化全局MqttAndroidClient实例。

1.2 权限与服务声明

AndroidManifest.xml中添加必要的网络权限和服务声明:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <application> <service android:name="org.eclipse.paho.android.service.MqttService" /> </application>

2. MQTT连接核心实现

2.1 连接参数配置

创建MqttConfig.kt数据类存储连接参数:

data class MqttConfig( val serverUri: String = "tcp://your_broker:1883", val clientId: String = "android_${System.currentTimeMillis()}", val username: String? = null, val password: String? = null, val keepAlive: Int = 60, val cleanSession: Boolean = true )

2.2 建立MQTT连接

在ViewModel中实现连接逻辑:

class MqttViewModel : ViewModel() { private lateinit var mqttClient: MqttAndroidClient fun connect(config: MqttConfig, context: Context) { mqttClient = MqttAndroidClient( context, config.serverUri, config.clientId ).apply { setCallback(object : MqttCallback { override fun connectionLost(cause: Throwable) { // 处理连接断开 } override fun messageArrived(topic: String, message: MqttMessage) { // 处理接收消息 } override fun deliveryComplete(token: IMqttDeliveryToken) {} }) } val options = MqttConnectOptions().apply { isCleanSession = config.cleanSession keepAliveInterval = config.keepAlive userName = config.username password = config.password?.toCharArray() } try { mqttClient.connect(options).actionCallback = object : IMqttActionListener { override fun onSuccess(asyncActionToken: IMqttToken) { subscribeToTopic("environment/data") } override fun onFailure(asyncActionToken: IMqttToken, exception: Throwable) { // 连接失败处理 } } } catch (e: MqttException) { e.printStackTrace() } } }

3. 数据解析与界面展示

3.1 STM32数据格式解析

假设硬件端发送的数据格式为JSON:

{ "temp": 25.3, "humidity": 45.2, "light": 1024, "timestamp": 1634567890 }

创建数据模型类:

data class EnvironmentData( val temp: Float, val humidity: Float, val light: Int, val timestamp: Long )

在消息回调中解析数据:

override fun messageArrived(topic: String, message: MqttMessage) { try { val data = Gson().fromJson(message.toString(), EnvironmentData::class.java) _liveData.postValue(data) } catch (e: JsonSyntaxException) { Log.e("MQTT", "Invalid data format: ${message.toString()}") } }

3.2 实时数据可视化

使用Jetpack Compose构建响应式UI:

@Composable fun EnvironmentScreen(viewModel: MqttViewModel) { val data by viewModel.liveData.observeAsState() Column(modifier = Modifier.padding(16.dp)) { data?.let { GaugeChart( value = it.temp, minValue = -10f, maxValue = 50f, label = "Temperature (°C)" ) Spacer(modifier = Modifier.height(16.dp)) GaugeChart( value = it.humidity, minValue = 0f, maxValue = 100f, label = "Humidity (%)" ) Spacer(modifier = Modifier.height(16.dp)) LightIndicator(lightValue = it.light) } ?: Text("Waiting for data...") } }

4. 关键问题解决方案

4.1 后台服务保活

AndroidManifest.xml中声明前台服务:

<service android:name=".MqttForegroundService" android:foregroundServiceType="dataSync" />

实现前台服务:

class MqttForegroundService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val notification = createNotification() startForeground(1, notification) return START_STICKY } private fun createNotification(): Notification { return NotificationCompat.Builder(this, "mqtt_channel") .setContentTitle("Environment Monitor") .setContentText("Receiving sensor data") .setSmallIcon(R.drawable.ic_sensor) .build() } }

4.2 网络状态处理

注册广播接收器监听网络变化:

private val networkReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val activeNetwork = connectivityManager.activeNetworkInfo if (activeNetwork?.isConnected == true) { reconnectIfNeeded() } } } fun registerNetworkReceiver(context: Context) { val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION) context.registerReceiver(networkReceiver, filter) }

5. 性能优化技巧

5.1 消息QoS设置

根据场景选择合适的服务质量等级:

QoS级别可靠性网络负载适用场景
0最低最低实时性要求高,允许丢包
1中等中等重要数据,需确保至少一次送达
2最高最高关键数据,需确保仅一次送达
fun subscribeToTopic(topic: String) { try { mqttClient.subscribe(topic, 1) } catch (e: MqttException) { Log.e("MQTT", "Subscribe failed", e) } }

5.2 数据缓存策略

使用Room数据库缓存历史数据:

@Entity data class EnvironmentRecord( @PrimaryKey val timestamp: Long, val temperature: Float, val humidity: Float, val light: Int ) @Dao interface EnvironmentDao { @Insert suspend fun insert(record: EnvironmentRecord) @Query("SELECT * FROM EnvironmentRecord ORDER BY timestamp DESC LIMIT 100") fun getRecentRecords(): LiveData<List<EnvironmentRecord>> }

在ViewModel中实现自动保存:

private val _liveData = MutableLiveData<EnvironmentData>() val liveData: LiveData<EnvironmentData> = _liveData init { viewModelScope.launch { _liveData.asFlow() .debounce(1000) // 每秒保存一次 .collect { data -> database.environmentDao().insert( EnvironmentRecord( timestamp = data.timestamp, temperature = data.temp, humidity = data.humidity, light = data.light ) ) } } }

6. 扩展功能实现

6.1 异常警报系统

设置阈值触发通知:

private fun checkThresholds(data: EnvironmentData) { if (data.temp > 30f) { showNotification("High Temperature Alert", "Current: ${data.temp}°C") } if (data.humidity < 30f) { showNotification("Low Humidity Alert", "Current: ${data.humidity}%") } } private fun showNotification(title: String, message: String) { val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val channel = NotificationChannel( "alerts", "Environment Alerts", NotificationManager.IMPORTANCE_HIGH ) notificationManager.createNotificationChannel(channel) val notification = NotificationCompat.Builder(this, "alerts") .setContentTitle(title) .setContentText(message) .setSmallIcon(R.drawable.ic_alert) .setAutoCancel(true) .build() notificationManager.notify(Random.nextInt(), notification) }

6.2 数据导出功能

实现CSV格式数据导出:

fun exportToCsv(context: Context): File { val records = database.environmentDao().getRecordsSync() val file = File(context.getExternalFilesDir(null), "environment_data.csv") file.bufferedWriter().use { writer -> writer.write("Timestamp,Temperature,Humidity,Light\n") records.forEach { record -> writer.write("${record.timestamp},${record.temperature},${record.humidity},${record.light}\n") } } return file }

在开发过程中发现,使用LiveData结合Flow可以极大简化数据管道的构建,而将业务逻辑集中在ViewModel中则使Activity/Fragment保持轻量。对于需要长期运行的MQTT连接,将其封装在独立的前台服务中是保证稳定性的关键。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询