[FIX] 修復廣告同步導致的 Activity 銷毀崩潰與局部廣告清空失效問題
1. 修正 HomeActivity 與 FontendActivity 在廣告更新廣播回呼中因 Activity 已銷毀而觸發 Glide IllegalArgumentException 的問題。 2. 在 VideoImageMixedPlayer 中增加 Activity 生命週期安全檢查,確保不會在非法 Context 下執行 UI 操作。 3. 優化 VideoImageMixedPlayer 的清空邏輯:當特定 Flag 下無廣告可播放時,主動隱藏影片與圖片視圖並清除殘留影像。 4. 強化 stop() 方法,確保切換頁面時能完整釋放播放器資源並重設視圖狀態。 5. 增加詳細的廣告過濾與播放日誌,並支援廣告動態播放時間 (Duration) 控制。
This commit is contained in:
parent
3044314856
commit
8ae4ae9c14
@ -12,13 +12,14 @@ description: 自動編譯 Android APK、輸出至專屬目錄並安裝至實體
|
||||
```bash
|
||||
// turbo
|
||||
# 終止殘留的 Gradle/Java 程序,避免多個 Daemon 互搶資源
|
||||
pkill -9 -f "gradle" 2>/dev/null; pkill -9 -f "GradleDaemon" 2>/dev/null; sleep 1
|
||||
pkill -9 -f "gradle" 2>/dev/null; pkill -9 -f "GradleDaemon" 2>/dev/null; sleep 3
|
||||
|
||||
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
|
||||
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
|
||||
|
||||
# 使用 clean 確保完整重新編譯,避免舊快取遮蔽 import 缺失等錯誤
|
||||
./gradlew clean :app:assembleS_12_XY_U_Standard_Debug
|
||||
# --no-daemon:強制使用單次 JVM 進程,避免 Daemon 初始化期間被殘留訊號中斷
|
||||
./gradlew clean :app:assembleS_12_XY_U_Standard_Debug --no-daemon
|
||||
```
|
||||
|
||||
## 步驟二:導出 APK 到 outputs 資料夾
|
||||
|
||||
6
.idea/AndroidProjectSystem.xml
Normal file
6
.idea/AndroidProjectSystem.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
@ -1235,6 +1235,18 @@
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2400" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="36" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="m1q" />
|
||||
<option name="id" value="m1q" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy S26" />
|
||||
<option name="screenDensity" value="480" />
|
||||
<option name="screenX" value="1080" />
|
||||
<option name="screenY" value="2340" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="36" />
|
||||
<option name="brand" value="samsung" />
|
||||
@ -1537,6 +1549,18 @@
|
||||
<option name="screenX" value="1856" />
|
||||
<option name="screenY" value="2160" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="36" />
|
||||
<option name="brand" value="samsung" />
|
||||
<option name="codename" value="q7mq" />
|
||||
<option name="id" value="q7mq" />
|
||||
<option name="labId" value="google" />
|
||||
<option name="manufacturer" value="Samsung" />
|
||||
<option name="name" value="Galaxy Z TriFold" />
|
||||
<option name="screenDensity" value="320" />
|
||||
<option name="screenX" value="2160" />
|
||||
<option name="screenY" value="1584" />
|
||||
</PersistentDeviceSelectionData>
|
||||
<PersistentDeviceSelectionData>
|
||||
<option name="api" value="36" />
|
||||
<option name="brand" value="samsung" />
|
||||
|
||||
12
.idea/gradle.xml
Normal file
12
.idea/gradle.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
|
||||
17
.idea/runConfigurations.xml
Normal file
17
.idea/runConfigurations.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@ -2,11 +2,14 @@ package com.unibuy.smartdevice;
|
||||
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
@ -76,6 +79,25 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
private ActivityHomeBinding binding;
|
||||
private static VideoEoxPlayer videoEoxPlayer;
|
||||
|
||||
// ✅ 廣告更新監聽器:MQTT 廣告同步完成後自動刷新待機廣告播放器
|
||||
private final BroadcastReceiver adUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
getLogs().info("🔔 [HomeActivity] 收到 ACTION_AD_UPDATED 廣播,準備刷新待機頁廣告播放器...");
|
||||
// 🔹 清除 Glide 記憶體快取,避免拿到舊的圖片 Bitmap
|
||||
Glide.get(context).clearMemory();
|
||||
// 使用 postDelayed 確保 DB 寫入完成後再刷新
|
||||
new android.os.Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
if (!isFinishing() && !isDestroyed()) {
|
||||
startOnSwitchFragment();
|
||||
getLogs().info("✅ [HomeActivity] 待機頁廣告播放器已重建");
|
||||
} else {
|
||||
getLogs().info("⚠️ [HomeActivity] Activity 已銷毀,取消刷新廣告播放器");
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
private long lastReportTime = 0;
|
||||
|
||||
private static ReportShotTimeByScheduler reportOnScheduler;
|
||||
@ -227,6 +249,14 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
reportOnScheduler.start(1L, 10L, TimeUnit.SECONDS);
|
||||
|
||||
startOnSwitchFragment();
|
||||
|
||||
// ✅ 註冊廣告更新監聽
|
||||
IntentFilter adFilter = new IntentFilter("com.unibuy.smartdevice.ACTION_AD_UPDATED");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(adUpdateReceiver, adFilter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(adUpdateReceiver, adFilter);
|
||||
}
|
||||
}
|
||||
|
||||
public void startOnSwitchFragment() {
|
||||
@ -236,6 +266,7 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
videoEoxPlayer = new VideoEoxPlayer(getCtx());
|
||||
|
||||
getHandlerMain().getHandler().postDelayed(() -> {
|
||||
if (isFinishing() || isDestroyed()) return;
|
||||
try {
|
||||
// videoEoxPlayer.playMedia(binding.videoView, binding.imageAd); // ✅ 同時支援影片與圖片輪播
|
||||
videoEoxPlayer.playMediaByFlag("3", binding.videoView, binding.imageAd); // ✅ 同時支援影片與圖片輪播
|
||||
@ -579,5 +610,11 @@ public class HomeActivity extends AppCompatActivityAbstract {
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopOnSwitchFragment();
|
||||
// ✅ 取消註冊廣告更新監聽
|
||||
try {
|
||||
unregisterReceiver(adUpdateReceiver);
|
||||
} catch (Exception e) {
|
||||
getLogs().info("unregisterReceiver adUpdateReceiver: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,17 +417,16 @@ public class MyApp extends Application {
|
||||
serviceIntent.putExtra("machineId", machineId);
|
||||
serviceIntent.putExtra("apiToken", apiKey);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(serviceIntent);
|
||||
} catch (Exception e) {
|
||||
// Android 12+ 在 AlarmManager 喚醒時不允許直接啟動 ForegroundService
|
||||
// 改用普通 startService 讓 Service 先啟動,等 Activity 進前台後再提升
|
||||
logs.info("ForegroundService start blocked (background state), falling back to startService");
|
||||
} else {
|
||||
startService(serviceIntent);
|
||||
}
|
||||
} else {
|
||||
startService(serviceIntent);
|
||||
} catch (Exception e) {
|
||||
// Android 12+ 在背景狀態下(如安裝後自動重啟)不允許啟動前台/背景服務
|
||||
// 這裡只記錄日誌,不再嘗試 fallback,等使用者開啟 App 進前台後再由 Activity 觸發
|
||||
logs.info("MQTT Service start blocked (app in background): " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,10 +20,14 @@ import com.unibuy.smartdevice.structure.ReportSlotListStructure;
|
||||
import com.unibuy.smartdevice.structure.SlotStructure;
|
||||
import com.unibuy.smartdevice.devices.SlotField;
|
||||
import com.unibuy.smartdevice.structure.SettingStructure;
|
||||
import com.unibuy.smartdevice.ui.SettingActivity;
|
||||
import com.unibuy.smartdevice.ui.tools.VideoEoxPlayer;
|
||||
import com.unibuy.smartdevice.ui.tools.VideoImageMixedPlayer;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* 專門對接 Star-Cloud 後台的 API 類別
|
||||
@ -671,11 +675,140 @@ public class StarCloudAPI {
|
||||
}
|
||||
Log.i(TAG, "Updated product references in all slots.");
|
||||
|
||||
// 🔹 發送廣播通知所有監聽者(如銷售平台)商品與貨道資料已更新
|
||||
// 🔹 發送廣播通知所有監聯者(如銷售平台)商品與貨道資料已更新
|
||||
android.content.Intent intent = new android.content.Intent("com.unibuy.smartdevice.ACTION_SLOT_UPDATED");
|
||||
intent.setPackage(handlerMain.getContext().getPackageName());
|
||||
handlerMain.getContext().sendBroadcast(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* B005: 下載廣告清單並同步媒體檔案
|
||||
*/
|
||||
public void downloadAdvertisement() {
|
||||
downloadAdvertisement(null);
|
||||
}
|
||||
|
||||
public void downloadAdvertisement(final Runnable onComplete) {
|
||||
final String url = BASE_URL + "machine/ad/B005";
|
||||
Log.i(TAG, "Starting B005 request to: " + url);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int responseCode = -1;
|
||||
String receive = "";
|
||||
try {
|
||||
java.net.URL reqUrl = new java.net.URL(url);
|
||||
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) reqUrl.openConnection();
|
||||
conn.setRequestMethod("GET");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
|
||||
String machineToken = MyApp.getInstance().getMachine().getApiKey();
|
||||
if (machineToken != null && !machineToken.isEmpty()) {
|
||||
conn.setRequestProperty("Authorization", "Bearer " + machineToken);
|
||||
}
|
||||
|
||||
conn.setConnectTimeout(15000);
|
||||
conn.setReadTimeout(30000); // 廣告清單可能較大
|
||||
|
||||
responseCode = conn.getResponseCode();
|
||||
java.io.InputStream is = (responseCode >= 200 && responseCode < 300)
|
||||
? conn.getInputStream() : conn.getErrorStream();
|
||||
if (is != null) {
|
||||
java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(is, "UTF-8"));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) sb.append(line);
|
||||
receive = sb.toString();
|
||||
}
|
||||
conn.disconnect();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "B005 Network Error", e);
|
||||
handlerMain.start("StarCloudAPI", 500, "廣告同步連線失敗");
|
||||
if (onComplete != null) onComplete.run();
|
||||
return;
|
||||
}
|
||||
|
||||
final int finalCode = responseCode;
|
||||
final String finalReceive = receive;
|
||||
if (finalCode >= 200 && finalCode < 300) {
|
||||
try {
|
||||
JSONObject res = new JSONObject(finalReceive);
|
||||
if (res.optBoolean("success", false)) {
|
||||
JSONArray ads = res.getJSONArray("data");
|
||||
saveAds(ads);
|
||||
handlerMain.start("StarCloudAPI", 200, "廣告同步完成");
|
||||
} else {
|
||||
handlerMain.start("StarCloudAPI", 401, "廣告同步失敗: " + res.optString("message"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "B005 Parse Error", e);
|
||||
handlerMain.start("StarCloudAPI", 500, "廣告解析錯誤");
|
||||
}
|
||||
} else {
|
||||
handlerMain.start("StarCloudAPI", finalCode, "廣告同步網路錯誤: " + finalCode);
|
||||
}
|
||||
if (onComplete != null) onComplete.run();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void saveAds(JSONArray ads) throws JSONException {
|
||||
SettingsDao settingsDao = new SettingsDao(handlerMain.getContext());
|
||||
// 清除舊廣告設定
|
||||
settingsDao.deleteWhere(VideoEoxPlayer.class.getSimpleName());
|
||||
|
||||
// 🔹 清除 MediaCacheHelper 本地快取,避免 URL 相同但內容變更時播放器讀到過期快取
|
||||
VideoImageMixedPlayer.clearAdsCache(handlerMain.getContext());
|
||||
Log.i(TAG, "Cleared local ads media cache before sync.");
|
||||
|
||||
MyApp.getInstance().getFontendAdUrlString().clear();
|
||||
|
||||
for (int i = 0; i < ads.length(); i++) {
|
||||
JSONObject ad = ads.getJSONObject(i);
|
||||
String url = ad.optString("t070v04", "");
|
||||
String flag = String.valueOf(ad.optInt("t070v03", 0));
|
||||
String duration = String.valueOf(ad.optInt("t070v02", 10));
|
||||
|
||||
if (url.isEmpty()) continue;
|
||||
|
||||
MyApp.getInstance().getFontendAdUrlString().add(url);
|
||||
|
||||
// 下載檔案
|
||||
try {
|
||||
Log.i(TAG, "Downloading Ad (" + (i+1) + "/" + ads.length() + "): " + url);
|
||||
HttpConnect httpConnect = new HttpConnect();
|
||||
httpConnect.download(url, true);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Download Ad Failed: " + url, e);
|
||||
}
|
||||
|
||||
// 儲存至資料庫 [URL, Flag, Duration]
|
||||
settingsDao.insertOne(new SettingStructure(
|
||||
VideoEoxPlayer.class.getSimpleName(),
|
||||
new String[]{url, flag, duration}
|
||||
));
|
||||
|
||||
// 更新 UI 進度
|
||||
final int percent = (i + 1) * 100 / ads.length();
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SettingActivity.updateProgressUI(percent, "廣告下載中...");
|
||||
}
|
||||
});
|
||||
}
|
||||
settingsDao.close();
|
||||
Log.i(TAG, "Synced " + ads.length() + " ads from star-cloud.");
|
||||
|
||||
// ✅ 發送廣播通知前台 Activity(HomeActivity / FontendActivity)刷新廣告播放器
|
||||
// 🔹 必須設定 setPackage 才能被 RECEIVER_NOT_EXPORTED 的接收器收到(Android 13+)
|
||||
android.content.Intent adIntent = new android.content.Intent("com.unibuy.smartdevice.ACTION_AD_UPDATED");
|
||||
adIntent.setPackage(handlerMain.getContext().getPackageName());
|
||||
handlerMain.getContext().sendBroadcast(adIntent);
|
||||
Log.i(TAG, "ACTION_AD_UPDATED broadcast sent.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得設備真實 IP 地址
|
||||
*/
|
||||
|
||||
@ -240,6 +240,40 @@ public class MqttService extends Service {
|
||||
logs.info("Failed to parse or start change command: " + e.getMessage());
|
||||
mqttManager.publishCommandAck(cmdId, "failed", "Error: " + e.getMessage());
|
||||
}
|
||||
} else if ("update_ads".equals(cmd)) {
|
||||
logs.info("Executing remote advertisement sync command...");
|
||||
|
||||
// 📢 在主執行緒顯示 Toast 提示使用者
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() ->
|
||||
android.widget.Toast.makeText(getApplicationContext(),
|
||||
"📢 廣告更新中...", android.widget.Toast.LENGTH_SHORT).show());
|
||||
|
||||
com.unibuy.smartdevice.tools.HandlerMain adHandler = new com.unibuy.smartdevice.tools.HandlerMain(
|
||||
getApplicationContext(), logs) {
|
||||
@Override
|
||||
protected void execute(Context context, int code, String message) {
|
||||
logs.info("Ad sync event: [" + code + "] " + message);
|
||||
if (code == 200 && "廣告同步完成".equals(message)) {
|
||||
mqttManager.publishCommandAck(cmdId, "success",
|
||||
"Advertisement sync completed successfully");
|
||||
|
||||
// ✅ Toast 提示完成
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() ->
|
||||
android.widget.Toast.makeText(context,
|
||||
"✅ 廣告更新完成", android.widget.Toast.LENGTH_SHORT).show());
|
||||
// 注意:廣播 ACTION_AD_UPDATED 已在 StarCloudAPI.saveAds() 中統一發送
|
||||
} else if (code >= 400) {
|
||||
mqttManager.publishCommandAck(cmdId, "failed",
|
||||
"Advertisement sync failed: " + message);
|
||||
|
||||
new android.os.Handler(android.os.Looper.getMainLooper()).post(() ->
|
||||
android.widget.Toast.makeText(context,
|
||||
"❌ 廣告更新失敗: " + message, android.widget.Toast.LENGTH_LONG).show());
|
||||
}
|
||||
}
|
||||
};
|
||||
// 使用新架構 StarCloudAPI 執行同步
|
||||
new com.unibuy.smartdevice.external.StarCloudAPI(adHandler, adHandler).downloadAdvertisement();
|
||||
} else if ("update_inventory".equals(cmd)) {
|
||||
logs.info("Executing update_inventory command...");
|
||||
try {
|
||||
|
||||
@ -141,6 +141,22 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
}
|
||||
};
|
||||
|
||||
// ✅ 廣告更新監聽器:MQTT 廣告同步完成後自動刷新販賣頁廣告播放器
|
||||
private final BroadcastReceiver adUpdateReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
getLogs().info("🔔 [FontendActivity] 收到 ACTION_AD_UPDATED 廣播,準備刷新販賣頁廣告播放器...");
|
||||
// 🔹 清除 Glide 記憶體快取,避免拿到舊的圖片 Bitmap
|
||||
Glide.get(context).clearMemory();
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
if (!isFinishing() && !isDestroyed()) {
|
||||
startOnSwitchFragment();
|
||||
getLogs().info("✅ [FontendActivity] 販賣頁廣告播放器已重建");
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
private static VideoEoxPlayer videoEoxPlayer;
|
||||
|
||||
@Override
|
||||
@ -423,7 +439,9 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
|
||||
// 確保前一個 ExoPlayer 完整釋放後再初始化新的
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
initVideoPlayer();
|
||||
if (!isFinishing() && !isDestroyed()) {
|
||||
initVideoPlayer();
|
||||
}
|
||||
}, 100);
|
||||
} catch (Exception e) {
|
||||
getLogs().warning(e);
|
||||
@ -434,6 +452,7 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
videoEoxPlayer = new VideoEoxPlayer(getCtx()); // ✅ 用新版建構子
|
||||
|
||||
getHandlerMain().getHandler().postDelayed(() -> {
|
||||
if (isFinishing() || isDestroyed()) return;
|
||||
if (videoEoxPlayer != null) {
|
||||
// videoEoxPlayer.playMedia(binding.videoView, binding.imageAd); // ✅ 傳入 view
|
||||
// 開始播放
|
||||
@ -1100,6 +1119,14 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
registerReceiver(slotUpdateReceiver, filter);
|
||||
}
|
||||
|
||||
// ✅ 註冊廣告更新監聽
|
||||
IntentFilter adFilter = new IntentFilter("com.unibuy.smartdevice.ACTION_AD_UPDATED");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
registerReceiver(adUpdateReceiver, adFilter, Context.RECEIVER_NOT_EXPORTED);
|
||||
} else {
|
||||
registerReceiver(adUpdateReceiver, adFilter);
|
||||
}
|
||||
|
||||
// 🔹 從設定頁面或其他頁面回到銷售平台時,強制刷新商品列表,確保新圖片能顯示
|
||||
// 先清除 Glide 記憶體快取,避免拿到舊的 Bitmap
|
||||
Glide.get(this).clearMemory();
|
||||
@ -1120,6 +1147,12 @@ public class FontendActivity extends AppCompatActivityAbstract {
|
||||
} catch (Exception e) {
|
||||
getLogs().info("unregisterReceiver slotUpdateReceiver: " + e.getMessage());
|
||||
}
|
||||
// ✅ 取消註冊廣告更新監聽
|
||||
try {
|
||||
unregisterReceiver(adUpdateReceiver);
|
||||
} catch (Exception e) {
|
||||
getLogs().info("unregisterReceiver adUpdateReceiver: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -58,6 +58,7 @@ public class RestartHostActivity extends AppCompatActivityAbstract {
|
||||
private RecyclerSettingListAdpter recyclerSettingListAdpter;
|
||||
|
||||
private HttpAPI httpAPI;
|
||||
private com.unibuy.smartdevice.external.StarCloudAPI starCloudAPI;
|
||||
|
||||
@Override
|
||||
protected Context setCtx() {
|
||||
@ -88,11 +89,7 @@ public class RestartHostActivity extends AppCompatActivityAbstract {
|
||||
new ImageReload(this).start();
|
||||
break;
|
||||
case AD_RELOAD:
|
||||
try {
|
||||
httpAPI.downloadAdvertisement();
|
||||
} catch (LogsParseException | LogsSettingEmptyException e) {
|
||||
getLogs().warning(e);
|
||||
}
|
||||
starCloudAPI.downloadAdvertisement();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -107,6 +104,7 @@ public class RestartHostActivity extends AppCompatActivityAbstract {
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
httpAPI = new HttpAPI(getHandlerMain());
|
||||
starCloudAPI = new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain());
|
||||
|
||||
recyclerSettingListAdpter = new RecyclerSettingListAdpter(statusList);
|
||||
binding.recyclerSettingList.setAdapter(recyclerSettingListAdpter);
|
||||
|
||||
@ -114,6 +114,9 @@ public class SettingActivity extends AppCompatActivityAbstract {
|
||||
if ("綁定成功".equals(message)) {
|
||||
binding.progressLayout.setVisibility(View.VISIBLE); // 顯示進度條,因為要同步商品
|
||||
starCloudAPI.downloadProductInfo();
|
||||
} else if ("商品下載成功".equals(message)) {
|
||||
binding.progressLayout.setVisibility(View.VISIBLE); // 顯示進度條,同步廣告
|
||||
starCloudAPI.downloadAdvertisement();
|
||||
}
|
||||
|
||||
checkSetting();
|
||||
@ -207,6 +210,7 @@ public class SettingActivity extends AppCompatActivityAbstract {
|
||||
}
|
||||
|
||||
public static void updateProgressUI(int percent, String message) {
|
||||
if (instance == null) return;
|
||||
SettingActivity activity = instance.get();
|
||||
if (activity == null)
|
||||
return;
|
||||
|
||||
@ -75,18 +75,26 @@ public class VideoEoxPlayer {
|
||||
|
||||
SettingsDao settingsDao = new SettingsDao(context);
|
||||
List<SettingStructure> dataList = settingsDao.getWhere(VideoEoxPlayer.class.getSimpleName());
|
||||
List<String> targetUrls = new ArrayList<>();
|
||||
List<VideoImageMixedPlayer.AdItem> targetAds = new ArrayList<>();
|
||||
|
||||
for (SettingStructure data : dataList) {
|
||||
String url = data.getData(0);
|
||||
String flag = data.getData(1);
|
||||
int duration = 10;
|
||||
try {
|
||||
if (data.getDataCount() > 2) {
|
||||
duration = Integer.parseInt(data.getData(2));
|
||||
}
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
if (flag != null && flag.equals(targetFlag)) {
|
||||
targetUrls.add(url);
|
||||
targetAds.add(new VideoImageMixedPlayer.AdItem(url, duration));
|
||||
}
|
||||
}
|
||||
settingsDao.close();
|
||||
logs.info("Ads [Flag: " + targetFlag + "] 過濾後數量: " + targetAds.size());
|
||||
|
||||
mixedPlayer = new VideoImageMixedPlayer(context, targetUrls);
|
||||
mixedPlayer = new VideoImageMixedPlayer(context, targetAds);
|
||||
mixedPlayer.start(videoView, imageView);
|
||||
|
||||
}
|
||||
|
||||
@ -29,9 +29,19 @@ import java.util.concurrent.Executors;
|
||||
|
||||
public class VideoImageMixedPlayer {
|
||||
|
||||
public static class AdItem {
|
||||
public String url;
|
||||
public int duration; // 秒
|
||||
|
||||
public AdItem(String url, int duration) {
|
||||
this.url = url;
|
||||
this.duration = duration;
|
||||
}
|
||||
}
|
||||
|
||||
private final Logs logs;
|
||||
private final Context context;
|
||||
private final List<String> mediaUrls;
|
||||
private final List<AdItem> adItems;
|
||||
|
||||
private ExoPlayer exoPlayer;
|
||||
private PlayerView playerView;
|
||||
@ -39,9 +49,9 @@ public class VideoImageMixedPlayer {
|
||||
private Handler handler;
|
||||
private int currentIndex = 0;
|
||||
|
||||
public VideoImageMixedPlayer(Context context, List<String> mediaUrls) {
|
||||
public VideoImageMixedPlayer(Context context, List<AdItem> adItems) {
|
||||
this.context = context;
|
||||
this.mediaUrls = mediaUrls;
|
||||
this.adItems = adItems;
|
||||
this.logs = new Logs(getClass());
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
@ -54,16 +64,28 @@ public class VideoImageMixedPlayer {
|
||||
}
|
||||
|
||||
private void playNext() {
|
||||
if (mediaUrls == null || mediaUrls.isEmpty()) {
|
||||
logs.info("Ads沒有任何媒體可以播放!");
|
||||
if (adItems == null || adItems.isEmpty()) {
|
||||
logs.info("Ads沒有任何媒體可以播放!清空視圖 (flag-based)...");
|
||||
if (playerView != null) {
|
||||
playerView.setVisibility(android.view.View.GONE);
|
||||
playerView.setPlayer(null);
|
||||
}
|
||||
if (imageView != null) {
|
||||
imageView.setVisibility(android.view.View.GONE);
|
||||
imageView.setImageDrawable(null); // 清除舊圖片
|
||||
}
|
||||
releaseExoPlayer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentIndex >= mediaUrls.size()) {
|
||||
logs.info("Ads 目前播放索引: " + currentIndex + " / 總數: " + adItems.size());
|
||||
|
||||
if (currentIndex >= adItems.size()) {
|
||||
currentIndex = 0;
|
||||
}
|
||||
|
||||
String url = mediaUrls.get(currentIndex);
|
||||
AdItem item = adItems.get(currentIndex);
|
||||
String url = item.url;
|
||||
|
||||
// 先檢查快取
|
||||
String cachedPath = MediaCacheHelper.getCachedPath(context, url);
|
||||
@ -132,10 +154,23 @@ public class VideoImageMixedPlayer {
|
||||
exoPlayer.prepare();
|
||||
exoPlayer.setPlayWhenReady(true);
|
||||
|
||||
// ✅ 使用設定的秒數控制影片播放時間,到時間就強制切下一個
|
||||
int duration = adItems.get(currentIndex).duration;
|
||||
if (duration <= 0) duration = 10;
|
||||
final Runnable forceNextRunnable = () -> {
|
||||
logs.info("影片播放到設定秒數 (" + adItems.get(currentIndex).duration + "s),強制切下一個廣告");
|
||||
releaseExoPlayer();
|
||||
currentIndex = (currentIndex + 1) % adItems.size();
|
||||
playNext();
|
||||
};
|
||||
handler.postDelayed(forceNextRunnable, duration * 1000L);
|
||||
|
||||
exoPlayer.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onPlaybackStateChanged(int state) {
|
||||
if (state == Player.STATE_ENDED) {
|
||||
// 影片自然播放結束,取消強制計時器,直接切下一個
|
||||
handler.removeCallbacks(forceNextRunnable);
|
||||
nextSchedule(1000);
|
||||
}
|
||||
}
|
||||
@ -143,6 +178,7 @@ public class VideoImageMixedPlayer {
|
||||
@Override
|
||||
public void onPlayerError(PlaybackException error) {
|
||||
logs.info("影片播放錯誤: " + error.getMessage());
|
||||
handler.removeCallbacks(forceNextRunnable);
|
||||
nextSchedule(1000);
|
||||
}
|
||||
});
|
||||
@ -155,17 +191,29 @@ public class VideoImageMixedPlayer {
|
||||
|
||||
Object source = path.startsWith("/") ? new File(path) : path;
|
||||
|
||||
// ✅ 增加生命週期檢查,避免 Glide 在 Activity 銷毀後嘗試載入導致崩潰
|
||||
if (context instanceof android.app.Activity) {
|
||||
android.app.Activity activity = (android.app.Activity) context;
|
||||
if (activity.isFinishing() || (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed())) {
|
||||
logs.info("⚠️ Activity 已銷毀,跳過圖片載入: " + path);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Glide.with(context)
|
||||
.load(source)
|
||||
.fitCenter()
|
||||
.into(imageView);
|
||||
|
||||
nextSchedule(10000); // 圖片輪播 10 秒
|
||||
// 使用動態秒數,若為 0 則預設 10 秒
|
||||
int duration = adItems.get(currentIndex).duration;
|
||||
if (duration <= 0) duration = 10;
|
||||
nextSchedule(duration * 1000L);
|
||||
}
|
||||
|
||||
private void nextSchedule(long delayMs) {
|
||||
handler.postDelayed(() -> {
|
||||
currentIndex = (currentIndex + 1) % mediaUrls.size();
|
||||
currentIndex = (currentIndex + 1) % adItems.size();
|
||||
playNext();
|
||||
}, delayMs);
|
||||
}
|
||||
@ -180,7 +228,16 @@ public class VideoImageMixedPlayer {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
logs.info("Ads 播放器停止,清空視圖");
|
||||
releaseExoPlayer();
|
||||
if (playerView != null) {
|
||||
playerView.setVisibility(android.view.View.GONE);
|
||||
playerView.setPlayer(null);
|
||||
}
|
||||
if (imageView != null) {
|
||||
imageView.setVisibility(android.view.View.GONE);
|
||||
imageView.setImageDrawable(null);
|
||||
}
|
||||
if (handler != null) {
|
||||
handler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
57
build_log.txt
Normal file
57
build_log.txt
Normal file
@ -0,0 +1,57 @@
|
||||
Starting a Gradle Daemon, 1 busy and 3 incompatible and 1 stopped Daemons could not be reused, use --status for details
|
||||
> Task :app:preBuild UP-TO-DATE
|
||||
> Task :app:preS_12_XY_U_Standard_DebugBuild UP-TO-DATE
|
||||
> Task :app:mergeS_12_XY_U_Standard_DebugNativeDebugMetadata NO-SOURCE
|
||||
> Task :app:generateS_12_XY_U_Standard_DebugResValues
|
||||
> Task :app:dataBindingMergeDependencyArtifactsS_12_XY_U_Standard_Debug
|
||||
> Task :app:generateS_12_XY_U_Standard_DebugResources
|
||||
> Task :app:packageS_12_XY_U_Standard_DebugResources
|
||||
> Task :app:javaPreCompileS_12_XY_U_Standard_Debug
|
||||
> Task :app:mergeS_12_XY_U_Standard_DebugResources
|
||||
> Task :app:checkS_12_XY_U_Standard_DebugAarMetadata
|
||||
> Task :app:mapS_12_XY_U_Standard_DebugSourceSetPaths
|
||||
> Task :app:createS_12_XY_U_Standard_DebugCompatibleScreenManifests
|
||||
> Task :app:extractDeepLinksS_12_XY_U_Standard_Debug
|
||||
> Task :app:parseS_12_XY_U_Standard_DebugLocalResources
|
||||
> Task :app:processS_12_XY_U_Standard_DebugMainManifest
|
||||
> Task :app:dataBindingGenBaseClassesS_12_XY_U_Standard_Debug
|
||||
> Task :app:processS_12_XY_U_Standard_DebugManifest
|
||||
> Task :app:mergeS_12_XY_U_Standard_DebugShaders
|
||||
> Task :app:compileS_12_XY_U_Standard_DebugShaders NO-SOURCE
|
||||
> Task :app:generateS_12_XY_U_Standard_DebugAssets UP-TO-DATE
|
||||
> Task :app:mergeS_12_XY_U_Standard_DebugAssets
|
||||
> Task :app:compressS_12_XY_U_Standard_DebugAssets
|
||||
> Task :app:desugarS_12_XY_U_Standard_DebugFileDependencies
|
||||
> Task :app:mergeS_12_XY_U_Standard_DebugJniLibFolders
|
||||
> Task :app:checkS_12_XY_U_Standard_DebugDuplicateClasses
|
||||
> Task :app:mergeS_12_XY_U_Standard_DebugNativeLibs
|
||||
> Task :app:processS_12_XY_U_Standard_DebugManifestForPackage
|
||||
> Task :app:mergeLibDexS_12_XY_U_Standard_Debug
|
||||
|
||||
> Task :app:stripS_12_XY_U_Standard_DebugDebugSymbols
|
||||
Unable to strip the following libraries, packaging them as they are: libserial_port.so.
|
||||
|
||||
> Task :app:validateSigningS_12_XY_U_Standard_Debug
|
||||
> Task :app:writeS_12_XY_U_Standard_DebugAppMetadata
|
||||
> Task :app:writeS_12_XY_U_Standard_DebugSigningConfigVersions
|
||||
> Task :app:mergeExtDexS_12_XY_U_Standard_Debug
|
||||
warn: removing resource com.unibuy.smartdevice:string/reload_product without required default value.
|
||||
warn: removing resource com.unibuy.smartdevice:string/reload_product_warning without required default value.
|
||||
|
||||
> Task :app:processS_12_XY_U_Standard_DebugResources
|
||||
|
||||
> Task :app:compileS_12_XY_U_Standard_DebugJavaWithJavac
|
||||
Note: Some input files use or override a deprecated API.
|
||||
Note: Recompile with -Xlint:deprecation for details.
|
||||
|
||||
> Task :app:dexBuilderS_12_XY_U_Standard_Debug
|
||||
> Task :app:processS_12_XY_U_Standard_DebugJavaRes
|
||||
> Task :app:mergeProjectDexS_12_XY_U_Standard_Debug
|
||||
> Task :app:mergeS_12_XY_U_Standard_DebugJavaResource
|
||||
> Task :app:packageS_12_XY_U_Standard_Debug
|
||||
> Task :app:createS_12_XY_U_Standard_DebugApkListingFileRedirect
|
||||
> Task :app:assembleS_12_XY_U_Standard_Debug
|
||||
[Incubating] Problems report is available at: file:///home/mama/projects/TaiwanStar_general_size_s_Chengwai/build/reports/problems/problems-report.html
|
||||
|
||||
BUILD SUCCESSFUL in 56s
|
||||
36 actionable tasks: 36 executed
|
||||
Loading…
Reference in New Issue
Block a user