ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

安卓智能家居源码项目实战:架构、MQTT通信与设备接入全解析

安卓智能家居源码项目实战:架构、MQTT通信与设备接入全解析 简介一份面向安卓智能家居开发的完整客户端源码适合学习Android开发、物联网及智能家居方向的开发者、在校学生及研究人员。源码不是零散Demo而是集蓝牙连接、PDA端与智能终端通信、手势识别、视频传输、串口信号处理、多传感器报警于一体的系统性工程项目编码UTF-8默认编译版本4.0.3。压缩包共含356个文件大小仅4.46MB包括219个png界面图片、46个xml布局与配置文件、12个java源码及55个class文件并附带jar库、apk安装包和so动态库等类型覆盖从界面到逻辑再到运行包的完整链路。资源还包含老人看护系统智能终端例程与效果图便于对照学习整体架构、通信协议及手势交互设计目前已有1678人学习下载适合作为智能家居项目开发或毕业设计的参考。1. 智能家居项目为什么都从安卓端切入这几年接到的智能家居相关咨询里十个人有八个第一句话都是我想做个安卓App来控制家里的设备。这个现象不奇怪安卓在智能家居生态里的位置太特殊了——它既是控制中枢又是设备端系统还是开发者的试验田。先说控制中枢这层。绝大多数智能家居网关、智能音箱、带屏中控面板底层跑的都是安卓。即便不是完整安卓也是基于AOSP裁剪的定制系统。所以你写一个安卓App天然能覆盖从手机到中控屏的一大片设备。用同一套代码逻辑稍作适配就能在不同形态的设备上跑这个性价比是iOS给不了的。再说设备端。像带屏的智能门锁、厨房中控、卧室床头面板很多直接用安卓主板方案常见的有全志、瑞芯微、MTK这些平台。开发者在这样的设备上做应用层开发本质上就是写安卓App。项目里如果涉及安卓核心板这类硬件选型软件层面几乎绕不开安卓。还有个现实因素安卓开发的学习资料、开源组件、社区方案都太丰富了。我做智能家居项目这么久深切的感受是——生态成熟度直接决定项目能不能落地。很多功能比如局域网设备发现、MQTT长连接、本地HTTP接口调用在安卓上都有现成库可以直接用踩坑成本比从零造轮子低得多。所以这篇内容不打算讲那种hello world级别的入门而是从一个完整可落地的安卓智能家居源码项目出发拆解它的架构、通信、UI、设备接入这几个核心模块把我在实际项目里验证过的东西拿出来说。2. 一套能跑的智能家居源码应该包含哪些模块先说结论一个合格的安卓智能家居项目代码结构上至少要覆盖设备发现、通信协议、数据管理、界面展示、消息推送这五块。缺了任何一块项目都只能算demo离能用差得远。2.1 设备发现模块是地基智能家居App打开的第一件事不是连接服务器而是发现局域网里的设备。常见的方案有三种UDP广播发现App往局域网广播地址发一条UDP报文设备收到后回复自己的IP、设备类型、设备名。优点是实现简单缺点是广播包在复杂网络环境下可能丢。mDNS/DNS-SD发现类似安卓上的NSD服务设备注册服务类型App按类型发现。安卓自带NsdManager不用引第三方库就能做。结合云端的设备列表拉取App登录账号后从云端拿设备列表再逐个尝试局域网直连。这个方案最接近商用产品。我在实测中发现纯UDP广播在普通家用路由器上表现不错但在开启了AP隔离的网络环境里会直接失败。所以商用项目普遍采用的是第三种方案——先走云端拿列表局域网通就P2P直连不通就走云端中转。2.2 通信层决定稳定性的上限智能家居的通信协议选择直接决定项目的稳定性。目前主流就这几条路线MQTT over TCP设备端和App都连同一个MQTT Broker通过Topic订阅/发布消息。这是目前最主流的方案适合状态上报、指令下发这种场景。HTTP RESTful API适合配置类操作比如修改设备名称、获取设备详情。实时性要求不高的场景用HTTP反而更简单。长连接WebSocket有些设备端用WebSocket和App通信好处是报文格式灵活调试也方便。私有TCP协议一些嵌入式设备是自己定义的二进制协议这时候App端只能做协议解析层去适配。我在项目里建议的搭配是MQTT负责实时指令和状态HTTP负责管理操作两者互补。纯MQTT写所有逻辑会显得臃肿纯HTTP做实时控制在网络抖动时体验会非常差。2.3 数据层别只想着SQLite智能家居App要存的数据其实分两类一类是设备属性、房间布局这类结构化数据适合放SQLite或Room另一类是设备上报的历史记录、日志数据这种用普通数据库存久了会膨胀得很厉害。实际项目里我的做法是结构化数据用Room历史数据只保留最近N条在本地其余依赖云端存储。App端本地库撑死就放最近一周的数据再多就是给自己找麻烦。2.4 UI层别套模板要理解家居场景很多源码项目的UI一看就是从管理后台模板改的卡片堆砌、列表套列表用户根本找不到设备在哪。智能家居的UI核心是场景化——用户要的是离家一键关灯关空调不是在列表里一个个翻设备。所以UI层至少要包含房间维度的设备分组、场景模式入口回家/离家/睡眠、设备控制面板的动态渲染。动态渲染是关键不同设备类型的控制面板差异很大灯的开关面板、空调的温度面板、窗帘的开合面板不可能用同一套布局。2.5 消息推送和服务保活手机锁屏后怎么收到设备告警这就涉及到推送。国内安卓生态推送比较头疼主流方案是接入厂商推送通道或者用WebSocket长连接做自建推送。源码项目里通常用的是后者因为不需要申请厂商推送的账号权限跑起来就能看效果。保活这块多说一句不要迷信各种保活黑科技在安卓高版本上后台限制越来越严老老实实用前台服务加通知栏常驻是最稳妥的方案。3. 核心代码结构拆解从入口到设备控制的完整链路网上能找到的安卓智能家居源码不少但很多结构混乱改起来想摔键盘。我自己整理项目时习惯按功能模块分包下面这套结构是我在几个落地项目里验证过的直接抄作业就行。com.example.smarthome ├── app │ ├── App.java // Application初始化全局配置 │ ├── MainActivity.java // 首页 │ ├── DeviceListActivity.java // 设备列表页 │ ├── DeviceControlActivity.java // 设备控制页 │ └── SceneActivity.java // 场景页面 ├── base │ ├── BaseActivity.java │ ├── BaseFragment.java │ └── BaseAdapter.java ├── data │ ├── local │ │ ├── AppDatabase.java // Room数据库 │ │ └── DeviceDao.java │ ├── remote │ │ ├── ApiService.java // Retrofit接口 │ │ └── MqttManager.java // MQTT封装 │ └── repository │ └── DeviceRepository.java // 仓库层 ├── model │ ├── Device.java │ ├── Scene.java │ └── Room.java ├── mqtt │ ├── MqttClientManager.java │ └── MqttMessageHandler.java ├── discovery │ └── DeviceDiscovery.java // 局域网发现 ├── ui │ ├── adapter │ ├── fragment │ └── viewmodel └── utils ├── SPUtil.java ├── LogUtil.java └── ByteUtil.java3.1 MQTT连接封装是重中之重MQTT这块用Eclipse Paho库是最常见的但直接把Paho的API撒在业务代码里后期维护会很痛苦。我习惯在外面再包一层把连接、订阅、发布、回调都收拢到一个类里。public class MqttManager { private MqttClient client; private MqttConnectOptions options; private String brokerUrl; private String clientId; public MqttManager(String brokerUrl, String clientId) { this.brokerUrl brokerUrl; this.clientId clientId; init(); } private void init() { client new MqttClient(brokerUrl, clientId, new MemoryPersistence()); options new MqttConnectOptions(); options.setCleanSession(true); options.setConnectionTimeout(10); options.setKeepAliveInterval(30); options.setAutomaticReconnect(true); } public void connect() throws MqttException { client.setCallback(new MqttCallback() { Override public void connectionLost(Throwable cause) { // 连接断开重连逻辑交给automaticReconnect } Override public void messageArrived(String topic, MqttMessage message) { MessageDispatcher.dispatch(topic, message); } Override public void deliveryComplete(IMqttDeliveryToken token) { // 消息发送成功回调 } }); client.connect(options); } public void publish(String topic, String payload) { MqttMessage message new MqttMessage(payload.getBytes()); message.setQos(1); try { client.publish(topic, message); } catch (MqttException e) { e.printStackTrace(); } } public void subscribe(String topic) { try { client.subscribe(topic, 1); } catch (MqttException e) { e.printStackTrace(); } } }有个细节需要注意setAutomaticReconnect(true)这个属性不是所有Paho版本都支持我早期用旧版本的时候网络切换后连接根本不会自动恢复只能手动重连。后来升级了版本又加了监听MqttCallbackExtended在connectComplete里重新订阅Topic才算彻底解决。QoS选择上设备控制指令建议用QoS 1保证消息至少到达一次。状态上报可以用QoS 0丢一帧状态问题不大下次还会再报。QoS 2在智能家居场景基本没必要会带来额外的消息开销。3.2 设备发现服务的实现细节局域网发现设备我最早用DatagramSocket写UDP广播后来发现一个问题安卓8.0之后有些路由器会拦截跨网段的广播包导致发现失败。后来改成用MulticastSocket走组播可靠性高了不少。public class DeviceDiscovery { private static final String MULTICAST_ADDR 239.255.255.250; private static final int PORT 8888; private MulticastSocket socket; public void startDiscovery() { new Thread(() - { try { socket new MulticastSocket(PORT); socket.setInterface(InetAddress.getByName(wlan0)); socket.joinGroup(InetAddress.getByName(MULTICAST_ADDR)); byte[] buf new byte[1024]; DatagramPacket packet new DatagramPacket(buf, buf.length); socket.setSoTimeout(3000); while (true) { socket.receive(packet); String message new String(packet.getData(), 0, packet.getLength()); parseAndHandleMessage(message, packet.getAddress()); } } catch (SocketTimeoutException e) { // 一轮发现结束 } catch (IOException e) { e.printStackTrace(); } }).start(); } }这个方案有个坑setInterface和joinGroup在某些设备上会抛NetworkInterfaceException。我实际遇到的情况是同一套代码在不同手机上表现不一致后来统一改成遍历网络接口寻找isUp()且!isLoopback()的IPv4地址来绑定稳定性好了很多。组播端口的选取也要避开常用端口我见过有人用8888端口和本地服务冲突排查了半天。建议用1024以上的高位端口比如6000到9000之间降低冲突概率。3.3 设备控制指令的协议设计App发给设备的指令一般就是JSON格式的字符串。设计协议时我习惯了引用一个统一的数据结构{ cmd: control, deviceId: living_room_light_001, params: { power: on, brightness: 80 }, timestamp: 1716000000000 }这里的cmd字段区分指令类型deviceId是设备唯一标识params是具体控制参数。注意时间戳一定要带我踩过的坑是设备端会根据时间戳判断指令是否过期如果App和设备的时钟偏差太大指令会被直接丢弃。设备状态上报的格式类似只不过cmd变成了reportparams里放的是状态值。4. 设备接入层怎么兼容不同厂商的设备协议智能家居项目做到后期一定会遇到接入第三方便签设备的需求。市面上不同品牌的插座、灯、传感器通信协议千奇百怪这时候就体现出协议适配层的重要性了。4.1 抽象出统一的设备接口我在项目里定义了一个IDevice接口所有具体设备都实现这个接口public interface IDevice { String getDeviceId(); String getDeviceType(); void connect(); void disconnect(); void sendCommand(String command, MapString, Object params); void onStatusChanged(StatusCallback callback); String parseStatus(String rawData); }parseStatus这个方法特别关键——设备上报的原始数据五花八门有的JSON有的十六进制字符串有的带转义字符。把这个解析逻辑隔离在每个设备实现类里上层只认统一的DeviceStatus对象就不会乱成一锅粥。4.2 一个实际的MQTT协议适配案例我的一个项目里接入了一款第三方红外遥控器设备。它用的是MQTT协议Topic格式是/device/{deviceId}/commands和/device/{deviceId}/status。但它有个比较特殊的地方状态上报不是主动推送而是要先发一条查询指令设备才回复状态。这种设备适配的时候代码要这么处理public class IRDevice implements IDevice { private static final String COMMAND_TOPIC_PREFIX /device/; private static final String STATUS_TOPIC_PREFIX /device/; Override public void sendCommand(String command, MapString, Object params) { String topic COMMAND_TOPIC_PREFIX deviceId /commands; JSONObject json new JSONObject(); json.put(command, command); json.put(params, new JSONObject(params)); mqttManager.publish(topic, json.toString()); } Override public void onStatusChanged(StatusCallback callback) { String topic STATUS_TOPIC_PREFIX deviceId /status; mqttManager.subscribe(topic); this.statusCallback callback; } Override public String parseStatus(String rawData) { // 有些厂商会在状态里带额外的转义字符需要先清理干净 String cleaned rawData.replace(\\\, \); JSONObject json new JSONObject(cleaned); return json.getString(status); } }适配第三方设备时最耗时的是抓协议和调试。我常用的调试技巧是在电脑上用MQTTX连同一个Broker同时订阅App和设备双方的Topic看消息流向这样能快速定位是App没发出去还是设备没回复还是回复格式解析失败。4.3 协议适配层的测试思路这一层代码的质量直接影响整个项目测试不能省。我的做法是分三层测试单元测试Mock掉MQTT连接验证JSON组装和解析是否正确。这层能拦下80%的格式错误。协议仿真测试在电脑上写一个TCP服务模拟设备端用真实的数据格式回包验证App的解析逻辑。真机联调拿真实设备跑完整流程这层主要测网络异常、弱网环境下的表现。真实项目里的坑大部分出现在字段名大小写不一致、多余的换行符、数字类型和字符串类型混用这种细节上。这些问题在单元测试里根本发现不了只有拿真实数据跑一遍才暴露。5. 源码项目的常见坑与实测避雷写这套源码的过程中有几类问题反复出现我把它们整理成一份避雷清单每次新项目启动前都会过一遍。5.1 MQTT连接频繁掉线的根源我以前遇到过一个特别诡异的问题App连着WiFi一切正常切到移动网络后MQTT就断有时候断了自己能重连有时候彻底连不上。排查了很久发现原因有两个方面一个原因是Broker的maxKeepAlive设置太小。当时Broker设在阿里云上默认心跳超时只有60秒而客户端每30秒发一次心跳中间一旦有网络抖动Broker就判定超时断开。后来把Broker侧的keepalive调大到300秒问题解决。另一个原因是移动网络下NAT超时时间比WiFi短TCP连接很容易被运营商掐断。这个没法从代码层面根治只能靠setAutomaticReconnect(true)加断线重连机制兜底。同时不要忘记在connectComplete里重新订阅Topic否则重连成功但Topic一个都没订阅设备状态照样收不到。5.2 局域网设备发现的兼容性坑前面讲了UDP广播和组播这里再多说一个安卓高版本的变化。安卓6.0之后WiFi扫描和组播需要运行时权限很多人代码写对了但忘了在MainActivity里动态请求ACCESS_FINE_LOCATION权限导致发现设备一直失败。这个坑特别隐蔽因为报错信息不直观看起来像网络问题。正确的做法是在App启动时先检查权限并且要说明申请定位权限的目的是扫描附近的智能设备不然应用市场上架审核的时候也会被挑战。5.3 UI渲染性能设备一多就卡顿设备数量超过50个时RecyclerView滑动卡顿几乎必然出现。问题不在RecyclerView本身而在于onBindViewHolder里做了太多耗时操作。我踩过的一个坑是设备状态图标用了VectorDrawable但在列表加载时调用vectorDrawable.setTint()频繁换色这个操作在部分低端安卓机上很费性能。优化方案是直接用ImageView.setImageTintList()配合预加载的Drawable避免动态创建。还有一个容易被忽略的点就是notifyDataSetChanged()在设备状态刷新时被频繁调用。我后来改成了DiffUtil只更新变化的item流畅度提升了一个档次。这个方案的原理就是对比新旧列表找出差异再更新UI几百个设备也hold得住。5.4 后台运行的保活方案实测源码项目里普遍会在AndroidManifest.xml里声明一个常驻Service用来监听MQTT消息。但安卓高版本对后台Service限制很严实测在小米、华为这些国产ROM上Service跑一段时间就会被杀。实测最稳定的方案是前台服务高优先级通知引导用户加入电池白名单。代码层面前台服务启动方式if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { NotificationChannel channel new NotificationChannel( smart_home_service, 智能家居服务, NotificationManager.IMPORTANCE_LOW ); NotificationManager manager getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); } Notification notification new NotificationCompat.Builder(this, smart_home_service) .setContentTitle(智能家居服务运行中) .setContentText(保持连接以接收设备状态) .setSmallIcon(R.drawable.ic_notification) .setPriority(NotificationCompat.PRIORITY_LOW) .build(); startForeground(1001, notification);注意通知等级一定要用IMPORTANCE_LOW或PRIORITY_LOW否则会持续发出通知声音用户会直接卸载你的App。从整个项目落地过程来看安卓智能家居源码的精髓不在于某一项技术用得多深而在于把设备发现、通信、数据、UI、保活这些环节串联成一个稳定闭环的能力。这里面的每一项单独拿出来都有现成轮子但组合起来之后出现的各种配合问题才是真正需要花时间去磨的地方。我自己的体会是动手写这套源码之前最好先把通信协议定下来尤其是Topic规则和消息格式这个设计得好后面所有工作都会顺畅很多。如果只是东拼西凑Demo代码后面改起来真的会想骂人。希望这篇拆解能给你的智能家居项目一个清晰的方向少走我走过的那些弯路。本文还有配套的精品资源点击获取
返回列表