Browse Source

fix: ci

master
高志龙 1 day ago
parent
commit
ada231241c
  1. 2
      app/build.gradle
  2. 7
      app/src/main/java/qianmu/container/activity/BaseActivity.java
  3. 6
      app/src/main/java/qianmu/container/activity/H5/WebViewActivity.java
  4. 10
      app/src/main/java/qianmu/container/activity/program/MyPresentation.java
  5. 9
      app/src/main/java/qianmu/container/activity/program/ScreenSaverActivity.java
  6. 88
      app/src/main/java/qianmu/container/activity/program/ViewScreenSaver.java
  7. 4
      app/src/main/java/qianmu/container/app/Constant.java
  8. 18
      app/src/main/java/qianmu/container/data/PowerData.java
  9. 146
      app/src/main/java/qianmu/container/handler/ContainerHandler.java
  10. 56
      app/src/main/java/qianmu/container/mqtt/MQTTService.java
  11. 6
      app/src/main/java/qianmu/container/util/LoggerUtil.java
  12. 2
      app/src/main/java/qianmu/container/util/SignWayUtil.java
  13. 9
      app/src/main/java/qianmu/container/view/CustomerVideoView.java
  14. 16
      app/src/main/res/layout/view_exo_texture_player.xml

2
app/build.gradle

@ -12,7 +12,7 @@ android {
minSdkVersion 24 minSdkVersion 24
targetSdkVersion 30 targetSdkVersion 30
versionCode 6 versionCode 6
versionName "V2.0.8.32"
versionName "V2.0.8.34"
//V2.0.8.22 ai背景视频切换代码 //V2.0.8.22 ai背景视频切换代码
//V2.0.8.23 //V2.0.8.23
//V2.0.8.30 4 //V2.0.8.30 4

7
app/src/main/java/qianmu/container/activity/BaseActivity.java

@ -151,10 +151,9 @@ public abstract class BaseActivity extends AppCompatActivity {
// 重启app // 重启app
restartApp(); restartApp();
} else if (Constant.ACTION_RESTART_MQTT.equals(message.getCode())) { } else if (Constant.ACTION_RESTART_MQTT.equals(message.getCode())) {
// 重启mqtt服务
LoggerUtil.e("BaseActivity", "通知关闭mqtt服务");
stopService(new Intent(this, MQTTService.class));// 关闭Mqtt服务
Constant.mqttState = "off";
// 服务内部断开重连,避免 stopService/startService 与 static client 的竞态
LoggerUtil.e("BaseActivity", "通知重连mqtt服务");
MQTTService.restartConnection();
} }

6
app/src/main/java/qianmu/container/activity/H5/WebViewActivity.java

@ -312,7 +312,11 @@ public class WebViewActivity extends BaseActivity {
wv.setWebChromeClient(new WebChromeClient() { wv.setWebChromeClient(new WebChromeClient() {
public boolean onConsoleMessage(ConsoleMessage cm) { public boolean onConsoleMessage(ConsoleMessage cm) {
if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) { if (cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) {
LoggerUtil.e("WebView_ConsoleError", "lineNumber: " + cm.lineNumber());
String msg = cm.message();
if (msg != null && msg.length() > 120) {
msg = msg.substring(0, 120);
}
LoggerUtil.e("WebView_ConsoleError", msg);
} else if (cm.message().contains("THREE.WebGLRenderer:") || cm.message().contains("Uncaught (in promise) AbortError")) { } else if (cm.message().contains("THREE.WebGLRenderer:") || cm.message().contains("Uncaught (in promise) AbortError")) {
LoggerUtil.e("WebView日志", cm.message()); LoggerUtil.e("WebView日志", cm.message());
} }

10
app/src/main/java/qianmu/container/activity/program/MyPresentation.java

@ -2651,7 +2651,10 @@ class MyPresentation extends Presentation {
videoView2.setOnErrorListener(new CustomerVideoView.OnErrorListener() { videoView2.setOnErrorListener(new CustomerVideoView.OnErrorListener() {
@Override @Override
public boolean onError() { public boolean onError() {
//视频播放失败
//视频播放失败:切下一个素材,避免出错后永久卡在当前帧
LoggerUtil.e("MyPresentation", "videoView2播放出错,切换下一个素材");
handler.removeMessages(TYPE_UPDATE_VIDEO0);
handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDEO0, 1000);
return true; return true;
} }
}); });
@ -2697,7 +2700,10 @@ class MyPresentation extends Presentation {
videoView3.setOnErrorListener(new CustomerVideoView.OnErrorListener() { videoView3.setOnErrorListener(new CustomerVideoView.OnErrorListener() {
@Override @Override
public boolean onError() { public boolean onError() {
//视频播放失败
//视频播放失败:切下一个素材,避免出错后永久卡在当前帧
LoggerUtil.e("MyPresentation", "videoView3播放出错,切换下一个素材");
handler.removeMessages(TYPE_UPDATE_VIDEO1);
handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDEO1, 1000);
return true; return true;
} }
}); });

9
app/src/main/java/qianmu/container/activity/program/ScreenSaverActivity.java

@ -542,9 +542,14 @@ public class ScreenSaverActivity extends BaseActivity {
public void dataCallBack(int code, String data) { public void dataCallBack(int code, String data) {
LoggerUtil.e("QHT: 开关屏:", "code:" + code + ",data:" + data); LoggerUtil.e("QHT: 开关屏:", "code:" + code + ",data:" + data);
if("CLOSE".equals(state)){ if("CLOSE".equals(state)){
MyApplication.getInstance().stopService(new Intent(MyApplication.getInstance(),MQTTService.class));
if(Constant.mqttState.equals("on")){
MQTTService.stopAndDisableReconnect(MyApplication.getInstance());
}
}else{ }else{
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
if(Constant.mqttState.equals("off")){
MQTTService.manualStopped = false;
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
}
} }
if(code == 65350){ if(code == 65350){
login(state); login(state);

88
app/src/main/java/qianmu/container/activity/program/ViewScreenSaver.java

@ -21,6 +21,7 @@ import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.TranslateAnimation; import android.view.animation.TranslateAnimation;
import android.webkit.ConsoleMessage; import android.webkit.ConsoleMessage;
@ -540,8 +541,8 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
binding.videoView0.setVideoPath(localPath); binding.videoView0.setVideoPath(localPath);
} else { } else {
// 图片 // 图片
Drawable drawable = new BitmapDrawable(localPath);
binding.videoView0.setBackground(drawable);
setSampledBackground(binding.videoView0, localPath,
components.getWidth(), components.getHeight());
} }
} }
} else { } else {
@ -551,8 +552,8 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
binding.videoView0.setVideoPath(localPath); binding.videoView0.setVideoPath(localPath);
} else { } else {
// 图片 // 图片
Drawable drawable = new BitmapDrawable(localPath);
binding.videoView0.setBackground(drawable);
setSampledBackground(binding.videoView0, localPath,
components.getWidth(), components.getHeight());
} }
} }
@ -1710,7 +1711,17 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
} }
if (webList != null && webList.size() > 0) { if (webList != null && webList.size() > 0) {
for (WebView webView : webList) { for (WebView webView : webList) {
binding.relativeLayoutMax.removeView(webView);
if (webView == null) continue;
try {
webView.stopLoading();
webView.loadUrl("about:blank");
webView.removeAllViews();
ViewParent p = webView.getParent();
if (p instanceof ViewGroup) ((ViewGroup) p).removeView(webView);
webView.destroy();
} catch (Throwable t) {
LoggerUtil.e("deleteView()销毁WebView", StringUtil.getThrowableStr(t));
}
} }
webList.clear(); webList.clear();
} }
@ -2867,7 +2878,10 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
binding.videoView0.setOnErrorListener(new CustomerVideoView.OnErrorListener() { binding.videoView0.setOnErrorListener(new CustomerVideoView.OnErrorListener() {
@Override @Override
public boolean onError() { public boolean onError() {
// 视频播放失败
// 视频播放失败:切下一个素材,避免出错后永久卡在当前帧
LoggerUtil.e("ViewScreenSaver", "videoView0播放出错,切换下一个素材");
handler.removeMessages(TYPE_UPDATE_VIDEO0);
handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDEO0, 1000);
return true; return true;
} }
}); });
@ -2913,7 +2927,10 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
binding.videoView1.setOnErrorListener(new CustomerVideoView.OnErrorListener() { binding.videoView1.setOnErrorListener(new CustomerVideoView.OnErrorListener() {
@Override @Override
public boolean onError() { public boolean onError() {
// 视频播放失败
// 视频播放失败:切下一个素材,避免出错后永久卡在当前帧
LoggerUtil.e("ViewScreenSaver", "videoView1播放出错,切换下一个素材");
handler.removeMessages(TYPE_UPDATE_VIDEO1);
handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDEO1, 1000);
return true; return true;
} }
}); });
@ -2956,7 +2973,10 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
binding.videoView2.setOnErrorListener(new CustomerVideoView.OnErrorListener() { binding.videoView2.setOnErrorListener(new CustomerVideoView.OnErrorListener() {
@Override @Override
public boolean onError() { public boolean onError() {
// 视频播放失败
// 视频播放失败:切下一个素材,避免出错后永久卡在当前帧
LoggerUtil.e("ViewScreenSaver", "videoView2播放出错,切换下一个素材");
handler.removeMessages(TYPE_UPDATE_VIDEO2);
handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDEO2, 1000);
return true; return true;
} }
}); });
@ -2999,7 +3019,10 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
binding.videoView3.setOnErrorListener(new CustomerVideoView.OnErrorListener() { binding.videoView3.setOnErrorListener(new CustomerVideoView.OnErrorListener() {
@Override @Override
public boolean onError() { public boolean onError() {
// 视频播放失败
// 视频播放失败:切下一个素材,避免出错后永久卡在当前帧
LoggerUtil.e("ViewScreenSaver", "videoView3播放出错,切换下一个素材");
handler.removeMessages(TYPE_UPDATE_VIDEO3);
handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDEO3, 1000);
return true; return true;
} }
}); });
@ -3105,10 +3128,53 @@ public class ViewScreenSaver extends ViewBase<ViewScreenSaverBinding> {
} }
handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDE, videoComponents.getConfig().getTransitionPeriod() * 1000); handler.sendEmptyMessageDelayed(TYPE_UPDATE_VIDE, videoComponents.getConfig().getTransitionPeriod() * 1000);
Drawable drawable = new BitmapDrawable(localPath);
videoView.setBackground(drawable);
// 按目标尺寸降采样解码并回收旧背景,避免循环切换时全尺寸Bitmap累积导致native内存OOM静默重启
setSampledBackground(videoView, localPath, videoComponents.getWidth(), videoComponents.getHeight());
}
}
/**
* 给视频控件设置静态图背景按目标尺寸降采样解码并回收上一张背景Bitmap
* 替代废弃的 new BitmapDrawable(String) 全尺寸解码防止内存泄漏导致播放过程中OOM重启
*/
private void setSampledBackground(View targetView, String localPath, int reqWidth, int reqHeight) {
try {
Drawable old = targetView.getBackground();
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(localPath, opts);
opts.inSampleSize = calculateInSampleSize(opts, reqWidth, reqHeight);
opts.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(localPath, opts);
if (bitmap == null) return;
targetView.setBackground(new BitmapDrawable(context.getResources(), bitmap));
// 回收旧背景的Bitmap
if (old instanceof BitmapDrawable) {
Bitmap oldBmp = ((BitmapDrawable) old).getBitmap();
if (oldBmp != null && !oldBmp.isRecycled()) oldBmp.recycle();
}
} catch (Throwable t) {
LoggerUtil.e("setSampledBackground", StringUtil.getThrowableStr(t));
} }
}
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int height = options.outHeight;
int width = options.outWidth;
int inSampleSize = 1;
if (reqWidth <= 0 || reqHeight <= 0) return inSampleSize;
if (height > reqHeight || width > reqWidth) {
int halfHeight = height / 2;
int halfWidth = width / 2;
while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
} }
/** /**

4
app/src/main/java/qianmu/container/app/Constant.java

@ -24,12 +24,12 @@ public class Constant {
public static String TTSHome="sbc"; // sbc-思必驰 kdxf-科大讯飞 (有语音的项目需要配置) public static String TTSHome="sbc"; // sbc-思必驰 kdxf-科大讯飞 (有语音的项目需要配置)
// public static String androidBoardType = ""; //设备板子型号 无固定版 // public static String androidBoardType = ""; //设备板子型号 无固定版
// public static String androidBoardType = "ys"; // 设备板子型号 ys(亿晟) 北京颐堤港定制touch // public static String androidBoardType = "ys"; // 设备板子型号 ys(亿晟) 北京颐堤港定制touch
// public static String androidBoardType = "xwst"; //设备板子型号 xwst(欣威视通3399)
public static String androidBoardType = "xwst"; //设备板子型号 xwst(欣威视通3399)
// public static String androidBoardType = "xwst2"; //设备板子型号 xwst2(欣威视通3588、T982、3576) // public static String androidBoardType = "xwst2"; //设备板子型号 xwst2(欣威视通3588、T982、3576)
// public static String androidBoardType = "zc"; //设备板子型号 zc(卓策主板——王府井喜悦、杨浦中心医院) // public static String androidBoardType = "zc"; //设备板子型号 zc(卓策主板——王府井喜悦、杨浦中心医院)
// public static String androidBoardType = "sx"; //设备板子型号 sx(视想) // public static String androidBoardType = "sx"; //设备板子型号 sx(视想)
// public static String androidBoardType = "nova"; //设备板子型号 诺瓦盒子 华贸LED // public static String androidBoardType = "nova"; //设备板子型号 诺瓦盒子 华贸LED
public static String androidBoardType = "huidu"; //设备板子型号 huidu(灰度主板) 罗湖寻车机
// public static String androidBoardType = "huidu"; //设备板子型号 huidu(灰度主板) 罗湖寻车机
// public static String androidBoardType = "bv"; //设备板子型号 Bv-3588M // public static String androidBoardType = "bv"; //设备板子型号 Bv-3588M
// public static String androidBoardType = "smt"; //设备板子型号 视美泰 // public static String androidBoardType = "smt"; //设备板子型号 视美泰
// public static String androidBoardType = "ctf"; //创泰丰 // public static String androidBoardType = "ctf"; //创泰丰

18
app/src/main/java/qianmu/container/data/PowerData.java

@ -141,6 +141,21 @@ public class PowerData extends BaseData {
} }
} }
//欣威视通:定时开机时间前后40秒内重启一次设备(防止定时开机后黑屏/异常),每天仅重启一次
if(Constant.androidBoardType.equals("xwst") && !bootTime.isEmpty()){
long currentTime = System.currentTimeMillis();
String s = TimeUtil.stampToDate(currentTime);
long bootLong = TimeUtil.pareTLong2(s + " " + bootTime);//开机时间
String today = new SimpleDateFormat("yyyy-MM-dd").format(currentTime);
if(Math.abs(currentTime-bootLong)<300000
&& !DeviceData.getDeviceInfo(DeviceData.DEVICE_RESTART_TIME).equals(today)){
LoggerUtil.e("PowerData","xwst开机时间后5分钟内,重启设备");
DeviceData.saveDeviceInfo(DeviceData.DEVICE_RESTART_TIME, today);
SignWayUtil.reboot();
return;
}
}
String dataJson = getDataJson(NAME, POWER_INFO, "{}"); String dataJson = getDataJson(NAME, POWER_INFO, "{}");
//跳过开机的第一次设置 //跳过开机的第一次设置
if(newTimeInfo.equals(dataJson)){ if(newTimeInfo.equals(dataJson)){
@ -222,7 +237,7 @@ public class PowerData extends BaseData {
long bootLong2 = bootLong+24*60*60*1000;//第二天开机时间 long bootLong2 = bootLong+24*60*60*1000;//第二天开机时间
if(Constant.androidBoardType.equals("xwst")){ if(Constant.androidBoardType.equals("xwst")){
SignWayUtil.clearPowerOnOffTime();
if(parameterLong > bootLong) { if(parameterLong > bootLong) {
//当天关机,第二天开机 //当天关机,第二天开机
SignWayUtil.setPowerOnTime("1", date2[0], date2[1], date2[2], on2[0], on2[1]); SignWayUtil.setPowerOnTime("1", date2[0], date2[1], date2[2], on2[0], on2[1]);
@ -241,7 +256,6 @@ public class PowerData extends BaseData {
LoggerUtil.e("PowerData()", "关机时间:"+s + " " + parameter+",开机时间:"+s + " " + bootTime); LoggerUtil.e("PowerData()", "关机时间:"+s + " " + parameter+",开机时间:"+s + " " + bootTime);
} }
} }
}else if (Constant.androidBoardType.equals("xwst2")){ }else if (Constant.androidBoardType.equals("xwst2")){
SdkApi.getInstance().TimerSwitch().setTimerSwitchOnoff(true); SdkApi.getInstance().TimerSwitch().setTimerSwitchOnoff(true);
SdkApi.getInstance().TimerSwitch().setTimerType(true); SdkApi.getInstance().TimerSwitch().setTimerType(true);

146
app/src/main/java/qianmu/container/handler/ContainerHandler.java

@ -78,6 +78,7 @@ public class ContainerHandler extends Handler {
public static final int INIT_JXB2 = 11; //设置机械臂 public static final int INIT_JXB2 = 11; //设置机械臂
public int goMemoryTime =0; public int goMemoryTime =0;
private boolean isSetOver = false; //是否设置过开机时间了,默认没有设置过 private boolean isSetOver = false; //是否设置过开机时间了,默认没有设置过
private boolean lastHdmiEnabled = true;//上次HDMI信号状态,用于检测off→on恢复以解除MQTT主动停止标志
private WeakReference<ContainerService> weakReference; private WeakReference<ContainerService> weakReference;
@ -242,23 +243,85 @@ public class ContainerHandler extends Handler {
// 获取 ActivityManager 服务 // 获取 ActivityManager 服务
ActivityManager activityManager = (ActivityManager) MyApplication.getInstance().getSystemService(Context.ACTIVITY_SERVICE); ActivityManager activityManager = (ActivityManager) MyApplication.getInstance().getSystemService(Context.ACTIVITY_SERVICE);
final Debug.MemoryInfo[] memInfo = activityManager.getProcessMemoryInfo(new int[]{android.os.Process.myPid()});
final int totalPss = memInfo[0].getTotalPss();
//部分定制ROM(本机rk3399 eng)下getTotalPss恒返回0,改用/proc/self/status的VmRSS作为可靠的主进程内存来源
int rssMb = readProcRssMb();
LoggerUtil.e("getMemory()","运行内存(RSS):"+rssMb+"MB");
LoggerUtil.e("getMemory()","运行内存:"+totalPss/1024);
// Long cpuUsage = getCpuUsage();
// if(cpuUsage>50){
// LoggerUtil.e("ContainerHandler","cpu使用率:"+cpuUsage+"%");
// }
//检测WebView渲染进程的内存和显存(WebView渲染器运行在独立进程,OOM会导致渲染进程崩溃)
String deviceType = DeviceData.getDeviceInfo(DeviceData.HINT_DEVICE_TYPE);
if ("导视".equals(deviceType) || "水牌".equals(deviceType)) {
checkWebViewMemory(activityManager);
}
if(totalPss/1024>1200){
//内存超过了1G会出现卡顿,内存溢出问题。重启设备
if(rssMb>2000){
//内存超过了2.4G会出现卡顿,内存溢出问题。重启软件
LoggerUtil.e("getMemory()","内存溢出重启软件"); LoggerUtil.e("getMemory()","内存溢出重启软件");
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_RESTART_APP)); EventBus.getDefault().post(new MessageEvent(Constant.ACTION_RESTART_APP));
} }
} }
/**
* 读取本进程 /proc/self/status VmRSS物理内存占用单位MB失败返回0
* 用于替代部分定制ROM下恒返回0的 getProcessMemoryInfo().getTotalPss()
*/
private int readProcRssMb(){
try (BufferedReader reader = new BufferedReader(new FileReader("/proc/self/status"))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.startsWith("VmRSS:")) {
//形如: VmRSS: 123456 kB
String[] parts = line.trim().split("\\s+");
return Integer.parseInt(parts[1]) / 1024;
}
}
} catch (Throwable t) {
LoggerUtil.e("readProcRssMb", StringUtil.getThrowableStr(t));
}
return 0;
}
/**
* 检测WebView渲染进程的内存和显存
* WebView渲染器运行在独立的沙箱进程(sandboxed_process)其内存不计入主进程PSS
* 渲染进程显存/内存过高会导致渲染进程被系统杀死(onRenderProcessGone)表现为app莫名重启
*/
private void checkWebViewMemory(ActivityManager activityManager){
try {
List<ActivityManager.RunningAppProcessInfo> processes = activityManager.getRunningAppProcesses();
if(processes == null) return;
String pkg = MyApplication.getInstance().getPackageName();
for(ActivityManager.RunningAppProcessInfo info : processes){
if(info.processName == null) continue;
//只统计本应用的WebView渲染/沙箱进程
boolean isWebViewProcess = info.processName.contains("sandboxed_process")
|| info.processName.contains("webview");
if(!isWebViewProcess || !info.processName.startsWith(pkg)) continue;
Debug.MemoryInfo[] wvMem = activityManager.getProcessMemoryInfo(new int[]{info.pid});
if(wvMem == null || wvMem.length == 0) continue;
int wvPss = wvMem[0].getTotalPss();
int wvGraphics = parseMemoryStat(wvMem[0], "summary.graphics");
LoggerUtil.e("getMemory()","WebView渲染进程["+info.processName+"]内存:"
+ wvPss/1024 + "MB,显存:" + wvGraphics/1024 + "MB");
}
} catch (Throwable t){
LoggerUtil.e("checkWebViewMemory", StringUtil.getThrowableStr(t));
}
}
/**
* 读取MemoryInfo的指定统计项如summary.graphics单位KB解析失败返回0
*/
private int parseMemoryStat(Debug.MemoryInfo memInfo, String key){
try {
String value = memInfo.getMemoryStat(key);
if(value == null) return 0;
return Integer.parseInt(value);
} catch (Throwable t){
return 0;
}
}
private long mLastCpuTime; private long mLastCpuTime;
private long mLastAppCpuTime; private long mLastAppCpuTime;
@ -315,25 +378,33 @@ public class ContainerHandler extends Handler {
try { try {
if(Constant.androidBoardType.equals("xwst") && Constant.screenType.equals("HDMI")){ if(Constant.androidBoardType.equals("xwst") && Constant.screenType.equals("HDMI")){
//欣威视通假关机 //欣威视通假关机
LoggerUtil.e("mqttState","HDMI结果:"+RootCmdUtil.HDMIEnabled()+",mqttState结果:"+Constant.mqttState);
if(!RootCmdUtil.HDMIEnabled()){
boolean hdmiOn = RootCmdUtil.HDMIEnabled();
LoggerUtil.e("mqttState","HDMI结果:"+hdmiOn+",mqttState结果:"+Constant.mqttState);
boolean isService = DeviceUtil.isServiceRunning(MyApplication.getInstance(), "MQTTService");
if(!hdmiOn){
//HDMI无信号 //HDMI无信号
if(Constant.mqttState.equals("on")){
if(isService){
LoggerUtil.e("mqttState","HDMI无信号关闭MQTTService"); LoggerUtil.e("mqttState","HDMI无信号关闭MQTTService");
MyApplication.getInstance().stopService(new Intent(MyApplication.getInstance(),MQTTService.class));
MQTTService.stopAndDisableReconnect(MyApplication.getInstance());
} }
}else { }else {
//HDMI有信号 //HDMI有信号
boolean isService = DeviceUtil.isServiceRunning(MyApplication.getInstance(), "MQTTService");
if(!lastHdmiEnabled){
//HDMI信号off→on恢复,解除主动停止标志,允许重新拉起MQTT
MQTTService.manualStopped = false;
}
LoggerUtil.e("mqttState","MQTTService:"+isService); LoggerUtil.e("mqttState","MQTTService:"+isService);
if(Constant.mqttState.equals("off") || !isService){
LoggerUtil.e("mqttState","HDMI有信号开启MQTTService");
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
}else {
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_MQTT_STATE));//通知mqtt状态判断
//主动停止(stopService)后不自动拉起,直到HDMI/开屏恢复或重新启动服务
if(!MQTTService.manualStopped){
if(Constant.mqttState.equals("off") || !isService){
LoggerUtil.e("mqttState","HDMI有信号开启MQTTService");
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
}else {
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_MQTT_STATE));//通知mqtt状态判断
}
} }
} }
lastHdmiEnabled = hdmiOn;
}else if(Constant.androidBoardType.equals("xwst2") && Constant.screenType.equals("HDMI")){ }else if(Constant.androidBoardType.equals("xwst2") && Constant.screenType.equals("HDMI")){
RootCmdUtil.checkHDMIEnabled(new HdmiStatusCallback() { RootCmdUtil.checkHDMIEnabled(new HdmiStatusCallback() {
@ -344,27 +415,38 @@ public class ContainerHandler extends Handler {
//HDMI无信号 //HDMI无信号
if(Constant.mqttState.equals("on")){ if(Constant.mqttState.equals("on")){
LoggerUtil.e("mqttState","HDMI无信号关闭MQTTService"); LoggerUtil.e("mqttState","HDMI无信号关闭MQTTService");
MyApplication.getInstance().stopService(new Intent(MyApplication.getInstance(),MQTTService.class));
MQTTService.stopAndDisableReconnect(MyApplication.getInstance());
} }
}else { }else {
//HDMI有信号 //HDMI有信号
if(!lastHdmiEnabled){
//HDMI信号off→on恢复,解除主动停止标志,允许重新拉起MQTT
MQTTService.manualStopped = false;
}
boolean isService = DeviceUtil.isServiceRunning(MyApplication.getInstance(), "MQTTService"); boolean isService = DeviceUtil.isServiceRunning(MyApplication.getInstance(), "MQTTService");
LoggerUtil.e("mqttState","MQTTService:"+isService); LoggerUtil.e("mqttState","MQTTService:"+isService);
if(Constant.mqttState.equals("off") || !isService){
LoggerUtil.e("mqttState","HDMI有信号开启MQTTService");
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
}else {
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_MQTT_STATE));//通知mqtt状态判断
//主动停止(stopService)后不自动拉起,直到HDMI/开屏恢复或重新启动服务
if(!MQTTService.manualStopped){
if(Constant.mqttState.equals("off") || !isService){
LoggerUtil.e("mqttState","HDMI有信号开启MQTTService");
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
}else {
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_MQTT_STATE));//通知mqtt状态判断
}
} }
} }
lastHdmiEnabled = isEnabled;
} }
}); });
}else { }else {
if(Constant.mqttState.equals("off")){
LoggerUtil.e("mqttState","MQTT被关闭了,开启MQTTService");
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
}else {
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_MQTT_STATE));//通知mqtt状态判断
//非HDMI板:主动停止(stopService)后不自动拉起,待开屏/重新启动服务(onCreate清除标志)时恢复
if(!MQTTService.manualStopped){
if(Constant.mqttState.equals("off")){
LoggerUtil.e("mqttState","MQTT被关闭了,开启MQTTService");
MyApplication.getInstance().startService(new Intent(MyApplication.getInstance(),MQTTService.class));
}else {
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_MQTT_STATE));//通知mqtt状态判断
}
} }
} }

56
app/src/main/java/qianmu/container/mqtt/MQTTService.java

@ -124,6 +124,7 @@ public class MQTTService extends Service {
public static final String TAG = "MQTTService"; public static final String TAG = "MQTTService";
private static MqttAndroidClient client; private static MqttAndroidClient client;
private static volatile MQTTService instance;//当前服务实例,用于内部重连
private MqttConnectOptions conOpt; private MqttConnectOptions conOpt;
static boolean isDownloadFile = false; static boolean isDownloadFile = false;
private long programTime =0;//防止短时间接收多条信息 private long programTime =0;//防止短时间接收多条信息
@ -137,6 +138,9 @@ public class MQTTService extends Service {
private int connectionLostNumb = 0;//失去连接次数 private int connectionLostNumb = 0;//失去连接次数
private long connectionLostTime = 0;//失去连接时间 private long connectionLostTime = 0;//失去连接时间
private long connectTime = 0;//连接时间 private long connectTime = 0;//连接时间
//主动停止标志:经stopService停止(onDestroy)时置true,抑制ContainerHandler.mqttState自动拉起与重连;
//仅在 服务重新启动(onCreate)/HDMI信号恢复/开屏 等"真正需要MQTT"时清除
public static volatile boolean manualStopped = false;
public MQTTService() { public MQTTService() {
} }
@ -150,7 +154,9 @@ public class MQTTService extends Service {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
instance = this;
Constant.mqttState="on"; Constant.mqttState="on";
manualStopped = false;//服务已(重新)启动,解除主动停止标志
LoggerUtil.e("MQTTService","onCreate"); LoggerUtil.e("MQTTService","onCreate");
// host = StringUtil.strSplice("tcp://",MqttData.getMqttInfo().getServer(), ":", MqttData.getMqttInfo().getPort()); // host = StringUtil.strSplice("tcp://",MqttData.getMqttInfo().getServer(), ":", MqttData.getMqttInfo().getPort());
host = StringUtil.strSplice("ssl://",MqttData.getMqttInfo().getServer(), ":", MqttData.getMqttInfo().getPort()); host = StringUtil.strSplice("ssl://",MqttData.getMqttInfo().getServer(), ":", MqttData.getMqttInfo().getPort());
@ -244,12 +250,47 @@ public class MQTTService extends Service {
sendOffline(); sendOffline();
disconnectMqtt(); disconnectMqtt();
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
if(instance == this){
instance = null;
}
super.onDestroy(); super.onDestroy();
} }
/**
* 服务内部重连断开旧连接后重新建连不经过 stopService/startService 生命周期
* 避免 static client 被旧服务实例 onDestroy 析构置空的竞态
*/
public static void restartConnection(){
MQTTService s = instance;
if(s == null){
LoggerUtil.e(TAG, "restartConnection: 服务实例为空,忽略重连");
return;
}
LoggerUtil.e(TAG, "restartConnection: 服务内部重连");
s.connectionLostNumb = 0;
s.isConnected = false;
s.disconnectMqtt();
s.init();
}
/**
* 主动停止MQTT并抑制自动重连先置manualStopped再stopService
* 与系统杀进程区分(onDestroy不置标志仍可被mqttState自愈)
* 停止后仅在 HDMI信号恢复/开屏/重新启动服务(onCreate) 时恢复连接
*/
public static void stopAndDisableReconnect(Context context){
manualStopped = true;
LoggerUtil.e(TAG, "主动停止MQTT并抑制自动重连");
context.stopService(new Intent(context, MQTTService.class));
}
/** 连接MQTT服务器 */ /** 连接MQTT服务器 */
public void doClientConnection() { public void doClientConnection() {
try { try {
if(manualStopped){
LoggerUtil.e(TAG,"MQTT已被主动停止,跳过重连");
return;
}
if(connectionLostNumb>10){ if(connectionLostNumb>10){
LoggerUtil.e(TAG,"出现多次断线重连,通知重启mqtt服务"); LoggerUtil.e(TAG,"出现多次断线重连,通知重启mqtt服务");
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_RESTART_MQTT)); EventBus.getDefault().post(new MessageEvent(Constant.ACTION_RESTART_MQTT));
@ -282,19 +323,21 @@ public class MQTTService extends Service {
client.subscribe(myTopic,2); client.subscribe(myTopic,2);
sendOnline(); sendOnline();
connectTime = System.currentTimeMillis(); connectTime = System.currentTimeMillis();
if(Math.abs(connectTime-connectionLostTime)>3600000){
//首次连接(connectionLostTime=0)或断连超60分钟才视为"需要刷新",短时抖动重连不刷新
boolean longGap = Math.abs(connectTime-connectionLostTime)>3600000;
if(longGap){
//连接时间与连接失败时间相差60分钟,连接上时自动拉取最新节目 //连接时间与连接失败时间相差60分钟,连接上时自动拉取最新节目
LoggerUtil.e("MQTTService","连接成功与连接失败时间相差60分钟,连接成功主动拉取最新节目"); LoggerUtil.e("MQTTService","连接成功与连接失败时间相差60分钟,连接成功主动拉取最新节目");
programPublish(); programPublish();
} }
String deviceType = DeviceData.getDeviceInfo(DeviceData.HINT_DEVICE_TYPE);
if (longGap && ("信发".equals(deviceType) || "双面屏".equals(deviceType))){
//仅在首次连接/断连超60分钟时刷新WebView;避免MQTT短时抖动重连反复reload扰动播放导致卡屏
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_UPDATE_WEBVIEW));
}
} catch (MqttException e) { } catch (MqttException e) {
e.printStackTrace(); e.printStackTrace();
} }
if ("信发".equals(DeviceData.getDeviceInfo(DeviceData.HINT_DEVICE_TYPE))
|| "双面屏".equals(DeviceData.getDeviceInfo(DeviceData.HINT_DEVICE_TYPE))){
EventBus.getDefault().post(new MessageEvent(Constant.ACTION_UPDATE_WEBVIEW));
}
} }
@Override @Override
@ -343,7 +386,6 @@ public class MQTTService extends Service {
} }
connectionLostTime = time; connectionLostTime = time;
LoggerUtil.e(TAG, " 失去连接: 时间:"+connectionLostTime+",次数:"+connectionLostNumb); LoggerUtil.e(TAG, " 失去连接: 时间:"+connectionLostTime+",次数:"+connectionLostNumb);
} }
}; };

6
app/src/main/java/qianmu/container/util/LoggerUtil.java

@ -14,6 +14,7 @@ import java.text.SimpleDateFormat;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import okhttp3.MediaType; import okhttp3.MediaType;
@ -105,9 +106,12 @@ public class LoggerUtil {
} }
//写日志的共享线程池(全局复用,避免每条日志都新建线程导致线程泄漏OOM)
private static final ExecutorService LOG_EXECUTOR = Executors.newSingleThreadExecutor();
//将日志信息保存至SD卡(异步,正常日志使用) //将日志信息保存至SD卡(异步,正常日志使用)
public static synchronized void storeLog(String strModule, String strErrMsg) { public static synchronized void storeLog(String strModule, String strErrMsg) {
Executors.newSingleThreadExecutor().execute(() -> writeLogToFile(strModule, strErrMsg));
LOG_EXECUTOR.execute(() -> writeLogToFile(strModule, strErrMsg));
} }
//将日志信息同步保存至SD卡(崩溃时使用,确保进程退出前写入完成) //将日志信息同步保存至SD卡(崩溃时使用,确保进程退出前写入完成)

2
app/src/main/java/qianmu/container/util/SignWayUtil.java

@ -297,6 +297,8 @@ public class SignWayUtil {
try{ try{
if(Constant.androidBoardType.equals("xwst")){ if(Constant.androidBoardType.equals("xwst")){
setPowerOffTime("0","1970","1","1","0","0");
setPowerOnTime("0","1970","1","1","0","0");
setPowerOffTime("1","1970","1","1","0","0"); setPowerOffTime("1","1970","1","1","0","0");
setPowerOnTime("1","1970","1","1","0","0"); setPowerOnTime("1","1970","1","1","0","0");

9
app/src/main/java/qianmu/container/view/CustomerVideoView.java

@ -6,12 +6,15 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceView; import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import qianmu.container.R;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
@ -58,8 +61,10 @@ public class CustomerVideoView extends FrameLayout {
} }
private void init(Context context) { private void init(Context context) {
// 1. 创建 StyledPlayerView
playerView = new StyledPlayerView(context);
// 1. 创建 StyledPlayerView —— 从XML inflate以启用 surface_type=texture_view
// (用TextureView替代默认SurfaceView,避免多路视频同播时争抢硬件overlay平面导致黑屏)
playerView = (StyledPlayerView) LayoutInflater.from(context)
.inflate(R.layout.view_exo_texture_player, this, false);
addView(playerView); addView(playerView);
// 2. 初始化播放器 // 2. 初始化播放器
DefaultRenderersFactory factory = DefaultRenderersFactory factory =

16
app/src/main/res/layout/view_exo_texture_player.xml

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
CustomerVideoView 内部使用的播放视图。
关键:surface_type=texture_view —— 用 TextureView 而非默认 SurfaceView 渲染。
原因:多路视频(最多4路)同时播放时,SurfaceView 会争抢 RK3399 有限的硬件 overlay 平面,
抢不到的那一路无法合成、永不回调 onRenderedFirstFrame,导致黑屏;TextureView 走 GPU
在视图层级内合成,无 overlay 平面数量限制,且 z-order 跟随视图层级(bringToFront 生效)。
-->
<com.google.android.exoplayer2.ui.StyledPlayerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:surface_type="texture_view"
app:use_controller="false"
app:resize_mode="fill" />
Loading…
Cancel
Save