refactor(mqtt): 优化MQTT连接管理与设备状态上报

- 使用DeviceStatus对象替代手动拼接JSON字符串
- 启用HiveMQ自动重连机制,移除自定义重连逻辑
- 添加连接状态标记防止并发重复建连
- 优化断线回调,记录服务端断开原因
- 使用设备序列号作为MQTT客户端标识
- 新增信号强度和真实屏幕分辨率获取方法
- 修复SystemUtils中系统属性获取方式
- 添加DeviceStatus单元测试
This commit is contained in:
TongTongStudio
2026-08-12 08:27:33 +08:00
parent ef34590570
commit 4e93deaf65
5 changed files with 408 additions and 103 deletions

View File

@@ -0,0 +1,73 @@
package com.ttstd.dialer.bean;
import com.google.gson.annotations.SerializedName;
/**
* MQTT 设备状态上报实体(心跳 / 屏幕亮灭)。
* <p>
* 序列化为 JSON{"sn":"...","status":"online","ip":"...","screenOn":true|false}
*/
public class DeviceStatus {
@SerializedName("sn")
private String sn;
@SerializedName("online")
private boolean online;
@SerializedName("ip")
private String ip;
@SerializedName("rssi")
private Integer rssi;
@SerializedName("screenOn")
private boolean screenOn;
public DeviceStatus(String sn, boolean online, String ip, Integer rssi, boolean screenOn) {
this.sn = sn;
this.online = online;
this.ip = ip;
this.rssi = rssi;
this.screenOn = screenOn;
}
public String getSn() {
return sn;
}
public void setSn(String sn) {
this.sn = sn;
}
public boolean isOnline() {
return online;
}
public void setOnline(boolean online) {
this.online = online;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getRssi() {
return rssi;
}
public void setRssi(Integer rssi) {
this.rssi = rssi;
}
public boolean isScreenOn() {
return screenOn;
}
public void setScreenOn(boolean screenOn) {
this.screenOn = screenOn;
}
}

View File

@@ -9,8 +9,15 @@ import android.text.TextUtils;
import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient;
import com.hivemq.client.mqtt.mqtt5.Mqtt5Client;
import com.hivemq.client.mqtt.mqtt5.Mqtt5ClientBuilder;
import com.hivemq.client.mqtt.lifecycle.MqttClientConnectedContext;
import com.hivemq.client.mqtt.lifecycle.MqttClientDisconnectedContext;
import com.hivemq.client.mqtt.lifecycle.MqttDisconnectSource;
import com.hivemq.client.mqtt.mqtt5.lifecycle.Mqtt5ClientConnectedContext;
import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAck;
import com.jeremyliao.liveeventbus.LiveEventBus;
import com.ttstd.dialer.bean.DeviceStatus;
import com.ttstd.dialer.config.CommonConfig;
import com.ttstd.dialer.gson.GsonUtils;
import com.ttstd.dialer.mdm.DeviceManagerService;
import com.ttstd.dialer.utils.Logger;
import com.ttstd.dialer.utils.SystemUtils;
@@ -24,6 +31,7 @@ import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
/**
* MQTT 消息管理
@@ -39,9 +47,9 @@ public class MqttManager {
/*MQTT 配置*/
public static final String MQTT_URL = "mqtt://175.178.213.60:1883";
public static final String MQTT_CLIENT_ID = "ttstd-dialer-android";
public static final String MQTT_USERNAME = "";
public static final String MQTT_PASSWORD = "";
// public static final String MQTT_CLIENT_ID = "ttstd-dialer-android";
public static final String MQTT_USERNAME = "tt";
public static final String MQTT_PASSWORD = "fanhuitong";
/*订阅主题*/
public static final String MQTT_TOPIC_COMMAND = "ttstd/device/command";
/*发布主题*/
@@ -62,16 +70,6 @@ public class MqttManager {
*/
private static final long HEARTBEAT_INTERVAL_MS = 30_000L;
/**
* 重连退避初始间隔(毫秒)
*/
private static final long RECONNECT_DELAY_INITIAL_MS = 1_000L;
/**
* 重连退避间隔上限(毫秒),超过此值不再翻倍
*/
private static final long RECONNECT_DELAY_MAX_MS = 64_000L;
@SuppressLint("StaticFieldLeak")
private static volatile MqttManager INSTANCE;
@@ -79,6 +77,11 @@ public class MqttManager {
private Mqtt5AsyncClient mMqttClient;
private volatile boolean mConnected;
/**
* 正在连接标记,防止并发调用 connect() 时重复 build 多个相同 clientId 的 client 互相踢
*/
private volatile boolean mConnecting;
/**
* 主动断开标记,避免主动断开时触发自动重连
*/
@@ -89,16 +92,6 @@ public class MqttManager {
*/
private final Handler mHeartbeatHandler = new Handler(Looper.getMainLooper());
/**
* 重连定时器(主线程 Handler
*/
private final Handler mReconnectHandler = new Handler(Looper.getMainLooper());
/**
* 重连退避间隔(毫秒),随失败次数指数增长
*/
private long mReconnectDelayMs = RECONNECT_DELAY_INITIAL_MS;
private boolean mHeartbeatRunning;
/**
@@ -146,7 +139,8 @@ public class MqttManager {
if (TextUtils.isEmpty(sn)) {
sn = "";
}
return ("{\"sn\":\"" + sn + "\",\"status\":\"offline\"}").getBytes(StandardCharsets.UTF_8);
return GsonUtils.toJSONString(new DeviceStatus(sn, true, getLocalIpAddress(), SystemUtils.getCurrentNetworkRssi(mContext), false))
.getBytes(StandardCharsets.UTF_8);
}
/**
@@ -157,8 +151,14 @@ public class MqttManager {
if (mMqttClient != null && mConnected) {
return;
}
// 已在连接中(上一次 connect() 的异步回调尚未返回),避免并发重复建连导致相同 clientId 互踢
if (mConnecting) {
Logger.e(TAG, "connect: 已有连接在进行中,跳过重复 connect");
return;
}
// 主动发起连接,清除主动断开标记,允许后续自动重连
mManualDisconnect = false;
mConnecting = true;
if (mMqttClient != null) {
try {
mMqttClient.disconnect();
@@ -172,46 +172,43 @@ public class MqttManager {
int port = uri.getPort() > 0 ? uri.getPort() : 1883;
Mqtt5ClientBuilder builder = Mqtt5Client.builder()
.identifier(MQTT_CLIENT_ID)
.identifier(SystemUtils.getSerial())
.serverHost(host)
.serverPort(port)
.addDisconnectedListener(context -> onDisconnected());
if (!TextUtils.isEmpty(MQTT_USERNAME)) {
builder.simpleAuth()
.username(MQTT_USERNAME)
.password(MQTT_PASSWORD.getBytes(StandardCharsets.UTF_8))
.applySimpleAuth();
}
// HiveMQ 自带断线自动重连(内置指数退避),避免与自定义重连叠加导致重复连接
.automaticReconnectWithDefaultConfig()
.addDisconnectedListener(this::onDisconnected)
.addConnectedListener(this::onConnected);
builder.simpleAuth()
.username(MQTT_USERNAME)
.password(MQTT_PASSWORD.getBytes(StandardCharsets.UTF_8))
.applySimpleAuth();
mMqttClient = builder.buildAsync();
mMqttClient.connectWith()
.keepAlive(30)
.cleanStart(false) // 复用历史会话
.sessionExpiryInterval(TimeUnit.DAYS.toSeconds(1)) // 断开后会话保留
.willPublish()
.topic(MQTT_TOPIC_STATUS)
.payload(buildWillPayload())
.qos(com.hivemq.client.mqtt.datatypes.MqttQos.AT_LEAST_ONCE)
.retain(false)
.applyWillPublish()
.topic(MQTT_TOPIC_STATUS)
.payload(buildWillPayload())
.qos(com.hivemq.client.mqtt.datatypes.MqttQos.AT_LEAST_ONCE)
.retain(false)
.applyWillPublish()
.send()
.whenComplete((connAck, throwable) -> {
// 首次 connect 异步结果返回,复位连接中标记
mConnecting = false;
if (throwable != null) {
String msg = throwable.getMessage();
Logger.e(TAG, "connect: 连接失败 " + msg
+ ",将自动重试连接 " + host + ":" + port);
Logger.e(TAG, "connect: 首次连接失败 " + msg
+ "HiveMQ 将自动重 " + host + ":" + port);
setConnected(false);
// 首次连接失败也安排重连(网络不可达等场景)
scheduleReconnect();
} else {
Logger.e(TAG, "connect: 连接成功");
mReconnectDelayMs = RECONNECT_DELAY_INITIAL_MS;
setConnected(true);
// 连接成功后重新订阅之前订阅的主题
reSubscribeAll();
// 连接成功后启动心跳上报
startHeartbeat();
// 连接成功后上报一次当前屏幕开关状态
reportScreenStateOnConnect();
// 首次连接成功(后续重连成功会走 onConnected 回调)
onConnected();
}
});
} catch (Throwable e) {
@@ -221,55 +218,71 @@ public class MqttManager {
}
/**
* 断线回调:若非主动断开,则按指数退避安排重连
* 连接成功回调(首次 connect 异步结果返回时调用,无 context 信息)
*/
private void onDisconnected() {
private void onConnected() {
onConnected(null);
}
/**
* 连接成功回调(首次连接与 HiveMQ 自动重连成功都会触发)
*/
private void onConnected(MqttClientConnectedContext context) {
boolean sessionPresent = false;
if (context instanceof Mqtt5ClientConnectedContext) {
Mqtt5ConnAck connAck = ((Mqtt5ClientConnectedContext) context).getConnAck();
sessionPresent = connAck != null && connAck.isSessionPresent();
}
Logger.e(TAG, "onConnected: 连接成功 sessionPresent=" + sessionPresent);
setConnected(true);
// 连接成功后重新订阅之前订阅的主题
reSubscribeAll();
// 连接成功后启动心跳上报
startHeartbeat();
// 连接成功后上报一次当前屏幕开关状态
reportScreenStateOnConnect();
}
/**
* 断线回调:仅做状态清理与心跳停止,重连交由 HiveMQ 自动重连处理,
* 不再自行调用 connect(),避免与 HiveMQ 的重连叠加导致重复连接。
* 同时打印 Broker 返回的断开原因,便于定位反复断开的根因。
*/
private void onDisconnected(MqttClientDisconnectedContext context) {
if (mManualDisconnect) {
Logger.e(TAG, "onDisconnected: 主动断开,不重连");
return;
}
Logger.e(TAG, "onDisconnected: 连接断开,准备自动重连");
// 解析断开来源与原因,区分"服务端踢"与"网络/客户端问题"
String source = context.getSource() != null ? context.getSource().name() : "UNKNOWN";
String cause = context.getCause() != null ? context.getCause().toString() : "null";
if (context.getSource() == MqttDisconnectSource.SERVER) {
// 服务端主动断开(如 clientId 冲突、鉴权失败、协议违规reason code 有价值
Logger.e(TAG, "onDisconnected: 服务端主动断开 source=" + source
+ " cause=" + cause
+ ",等待 HiveMQ 自动重连");
} else {
Logger.e(TAG, "onDisconnected: 连接断开 source=" + source
+ " cause=" + cause
+ ",等待 HiveMQ 自动重连");
}
setConnected(false);
stopHeartbeat();
scheduleReconnect();
}
/**
* 安排一次延迟重连(指数退避,上限 30s
*/
private void scheduleReconnect() {
mReconnectHandler.removeCallbacks(mReconnectRunnable);
Logger.e(TAG, "scheduleReconnect: " + (mReconnectDelayMs / 1000) + "s 后重连");
mReconnectHandler.postDelayed(mReconnectRunnable, mReconnectDelayMs);
// 下次退避间隔翻倍,封顶 30s
mReconnectDelayMs = Math.min(mReconnectDelayMs * 2, RECONNECT_DELAY_MAX_MS);
}
private final Runnable mReconnectRunnable = new Runnable() {
@Override
public void run() {
if (mManualDisconnect) {
return;
}
Logger.e(TAG, "reconnect: 尝试重新连接...");
connect();
}
};
/**
* 断开 MQTT 连接(主动断开,先发离线消息再断连,不会触发遗言)
*/
public void disconnect() {
// 标记主动断开,重连任务与断线监听据此跳过重连
// 标记主动断开,断线监听据此跳过重连
mManualDisconnect = true;
mReconnectHandler.removeCallbacks(mReconnectRunnable);
stopHeartbeat();
try {
if (mMqttClient != null && mConnected) {
// 主动上报离线消息,确保服务端即时感知设备下线
String sn = DeviceManagerService.getInstance().getSerial();
if (!TextUtils.isEmpty(sn)) {
String offlinePayload = "{\"sn\":\"" + sn + "\",\"status\":\"offline\"}";
String offlinePayload = GsonUtils.toJSONString(new DeviceStatus(sn, false, getLocalIpAddress(), SystemUtils.getCurrentNetworkRssi(mContext), false));
publish(MQTT_TOPIC_STATUS, offlinePayload);
}
mMqttClient.disconnect();
@@ -427,12 +440,11 @@ public class MqttManager {
}
/**
* 构建设备状态上报 JSON{"sn":"...","status":"online","ip":"...","screenOn":true|false}
* 构建设备状态上报 JSON{"sn":"...","online":"online","ip":"...","screenOn":true|false}
*/
private String buildStatusPayload(String sn, boolean screenOn) {
String ip = getLocalIpAddress();
return "{\"sn\":\"" + sn + "\",\"status\":\"online\",\"ip\":\"" + (ip == null ? "" : ip)
+ "\",\"screenOn\":" + screenOn + "}";
DeviceStatus status = new DeviceStatus(sn, true, getLocalIpAddress(), SystemUtils.getCurrentNetworkRssi(mContext), screenOn);
return GsonUtils.toJSONString(status);
}
/**
@@ -452,7 +464,7 @@ public class MqttManager {
} catch (Throwable e) {
Logger.e(TAG, "getLocalIpAddress: " + e.getMessage());
}
return "";
return "127.0.0.1";
}
private void onMessageReceived(com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish publish) {

View File

@@ -18,6 +18,8 @@ import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.Rect;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
@@ -26,9 +28,13 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.telephony.CellInfo;
import android.telephony.CellSignalStrength;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
@@ -78,15 +84,14 @@ public class SystemUtils {
* @return
*/
public static String getProperty(String key, String defaultValue) {
String value = defaultValue;
// SystemProperties.get();
try {
Class<?> c = Class.forName("android.os.SystemProperties");
Method get = c.getMethod("get", String.class, String.class);
value = (String) (get.invoke(c, key, Build.UNKNOWN));
return (String) (get.invoke(c, key, defaultValue));
} catch (Exception e) {
e.printStackTrace();
} finally {
return value;
return defaultValue;
}
}
@@ -366,6 +371,135 @@ public class SystemUtils {
return "";
}
/**
* 获取当前蜂窝信号强度等级0~4对应 ASU 的粗略分级4 最强)。
* 该值可用于状态栏信号格显示,与 {@link android.telephony.SignalStrength#getLevel()} 一致。
*
* @param context 上下文
* @return 信号等级 0~4无 SIM / 非蜂窝网络 / 获取失败返回 -1
*/
@SuppressLint("MissingPermission")
public static int getSignalLevel(Context context) {
try {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null) {
return -1;
}
// Android 9 (API 28) 及以下无 getAllCellInfo/SignalStrength 的公开 getLevel
// 这里统一用 getAllCellInfo 取最强小区的 level覆盖绝大多数系统版本。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
List<CellInfo> cellInfos = tm.getAllCellInfo();
if (cellInfos != null) {
int maxLevel = -1;
for (CellInfo info : cellInfos) {
if (info == null || !info.isRegistered()) {
continue;
}
CellSignalStrength strength = info.getCellSignalStrength();
if (strength != null) {
maxLevel = Math.max(maxLevel, strength.getLevel());
}
}
if (maxLevel >= 0) {
return maxLevel;
}
}
}
// 兜底API 29+ 可直接取 SignalStrength 的 level
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
SignalStrength signalStrength = tm.getSignalStrength();
if (signalStrength != null) {
return signalStrength.getLevel();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
/**
* 获取当前蜂窝信号强度dBm如 -75 表示较强,-113 接近无信号)。
* 优先使用各制式 CellInfo 的 dBm找不到则回退到 ASU 换算asu - 113 - 2 * (asu & 1))。
*
* @param context 上下文
* @return 信号强度 dBm无信号 / 获取失败返回 {@code null}
*/
@SuppressLint("MissingPermission")
public static Integer getSignalDbm(Context context) {
try {
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null) {
return null;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
List<CellInfo> cellInfos = tm.getAllCellInfo();
if (cellInfos != null) {
int bestDbm = Integer.MIN_VALUE;
for (CellInfo info : cellInfos) {
if (info == null || !info.isRegistered()) {
continue;
}
CellSignalStrength strength = info.getCellSignalStrength();
if (strength != null) {
int dbm = strength.getDbm();
if (dbm != Integer.MAX_VALUE && dbm > bestDbm) {
bestDbm = dbm;
}
}
}
if (bestDbm != Integer.MIN_VALUE) {
return bestDbm;
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
SignalStrength signalStrength = tm.getSignalStrength();
if (signalStrength != null) {
int dbm = signalStrength.getDbm();
if (dbm != Integer.MAX_VALUE && dbm != Integer.MIN_VALUE) {
return dbm;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 根据当前活跃网络获取信号强度 (RSSI/dBm)
* 优先返回 Wi-Fi 信号强度,如果未连接 Wi-Fi 则返回移动网络信号强度
*
* @param context 上下文
* @return 信号强度 (dBm);获取失败返回 null
*/
public static Integer getCurrentNetworkRssi(Context context) {
try {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) return null;
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
if (activeNetwork == null || !activeNetwork.isConnected()) {
return null;
}
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
WifiManager wifiManager = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null) {
return info.getRssi();
}
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
return getSignalDbm(context);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String getNetworkTypeName(int networkType) {
switch (networkType) {
case TelephonyManager.NETWORK_TYPE_GPRS:
@@ -504,11 +638,11 @@ public class SystemUtils {
}
public static String getBuildId() {
return getProperty(Build.ID, Build.UNKNOWN);
return getProperty("ro.build.id", Build.ID);
}
public static String getBuildDisplayId() {
return getProperty(Build.DISPLAY, Build.UNKNOWN);
return getProperty("ro.build.display.id", Build.DISPLAY);
}
// ==================================================================
@@ -527,7 +661,7 @@ public class SystemUtils {
}
public static String getDeviceManufacturer() {
return getProperty(Build.MANUFACTURER, Build.UNKNOWN);
return getProperty("ro.product.manufacturer", Build.MANUFACTURER);
}
public static String getHardware() {
@@ -535,7 +669,7 @@ public class SystemUtils {
}
public static String getHost() {
return getProperty(Build.HOST, Build.UNKNOWN);
return getProperty("ro.build.host", Build.HOST);
}
public static String getAndroidId(Context context) {
@@ -547,23 +681,23 @@ public class SystemUtils {
}
public static String getBuildFingerprint() {
return getProperty(Build.FINGERPRINT, Build.UNKNOWN);
return getProperty("ro.build.fingerprint", Build.FINGERPRINT);
}
public static String getBuildType() {
return getProperty(Build.TYPE, Build.UNKNOWN);
return getProperty("ro.build.type", Build.TYPE);
}
public static String getBuildUser() {
return getProperty(Build.USER, Build.UNKNOWN);
return getProperty("ro.build.user", Build.USER);
}
public static String getBuildHost() {
return getProperty(Build.HOST, Build.UNKNOWN);
return getProperty("ro.build.host", Build.HOST);
}
public static String getBuildTags() {
return getProperty(Build.TAGS, Build.UNKNOWN);
return getProperty("ro.build.tags", Build.TAGS);
}
public static long getBuildTime() {
@@ -600,13 +734,30 @@ public class SystemUtils {
public static String getScreenResolution(Context context) {
try {
android.util.DisplayMetrics dm = context.getResources().getDisplayMetrics();
android.util.DisplayMetrics dm = getRealDisplayMetrics(context);
return dm.widthPixels + "x" + dm.heightPixels;
} catch (Throwable t) {
return "";
}
}
/**
* 获取设备真实屏幕显示参数(包含状态栏、导航栏,不被窗口裁切)。
* 优先使用 getRealMetrics显示设备真实分辨率低版本无此 API 时回退 getMetrics。
*/
private static android.util.DisplayMetrics getRealDisplayMetrics(Context context) {
android.util.DisplayMetrics dm = new android.util.DisplayMetrics();
android.view.WindowManager wm =
(android.view.WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
android.view.Display display = wm.getDefaultDisplay();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
display.getRealMetrics(dm);
} else {
display.getMetrics(dm);
}
return dm;
}
public static int getScreenDensity(Context context) {
try {
return (int) Math.round(context.getResources().getDisplayMetrics().density);
@@ -635,7 +786,7 @@ public class SystemUtils {
}
public static String getProductName() {
return getProperty(Build.PRODUCT, Build.UNKNOWN);
return getProperty("ro.product.name", Build.PRODUCT);
}
public static String getProductModel() {
@@ -682,7 +833,7 @@ public class SystemUtils {
}
public static String getBootloader() {
return getProperty(Build.BOOTLOADER, Build.UNKNOWN);
return getProperty("ro.bootloader", Build.BOOTLOADER);
}
public static String getBasebandVersion() {
@@ -699,7 +850,7 @@ public class SystemUtils {
public static String getScreenSize(Context context) {
try {
android.util.DisplayMetrics dm = context.getResources().getDisplayMetrics();
android.util.DisplayMetrics dm = getRealDisplayMetrics(context);
double diagonal = Math.sqrt(Math.pow(dm.widthPixels / dm.xdpi, 2)
+ Math.pow(dm.heightPixels / dm.ydpi, 2));
return String.format(Locale.US, "%.1f", diagonal);
@@ -710,7 +861,7 @@ public class SystemUtils {
public static double getScreenInch(Context context) {
try {
android.util.DisplayMetrics dm = context.getResources().getDisplayMetrics();
android.util.DisplayMetrics dm = getRealDisplayMetrics(context);
double diagonal = Math.sqrt(Math.pow(dm.widthPixels / dm.xdpi, 2)
+ Math.pow(dm.heightPixels / dm.ydpi, 2));
return Math.round(diagonal * 10.0) / 10.0;

View File

@@ -0,0 +1,69 @@
package com.ttstd.dialer.bean;
import com.google.gson.Gson;
import org.junit.Assert;
import org.junit.Test;
public class DeviceStatusTest {
@Test
public void testConstructorAndGetters() {
String sn = "SN123456";
boolean online = true;
String ip = "192.168.1.100";
Integer rssi = -65;
boolean screenOn = true;
DeviceStatus status = new DeviceStatus(sn, online, ip, rssi, screenOn);
Assert.assertEquals(sn, status.getSn());
Assert.assertEquals(online, status.isOnline());
Assert.assertEquals(ip, status.getIp());
Assert.assertEquals(rssi, status.getRssi());
Assert.assertEquals(screenOn, status.isScreenOn());
}
@Test
public void testSetters() {
DeviceStatus status = new DeviceStatus(null, false, null, null, false);
status.setSn("NEW_SN");
status.setOnline(true);
status.setIp("10.0.0.1");
status.setRssi(-70);
status.setScreenOn(true);
Assert.assertEquals("NEW_SN", status.getSn());
Assert.assertTrue(status.isOnline());
Assert.assertEquals("10.0.0.1", status.getIp());
Assert.assertEquals(Integer.valueOf(-70), status.getRssi());
Assert.assertTrue(status.isScreenOn());
}
@Test
public void testJsonSerialization() {
DeviceStatus status = new DeviceStatus("SN123", true, "1.1.1.1", -50, true);
Gson gson = new Gson();
String json = gson.toJson(status);
// 验证 JSON 字符串中包含正确的字段和值
Assert.assertTrue(json.contains("\"sn\":\"SN123\""));
Assert.assertTrue(json.contains("\"online\":true"));
Assert.assertTrue(json.contains("\"ip\":\"1.1.1.1\""));
Assert.assertTrue(json.contains("\"rssi\":-50"));
Assert.assertTrue(json.contains("\"screenOn\":true"));
}
@Test
public void testJsonDeserialization() {
String json = "{\"sn\":\"SN999\",\"online\":false,\"ip\":\"8.8.8.8\",\"rssi\":-80,\"screenOn\":false}";
Gson gson = new Gson();
DeviceStatus status = gson.fromJson(json, DeviceStatus.class);
Assert.assertEquals("SN999", status.getSn());
Assert.assertFalse(status.isOnline());
Assert.assertEquals("8.8.8.8", status.getIp());
Assert.assertEquals(Integer.valueOf(-80), status.getRssi());
Assert.assertFalse(status.isScreenOn());
}
}

View File

@@ -19,7 +19,7 @@ base_dp=360
# Also need to match the phone screen of [match_dp].
# If you have another dp values.
# System default values is 240,320,384,392,400,410,411,480,533,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365
match_dp=
match_dp=240,260,280,300,320,360,362.2641,380,384,390,392.7272,400,410,411.4285,430,432,451.7647,480,533,540,592,600,640,662,720,768,800,811,820,960,961,1024,1280,1365
# If you not wanna to match dp values above. Write some above values here, append value with "," .
# For example: 811,961,1365
ignore_dp=