Android App连接OneNET物联网平台实战:用OkHttp3获取MQTTS设备数据(附完整代码)
Android App连接OneNET物联网平台实战:用OkHttp3获取MQTTS设备数据(附完整代码)
在物联网应用开发中,设备数据的实时获取与展示是核心需求之一。本文将手把手带你完成一个完整的Android应用开发实战,通过OkHttp3库连接OneNET物联网平台,获取MQTTS协议设备数据并展示在UI上。无论你是刚接触物联网开发的Android程序员,还是需要快速集成OneNET平台的经验开发者,这篇指南都能提供可直接复用的代码模块和避坑经验。
1. 项目准备与环境配置
在开始编码前,我们需要完成基础环境配置。不同于简单的网络请求,物联网应用需要特别注意安全配置和权限管理。
首先在AndroidManifest.xml中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />对于Android 9.0及以上版本,还需要配置网络安全策略。在res/xml目录下创建network_security_config.xml文件:
<?xml version="1.0" encoding="utf-8"?> <network-security-config> <base-config cleartextTrafficPermitted="true"> <trust-anchors> <certificates src="system" /> </trust-anchors> </base-config> </network-security-config>然后在AndroidManifest.xml的application标签中引用这个配置:
android:networkSecurityConfig="@xml/network_security_config"提示:虽然我们允许明文传输(cleartextTrafficPermitted="true")用于开发测试,但生产环境建议使用HTTPS加密通信。
2. 依赖引入与基础架构
我们将使用OkHttp3进行网络请求,在app/build.gradle中添加依赖:
implementation 'com.squareup.okhttp3:okhttp:4.9.3'最新版本建议查看OkHttp官网。相比原文使用的3.10.0版本,4.x系列在性能和安全性上都有显著提升。
基础Activity布局(activity_main.xml)可以这样设计:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <Button android:id="@+id/btnFetchData" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="获取设备数据" /> <TextView android:id="@+id/tvData1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:textSize="16sp" /> <!-- 可根据需要添加更多TextView展示不同数据点 --> </LinearLayout>3. OneNET平台认证机制解析
OneNET平台提供两种主要认证方式:
| 认证方式 | 适用协议 | 特点 | 有效期 |
|---|---|---|---|
| API-Key | HTTP/HTTPS | 简单直接,在header中传递 | 永久有效 |
| Authorization | MQTTS | 需要生成token,安全性更高 | 通常3小时 |
关键避坑点:对于MQTTS协议设备,必须使用Authorization token认证,API-Key方式将无法获取数据,这是官方文档中未明确说明的重要细节。
Token生成需要使用官方提供的算法,以下是Java版token生成工具类:
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.util.Base64; public class TokenUtil { public static String generateToken(String productId, String deviceId, String accessKey, long expiry) { try { String version = "2022-05-01"; String resource = String.format("products/%s/devices/%s", productId, deviceId); String signatureMethod = "md5"; String data = String.join("\n", version, resource, String.valueOf(expiry), signatureMethod); Mac hmac = Mac.getInstance("HmacSHA1"); SecretKeySpec secretKey = new SecretKeySpec( accessKey.getBytes(StandardCharsets.UTF_8), "HmacSHA1"); hmac.init(secretKey); byte[] hash = hmac.doFinal(data.getBytes(StandardCharsets.UTF_8)); String signature = Base64.getEncoder().encodeToString(hash); return String.format("version=%s&res=%s&et=%d&method=%s&sign=%s", version, resource, expiry, signatureMethod, signature); } catch (Exception e) { e.printStackTrace(); return null; } } }注意:官方提供的在线token计算工具可能存在兼容性问题,建议使用代码生成token以确保可靠性。
4. 数据请求与UI更新实现
现在我们可以实现核心的数据获取逻辑。在MainActivity中:
public class MainActivity extends AppCompatActivity { private static final String TAG = "OneNETDemo"; private static final String DEVICE_ID = "your_device_id"; private static final String PRODUCT_ID = "your_product_id"; private static final String ACCESS_KEY = "your_access_key"; private OkHttpClient client; private TextView tvData1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); client = new OkHttpClient(); tvData1 = findViewById(R.id.tvData1); Button btnFetch = findViewById(R.id.btnFetchData); btnFetch.setOnClickListener(v -> fetchDeviceData()); } private void fetchDeviceData() { new Thread(() -> { try { // 生成有效期为1小时的token long expiry = System.currentTimeMillis() / 1000 + 3600; String token = TokenUtil.generateToken( PRODUCT_ID, DEVICE_ID, ACCESS_KEY, expiry); if (token == null) { runOnUiThread(() -> Toast.makeText(this, "Token生成失败", Toast.LENGTH_SHORT).show()); return; } String url = String.format( "https://api.heclouds.com/devices/%s/datapoints?limit=1", DEVICE_ID); Request request = new Request.Builder() .url(url) .addHeader("Authorization", token) .build(); Response response = client.newCall(request).execute(); if (!response.isSuccessful()) { throw new IOException("Unexpected code " + response); } String responseData = response.body().string(); JSONObject json = new JSONObject(responseData); JSONArray datastreams = json.getJSONObject("data") .getJSONArray("datastreams"); // 解析第一个数据流 JSONObject datastream = datastreams.getJSONObject(0); String id = datastream.getString("id"); JSONArray datapoints = datastream.getJSONArray("datapoints"); String value = datapoints.getJSONObject(0).getString("value"); String displayText = String.format("%s: %s", id, value); runOnUiThread(() -> tvData1.setText(displayText)); } catch (Exception e) { Log.e(TAG, "请求失败", e); runOnUiThread(() -> Toast.makeText(this, "请求失败: " + e.getMessage(), Toast.LENGTH_SHORT).show()); } }).start(); } }5. 高级功能与优化建议
5.1 多数据流处理
实际项目中,设备通常会有多个数据流。我们可以优化代码处理这种情况:
private void processMultipleDatastreams(JSONArray datastreams) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < datastreams.length(); i++) { JSONObject datastream = datastreams.getJSONObject(i); String id = datastream.getString("id"); String value = datastream.getJSONArray("datapoints") .getJSONObject(0).getString("value"); sb.append(String.format("%s: %s\n", id, value)); } runOnUiThread(() -> tvData1.setText(sb.toString())); }5.2 数据刷新机制
实现定时自动刷新数据:
private final Handler handler = new Handler(); private final Runnable refreshRunnable = new Runnable() { @Override public void run() { fetchDeviceData(); handler.postDelayed(this, 5000); // 每5秒刷新一次 } }; // 在onResume中启动刷新 @Override protected void onResume() { super.onResume(); handler.post(refreshRunnable); } // 在onPause中停止刷新 @Override protected void onPause() { super.onPause(); handler.removeCallbacks(refreshRunnable); }5.3 错误处理与重试机制
健壮的网络请求需要完善的错误处理:
private void fetchWithRetry(int retryCount) { new Thread(() -> { int attempts = 0; while (attempts < retryCount) { try { fetchDeviceData(); return; } catch (Exception e) { attempts++; if (attempts == retryCount) { runOnUiThread(() -> Toast.makeText(this, "最终请求失败", Toast.LENGTH_SHORT).show()); } else { try { Thread.sleep(2000); // 等待2秒后重试 } catch (InterruptedException ie) { Thread.currentThread().interrupt(); } } } } }).start(); }6. 性能优化与安全建议
- OkHttpClient复用:避免为每个请求创建新实例,使用单例模式管理
- 连接池配置:优化HTTP连接管理
- 缓存策略:对不常变的数据启用缓存
- HTTPS证书校验:生产环境应严格校验服务器证书
- 敏感信息保护:不要将设备ID和密钥硬编码在代码中
示例优化后的OkHttpClient配置:
private OkHttpClient createHttpClient() { return new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES)) .addInterceptor(new HttpLoggingInterceptor().setLevel( BuildConfig.DEBUG ? HttpLoggingInterceptor.Level.BODY : HttpLoggingInterceptor.Level.NONE)) .build(); }在实际项目中,设备认证信息应该通过安全的方式获取,如后台API或Android Keystore系统。
