[FIX] 修正 OTA 連線超時與首頁執行緒併發競態導致 UI 異常問題
1. OtaManager.java: 將下載的連線逾時從 15 秒調高至 30 秒,讀取逾時調高至 120 秒,提升 OTA 下載穩定性。 2. FontendActivity.java: 修正多執行緒更新 UI 時,共用 logs 緩衝區所引發的競態條件 (Race Condition) 導致欄位變空的問題,改採區域變數更新;並安全封裝重構 reportOnScheduler 的生命週期管理方法;調整閒置逾時為 5 分鐘。 3. build.gradle: 依據實體機台 OTA 版號自動化規範,安全遞增 verPatch 修補版號至 56。 4. compiler-apk.md: 優化編譯工作流中的後台更新說明提示欄位。
This commit is contained in:
parent
8768e95178
commit
808b9c40d9
@ -157,6 +157,7 @@ if [ -n "$APK_SOURCE" ]; then
|
||||
echo "* 版本名稱 (versionName):$VERSION_NAME"
|
||||
echo "* 版本代碼 (versionCode):$VERSION_CODE"
|
||||
echo "* APK 檔案名稱:$APK_NAME"
|
||||
echo "* 更新說明:[請在此處附上口語化的一句話更新說明]"
|
||||
echo "=================================================="
|
||||
else
|
||||
echo "❌ 找不到編譯產出的 APK 檔案,複製失敗!"
|
||||
|
||||
@ -9,7 +9,7 @@ plugins {
|
||||
// 避免不同尺寸機台間版本號產生衝突。
|
||||
// =========================================================================
|
||||
def verMinor = 1
|
||||
def verPatch = 38
|
||||
def verPatch = 56
|
||||
|
||||
|
||||
android {
|
||||
|
||||
@ -17,6 +17,8 @@ import java.net.URL;
|
||||
public class OtaManager {
|
||||
private static final String TAG = "OtaManager";
|
||||
private static final Logs logs = new Logs(OtaManager.class);
|
||||
private static final int DOWNLOAD_CONNECT_TIMEOUT_MS = 30000;
|
||||
private static final int DOWNLOAD_READ_TIMEOUT_MS = 120000;
|
||||
|
||||
/**
|
||||
* 啟動背景執行緒執行 APP OTA 更新
|
||||
@ -67,8 +69,8 @@ public class OtaManager {
|
||||
private static File downloadApk(Context context, String apkUrl) throws Exception {
|
||||
URL url = new URL(apkUrl);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
connection.setConnectTimeout(DOWNLOAD_CONNECT_TIMEOUT_MS);
|
||||
connection.setReadTimeout(DOWNLOAD_READ_TIMEOUT_MS);
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||
|
||||
@ -118,11 +118,11 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
private Fragment activeFragment;
|
||||
private Fragment vmcFragment;
|
||||
private Fragment electricFragment;
|
||||
private static ReportShotTimeByScheduler reportOnScheduler;
|
||||
private ReportShotTimeByScheduler reportOnScheduler;
|
||||
private ConnectivityManager connectivityManager;
|
||||
private ConnectivityManager.NetworkCallback networkCallback;
|
||||
|
||||
private static final long IDLE_TIMEOUT = 5 * 60 * 1000; // 3 分鐘
|
||||
private static final long IDLE_TIMEOUT = 5 * 60 * 1000; // 5 分鐘
|
||||
private final long TIME_LIMIT = 200000;
|
||||
private int clickCount = 0;
|
||||
private long firstClickTime = 0;
|
||||
@ -263,7 +263,8 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
// 【第一層】定時重啟:到了整點就啟動等待模式(RestartAPP2 自己會等閒置 >= 90 秒才真重啟)
|
||||
if (timeByM.equals("04:00") || timeByM.equals("08:00") || timeByM.equals("12:00")
|
||||
|| timeByM.equals("16:00") || timeByM.equals("20:00") || timeByM.equals("23:59")) {
|
||||
android.content.SharedPreferences prefs = getSharedPreferences("AppRestartPrefs", Context.MODE_PRIVATE);
|
||||
android.content.SharedPreferences prefs = getSharedPreferences("AppRestartPrefs",
|
||||
Context.MODE_PRIVATE);
|
||||
String today = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date());
|
||||
String currentSlot = today + " " + timeByM;
|
||||
String lastRestartSlot = prefs.getString("last_restart_slot", "");
|
||||
@ -280,11 +281,11 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
|
||||
// 【第二層】閒置深度刷新:任何時間閒置超過 30 分鐘就重啟 (暫時關閉,改依賴定時重啟)
|
||||
/*
|
||||
if (MyApp.getInstance().getIdleSeconds() >= 30 * 60) {
|
||||
getLogs().info("閒置超過 30 分鐘,執行深度排毒重啟...");
|
||||
new RestartAPP2(this).start();
|
||||
}
|
||||
*/
|
||||
* if (MyApp.getInstance().getIdleSeconds() >= 30 * 60) {
|
||||
* getLogs().info("閒置超過 30 分鐘,執行深度排毒重啟...");
|
||||
* new RestartAPP2(this).start();
|
||||
* }
|
||||
*/
|
||||
|
||||
// 這裡判斷每5分鐘去跑連線下位機的狀態
|
||||
// String minutePart = timeByM.split(":")[1]; // 取得 "mm" 部分
|
||||
@ -305,24 +306,24 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
// new CMUHAutoRun(this).start();
|
||||
}
|
||||
|
||||
getLogs().update("ThreadCount", String.valueOf(Tools.threadCount()));
|
||||
String tc = getLogs().getMessage();
|
||||
getLogs().update("ViceoCount", String.valueOf(VideoEoxPlayer.getExoPlayerTotal()));
|
||||
String vc = getLogs().getMessage();
|
||||
binding.textThreadCount.setText("TC:" + tc + " VC:" + vc);
|
||||
getLogs().clearMessage();
|
||||
// 直接使用區域變數更新 UI,避免多執行緒 Race Condition 導致 getLogs().getMessage() 被改寫或清空而使 UI 變空
|
||||
String threadCountStr = String.valueOf(Tools.threadCount());
|
||||
String videoCountStr = String.valueOf(VideoEoxPlayer.getExoPlayerTotal());
|
||||
binding.textThreadCount.setText("TC:" + threadCountStr + " VC:" + videoCountStr);
|
||||
|
||||
getLogs().update("ThreadCount", threadCountStr);
|
||||
getLogs().update("ViceoCount", videoCountStr);
|
||||
|
||||
Tools.threadInfos();
|
||||
|
||||
String networkDevice = getNetworkDevice();
|
||||
binding.textNetwork.setText(getString(R.string.network) + ":" + networkDevice);
|
||||
getLogs().update("Network", networkDevice);
|
||||
binding.textNetwork.setText(getString(R.string.network) + ":" + getLogs().getMessage());
|
||||
getLogs().clearMessage();
|
||||
|
||||
DevXinYuanController.setVersion(ver);
|
||||
String tempStr = DevXinYuanController.getTemperature() + "℃";
|
||||
binding.textTemperature.setText(getString(R.string.temp) + ":" + tempStr);
|
||||
getLogs().update("Temperature", String.valueOf(DevXinYuanController.getTemperature()), "℃");
|
||||
binding.textTemperature.setText(getString(R.string.temp) + ":" + getLogs().getMessage());
|
||||
getLogs().clearMessage();
|
||||
|
||||
try {
|
||||
// httpAPI.detectHost();
|
||||
@ -467,7 +468,6 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
return saleFlowHandler;
|
||||
}
|
||||
|
||||
|
||||
public void startOnSwitchFragment() {
|
||||
try {
|
||||
stopOnSwitchFragment(); // ✅ 先安全釋放舊播放器
|
||||
@ -487,7 +487,8 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
videoEoxPlayer = new VideoEoxPlayer(getCtx()); // ✅ 用新版建構子
|
||||
|
||||
getHandlerMain().getHandler().postDelayed(() -> {
|
||||
if (isFinishing() || isDestroyed()) return;
|
||||
if (isFinishing() || isDestroyed())
|
||||
return;
|
||||
if (videoEoxPlayer != null) {
|
||||
// videoEoxPlayer.playMedia(binding.videoView, binding.imageAd); // ✅ 傳入 view
|
||||
// 開始播放
|
||||
@ -700,12 +701,7 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
binding.floatButtonShoppingCart.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (reportOnScheduler != null) {
|
||||
reportOnScheduler.shutdown();
|
||||
}
|
||||
|
||||
reportOnScheduler = new ReportShotTimeByScheduler(getHandlerMain());
|
||||
reportOnScheduler.start(1L, 10L, TimeUnit.SECONDS);
|
||||
startHeaderReportScheduler();
|
||||
|
||||
// binding.btnswitchlanTW.setOnClickListener(v -> switchLanguage("tw"));
|
||||
//
|
||||
@ -753,6 +749,20 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
startOnSwitchFragment();
|
||||
}
|
||||
|
||||
private void startHeaderReportScheduler() {
|
||||
stopHeaderReportScheduler();
|
||||
reportOnScheduler = new ReportShotTimeByScheduler(getHandlerMain());
|
||||
getHandlerMain().start(getClass().getSimpleName(), Option.REPORT_AND_SHOT_TIME.getOption(), "start");
|
||||
reportOnScheduler.start(1L, 10L, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void stopHeaderReportScheduler() {
|
||||
if (reportOnScheduler != null) {
|
||||
reportOnScheduler.shutdown();
|
||||
reportOnScheduler = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void hiddenMultiLanguageButton() {
|
||||
binding.btnswitchlanTW.setVisibility(View.GONE);
|
||||
binding.btnswitchlanEn.setVisibility(View.GONE);
|
||||
@ -1230,9 +1240,6 @@ public class FontendActivity extends AppCompatActivityAbstract implements SaleFl
|
||||
}
|
||||
}
|
||||
|
||||
if (reportOnScheduler != null) {
|
||||
reportOnScheduler.shutdown();
|
||||
reportOnScheduler = null;
|
||||
}
|
||||
stopHeaderReportScheduler();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user