Compare commits

..

5 Commits

4 changed files with 322 additions and 171 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sat Apr 18 16:47:18 HKT 2026
stageCount=11
#Sat Apr 18 21:14:59 HKT 2026
stageCount=13
libraryProject=
baseVersion=15.14
publishVersion=15.14.10
publishVersion=15.14.12
buildCount=0
baseBetaVersion=15.14.11
baseBetaVersion=15.14.13

View File

@@ -11,13 +11,14 @@ import cc.winboll.studio.libappbase.LogUtils;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/04/18 15:00:00 (香港时区)
* @LastEditTime 2026/04/18 22:15:00 (香港时区)
* @Date 2026/04/18 15:00:00 (GMT+8)
* @LastEditTime 2026/04/22 17:20:00 (GMT+8)
* @Describe 限时特殊通道服务
* 提供安全的服务启动、令牌校验及循环任务管理
* 新增功能:
* 1. 计时过程中实时输出剩余秒数
* 2. 服务销毁前,发送本地广播通知应用内其他组件
* 2. 服务销毁前,发送本地广播通知应用内其他组件,并携带剩余秒数信息
* 3. 每秒发送一次倒计时状态的本地广播
*/
public class LimitedTimeSpecialChannelService extends Service {
@@ -26,9 +27,16 @@ public class LimitedTimeSpecialChannelService extends Service {
public static final String EXTRA_DELAY_MILLIS = "EXTRA_DELAY_MILLIS";
private static final String EXTRA_SECURITY_TOKEN = "EXTRA_SECURITY_TOKEN";
// 新增:本地广播 Action 常量
// 本地广播 Action 常量
public static final String ACTION_SERVICE_DESTROYED = "cc.winboll.studio.contacts.services.ACTION_SERVICE_DESTROYED";
// 倒计时心跳广播 Action
public static final String ACTION_COUNTDOWN_TICK = "cc.winboll.studio.contacts.services.ACTION_COUNTDOWN_TICK";
// 广播携带的额外参数:剩余秒数
public static final String EXTRA_REMAINING_SECONDS = "EXTRA_REMAINING_SECONDS";
// 广播携带的额外参数:总定时秒数
public static final String EXTRA_TOTAL_SECONDS = "EXTRA_TOTAL_SECONDS";
// ========================= 静态变量 =========================
/**
* 公共静态私有字段:安全校验令牌
@@ -52,7 +60,7 @@ public class LimitedTimeSpecialChannelService extends Service {
* @param delayMillis 定时时长(毫秒)
*/
public static void startService(Context context, long delayMillis) {
LogUtils.i(TAG, "调用 startService 入口");
LogUtils.i(TAG, "调用静态入口方法 startService");
LogUtils.i(TAG, "入参 - context: " + context + ", delayMillis: " + delayMillis);
if (context == null) {
@@ -60,7 +68,7 @@ public class LimitedTimeSpecialChannelService extends Service {
return;
}
if (isServiceRunning()) {
LogUtils.i(TAG, "服务已运行,忽略重复启动");
LogUtils.i(TAG, "服务已运行,忽略重复启动请求");
return;
}
@@ -78,7 +86,7 @@ public class LimitedTimeSpecialChannelService extends Service {
* @param context 上下文
*/
public static void stopService(Context context) {
LogUtils.i(TAG, "调用 stopService 入口");
LogUtils.i(TAG, "调用静态入口方法 stopService");
LogUtils.i(TAG, "入参 - context: " + context);
if (context == null) {
@@ -153,6 +161,7 @@ public class LimitedTimeSpecialChannelService extends Service {
// 初始化本地广播管理器
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
LogUtils.i(TAG, "本地广播管理器初始化完成");
}
@Override
@@ -161,7 +170,7 @@ public class LimitedTimeSpecialChannelService extends Service {
LogUtils.i(TAG, "入参 - intent: " + intent + ", flags: " + flags + ", startId: " + startId);
if (!isValidToken(intent)) {
LogUtils.w(TAG, "安全校验失败,拒绝启动");
LogUtils.w(TAG, "安全校验失败,拒绝启动服务");
stopSelf();
return START_NOT_STICKY;
}
@@ -183,7 +192,7 @@ public class LimitedTimeSpecialChannelService extends Service {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
LogUtils.i(TAG, "定时结束,准备停止服务");
LogUtils.i(TAG, "定时时长结束,准备停止服务");
stopSelf();
}
}, delayMillis);
@@ -195,42 +204,63 @@ public class LimitedTimeSpecialChannelService extends Service {
public void onDestroy() {
LogUtils.i(TAG, "服务 onDestroy准备销毁");
// 新增:发送本地广播,通知应用内其他组件服务即将销毁
// 发送本地广播,通知应用内其他组件服务即将销毁
// 此处携带剩余秒数信息,方便接收端知晓具体的结束状态
sendServiceDestroyedBroadcast();
// 原有逻辑:重置状态
// 重置运行状态
sIsServiceRunning = false;
sInstance = null;
// 原有逻辑:清理所有回调
// 清理所有回调
mHandler.removeCallbacksAndMessages(null);
// 原有逻辑:执行父类销毁
// 执行父类销毁
super.onDestroy();
LogUtils.i(TAG, "服务销毁完成");
LogUtils.i(TAG, "服务销毁流程完成");
}
/**
* 新增:发送服务销毁的本地广播
* 发送服务销毁的本地广播
* 确保应用内所有注册了该广播接收器的组件都能收到通知
* [新增] 携带当前剩余秒数信息
*/
private void sendServiceDestroyedBroadcast() {
LogUtils.i(TAG, "准备发送服务销毁本地广播");
try {
Intent intent = new Intent(ACTION_SERVICE_DESTROYED);
// 可在此处添加额外数据,例如剩余时长等
intent.putExtra("REMAINING_SECONDS", mRemainingMillis / 1000);
// 使用本地广播管理器发送
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
LogUtils.i(TAG, "已发送本地广播 - 服务销毁通知: " + ACTION_SERVICE_DESTROYED);
// 新增:将当前剩余秒数放入广播附加数据中
intent.putExtra(EXTRA_REMAINING_SECONDS, mRemainingMillis / 1000);
mLocalBroadcastManager.sendBroadcast(intent);
LogUtils.i(TAG, "服务销毁广播发送成功Action: " + ACTION_SERVICE_DESTROYED);
LogUtils.i(TAG, "广播附加数据 - 剩余秒数: " + (mRemainingMillis / 1000));
} catch (Exception e) {
LogUtils.e(TAG, "发送服务销毁广播失败", e);
}
}
/**
* 发送倒计时心跳的本地广播
* 每秒执行一次,携带当前剩余秒数和总时长
*/
private void sendCountdownTickBroadcast() {
try {
Intent intent = new Intent(ACTION_COUNTDOWN_TICK);
intent.putExtra(EXTRA_REMAINING_SECONDS, mRemainingMillis / 1000);
intent.putExtra(EXTRA_TOTAL_SECONDS, mTotalMillis / 1000);
mLocalBroadcastManager.sendBroadcast(intent);
// 日志已精简,只在关键节点打印
} catch (Exception e) {
LogUtils.e(TAG, "发送倒计时广播失败", e);
}
}
@Override
public IBinder onBind(Intent intent) {
LogUtils.i(TAG, "服务 onBind不支持绑定");
LogUtils.i(TAG, "服务 onBind不支持绑定操作");
// 不支持绑定
return null;
}
@@ -240,7 +270,7 @@ public class LimitedTimeSpecialChannelService extends Service {
* 校验令牌有效性
*/
private boolean isValidToken(Intent intent) {
LogUtils.i(TAG, "调用 isValidToken 校验");
LogUtils.i(TAG, "调用 isValidToken 方法进行安全校验");
LogUtils.i(TAG, "入参 - intent: " + intent);
if (intent == null) {
@@ -248,7 +278,7 @@ public class LimitedTimeSpecialChannelService extends Service {
}
String incomingToken = intent.getStringExtra(EXTRA_SECURITY_TOKEN);
LogUtils.i(TAG, "接收到外部传入令牌: " + incomingToken);
LogUtils.i(TAG, "比对本地存储令牌: " + mValidToken);
LogUtils.i(TAG, "本地校验令牌: " + mValidToken);
return mValidToken.equals(incomingToken);
}
@@ -256,30 +286,42 @@ public class LimitedTimeSpecialChannelService extends Service {
* 启动循环任务
*/
private void startLoopTask() {
LogUtils.i(TAG, "启动倒计时循环任务");
mHandler.removeCallbacks(mLoopTaskRunnable);
mHandler.postDelayed(mLoopTaskRunnable, 1000);
}
/**
* 循环任务心跳
* 新增:实时计算并输出剩余秒数
* 核心逻辑先递减时长再发送广播确保0秒能被正确发送
*/
private final Runnable mLoopTaskRunnable = new Runnable() {
@Override
public void run() {
if (sIsServiceRunning) {
// 计算剩余秒数
long remainingSeconds = mRemainingMillis / 1000;
// 输出剩余秒数信息
LogUtils.i(TAG, "循环任务心跳:服务运行中,剩余 " + remainingSeconds + "");
// 核心修复:先递减剩余时长
if (mRemainingMillis >= 1000) {
mRemainingMillis -= 1000;
} else {
// 兜底防止负数直接置为0
mRemainingMillis = 0;
}
// 剩余时长递减
mRemainingMillis -= 1000;
// 计算并发送当前剩余秒数
long remainingSeconds = mRemainingMillis / 1000;
// 日志已精简只在非0状态打印避免日志刷屏
if (remainingSeconds > 0) {
LogUtils.i(TAG, "循环心跳:剩余 " + remainingSeconds + "");
}
// 发送倒计时广播
sendCountdownTickBroadcast();
// 递归调用,形成循环
startLoopTask();
}
}
};
}

View File

@@ -5,6 +5,8 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.LinearLayout;
@@ -16,193 +18,276 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
/**
* @Author ZhanGSKen<zhangsken@qq.com>
* @Date 2026/04/18 15:05
* @LastEditTime 2026/04/18 22:30:00 (香港时区)
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/04/18 15:05:00 (GMT+8)
* @LastEditTime 2026/04/22 17:30:00 (GMT+8)
* @Describe 限时特殊通道视图
* 功能更新
* 1. Switch 打开时EditText 不可编辑,布局背景变为绿色
* 2. Switch 关闭时EditText 可编辑,布局背景恢复为白色
* 3. 监听服务销毁本地广播,到达后自动关闭 Switch 并恢复背景色
* 功能说明
* 1. Switch 打开时EditText 不可编辑,布局背景变为绿色,加减按钮不可用
* 2. Switch 关闭时EditText 可编辑,布局背景恢复为白色,加减按钮恢复可用
* 3. 监听服务销毁本地广播,到达后自动关闭 Switch 并恢复背景色与按钮状态
* 4. 监听倒计时心跳广播实时更新EditText显示剩余秒数
* 5. 加减按钮控制秒数增减最小值限制为0
* 6. 新增快速设置按钮(btn_thisSeconds)点击直接设置为当前量级3600秒
* 7. [新增] 服务销毁时恢复EditText显示为倒计时剩余秒数
*/
public class LimitedTimeSpecialChannelView extends LinearLayout {
// ====================== 常量定义 =========================
public static final String TAG = "LimitedTimeSpecialChannelView";
// 定义秒数增量的量级
private static final long SECONDS_INCREMENT = 3600;
// ====================== 成员变量 =========================
private Context mContext;
private EditText mEtSeconds;
private Switch mSwEnable;
private LinearLayout mLlMain;
private Button mBtnAddSeconds;
private Button mBtnSubSeconds;
// [新增] 快速设置量级按钮
private Button mBtnThisSeconds;
private android.content.BroadcastReceiver mCountdownReceiver;
// ====================== 构造函数 =========================
/**
* 代码创建View时调用
* @param context 上下文
*/
public LimitedTimeSpecialChannelView(Context context) {
super(context);
initView(context);
}
/**
* XML布局中引用时调用
* @param context 上下文
* @param attrs 属性集合
*/
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
/**
* 指定样式属性
* @param context 上下文
* @param attrs 属性集合
* @param defStyleAttr 默认样式属性
*/
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
/**
* 指定样式资源
* @param context 上下文
* @param attrs 属性集合
* @param defStyleAttr 默认样式属性
* @param defStyleRes 默认样式资源
*/
public LimitedTimeSpecialChannelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context);
}
// ====================== 初始化方法 ========================
/**
* 初始化视图
* 加载布局文件,绑定控件,并注册广播接收器
* @param context 上下文
*/
// ====================== 初始化方法 =========================
private void initView(Context context) {
LogUtils.i(TAG, "初始化限时特殊通道视图");
LogUtils.i(TAG, "开始初始化视图...");
this.mContext = context;
// 1. 加载布局文件
LayoutInflater.from(context)
.inflate(R.layout.view_limitedtimespecialchannel, this, true);
// 加载布局文件
LayoutInflater.from(context).inflate(R.layout.view_limitedtimespecialchannel, this, true);
LogUtils.i(TAG, "布局文件加载完成");
LogUtils.i(TAG, "布局加载完成view_limitedtimespecialchannel.xml");
// 2. 绑定核心控件引用
// 绑定控件ID
bindViews();
// 3. 初始化开关状态与背景色关联
// 初始化所有按钮点击事件
initButtons();
// 初始化开关状态
initSwitchState();
// 4. 注册服务销毁本地广播监听
// 注册广播监听
registerServiceDestroyedListener();
registerCountdownTickListener();
LogUtils.i(TAG, "视图初始化流程结束");
}
/**
* 绑定布局中的控件ID
* 绑定布局中的所有控件ID
*/
private void bindViews() {
LogUtils.i(TAG, "开始绑定控件ID");
mEtSeconds = (EditText) findViewById(R.id.et_seconds);
mSwEnable = (Switch) findViewById(R.id.sw_enable);
mLlMain = (LinearLayout) findViewById(R.id.ll_main);
mBtnAddSeconds = (Button) findViewById(R.id.btn_addSeconds);
mBtnSubSeconds = (Button) findViewById(R.id.btn_subSeconds);
// [新增] 绑定快速设置按钮ID
mBtnThisSeconds = (Button) findViewById(R.id.btn_thisSeconds);
// 健壮性检查
if (mEtSeconds == null) {
LogUtils.e(TAG, "未找到ID为 et_seconds 的EditText");
if (mEtSeconds == null) LogUtils.e(TAG, "未找到ID为 et_seconds 的EditText");
if (mSwEnable == null) LogUtils.e(TAG, "未找到ID为 sw_enable 的Switch");
if (mLlMain == null) LogUtils.w(TAG, "未找到ID为 ll_main 的LinearLayout将无法设置背景色");
if (mBtnAddSeconds == null) LogUtils.e(TAG, "未找到ID为 btn_addSeconds 的Button");
if (mBtnSubSeconds == null) LogUtils.e(TAG, "未找到ID为 btn_subSeconds 的Button");
if (mBtnThisSeconds == null) LogUtils.e(TAG, "未找到ID为 btn_thisSeconds 的Button");
LogUtils.i(TAG, "控件ID绑定完成");
}
/**
* 初始化所有按钮的点击事件
* 包含:加号、减号、快速设置量级按钮
*/
private void initButtons() {
LogUtils.i(TAG, "初始化按钮点击事件");
// 1. 加号按钮:增加一个量级
if (mBtnAddSeconds != null) {
mBtnAddSeconds.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.i(TAG, "点击加号按钮,准备增加秒数");
try {
long current = Long.parseLong(mEtSeconds.getText().toString().trim());
long newValue = current + SECONDS_INCREMENT;
mEtSeconds.setText(String.valueOf(newValue));
LogUtils.i(TAG, "增加成功,当前秒数: " + newValue);
} catch (NumberFormatException e) {
LogUtils.e(TAG, "当前输入格式错误,无法增加", e);
mEtSeconds.setText("0");
}
}
});
}
if (mSwEnable == null) {
LogUtils.e(TAG, "未找到ID为 sw_enable 的Switch");
// 2. 减号按钮减少一个量级最低为0
if (mBtnSubSeconds != null) {
mBtnSubSeconds.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.i(TAG, "点击减号按钮,准备减少秒数");
try {
long current = Long.parseLong(mEtSeconds.getText().toString().trim());
long newValue = Math.max(0, current - SECONDS_INCREMENT);
mEtSeconds.setText(String.valueOf(newValue));
LogUtils.i(TAG, "减少成功,当前秒数: " + newValue);
} catch (NumberFormatException e) {
LogUtils.e(TAG, "当前输入格式错误,无法减少", e);
mEtSeconds.setText("0");
}
}
});
}
if (mLlMain == null) {
LogUtils.w(TAG, "未找到ID为 ll_main 的LinearLayout将无法设置背景色");
// 3. [新增] 快速设置按钮:点击直接设置为量级数值 3600
if (mBtnThisSeconds != null) {
mBtnThisSeconds.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.i(TAG, "点击快速设置按钮,设置秒数为: " + SECONDS_INCREMENT);
// 直接设置为常量数值
mEtSeconds.setText(String.valueOf(SECONDS_INCREMENT));
LogUtils.i(TAG, "快速设置成功,当前秒数: " + SECONDS_INCREMENT);
}
});
}
}
/**
* 初始化开关状态
* 设置Switch的监听事件以及初始背景色
* 设置Switch的监听事件以及背景色、按钮可点击状态
*/
private void initSwitchState() {
// 设置初始状态
LogUtils.i(TAG, "初始化开关状态");
boolean isChecked = mSwEnable.isChecked();
mEtSeconds.setEnabled(!isChecked);
if (mLlMain != null) {
mLlMain.setBackgroundColor(isChecked ? android.graphics.Color.GREEN : android.graphics.Color.WHITE);
}
LogUtils.i(TAG, "初始状态 - 开关: " + (isChecked ? "启用" : "关闭") + ", 背景色: " + (isChecked ? "绿色" : "白色"));
// 为Switch设置切换监听
// 设置初始UI状态
updateUIState(!isChecked);
LogUtils.i(TAG, "初始状态 - 开关: " + (isChecked ? "开启" : "关闭") + ", 背景色: " + (isChecked ? "绿色" : "白色") + ", 按钮可用: " + (!isChecked ? "" : ""));
// 设置开关切换监听
mSwEnable.setOnCheckedChangeListener(new Switch.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
LogUtils.i(TAG, "开关状态变更: " + (isChecked ? "" : "关闭"));
LogUtils.i(TAG, "开关状态变更: " + (isChecked ? "" : "关闭"));
// 根据开关状态更新UI可操作状态
updateUIState(!isChecked);
// 核心逻辑控制EditText的可编辑状态
mEtSeconds.setEnabled(!isChecked);
// 控制布局背景色
if (mLlMain != null) {
mLlMain.setBackgroundColor(isChecked ? android.graphics.Color.GREEN : android.graphics.Color.WHITE);
}
// 控制服务启动与停止
if (isChecked) {
// 打开开关时,启动服务
startLimitedTimeSpecialChannelService();
} else {
// 关闭开关时,停止服务
LimitedTimeSpecialChannelService.stopService(mContext);
}
}
});
}
/**
* 统一更新UI可操作状态
* @param enabled 可用状态
*/
private void updateUIState(boolean enabled) {
if (mEtSeconds != null) mEtSeconds.setEnabled(enabled);
if (mBtnAddSeconds != null) mBtnAddSeconds.setEnabled(enabled);
if (mBtnSubSeconds != null) mBtnSubSeconds.setEnabled(enabled);
// [新增] 同步快速设置按钮状态
if (mBtnThisSeconds != null) mBtnThisSeconds.setEnabled(enabled);
if (mLlMain != null) {
mLlMain.setBackgroundColor(enabled ? android.graphics.Color.WHITE : android.graphics.Color.GREEN);
}
}
/**
* 注册服务销毁本地广播监听
* 当服务销毁消息到达时,自动关闭开关并恢复背景色
* [修改] 接收广播时检索倒计时剩余秒数参数并设置到EditText
*/
private void registerServiceDestroyedListener() {
// 创建广播接收器
LocalBroadcastManager.getInstance(mContext)
.registerReceiver(new android.content.BroadcastReceiver() {
LogUtils.i(TAG, "注册服务销毁广播监听");
IntentFilter filter = new IntentFilter(LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED);
LocalBroadcastManager.getInstance(mContext).registerReceiver(new android.content.BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// 校验广播Action
if (LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED.equals(intent.getAction())) {
LogUtils.i(TAG, "收到服务销毁本地广播,准备关闭开关并恢复背景");
LogUtils.i(TAG, "收到服务销毁广播,执行恢复操作");
// 核心逻辑关闭Switch
// 1. 恢复开关状态
if (mSwEnable != null && mSwEnable.isChecked()) {
mSwEnable.setChecked(false);
}
// 恢复布局背景色为白色
if (mLlMain != null) {
mLlMain.setBackgroundColor(android.graphics.Color.WHITE);
// 2. [核心修改] 检索并设置倒计时剩余秒数参数
// 从广播附加数据中获取之前服务发送的剩余秒数
long remainingSeconds = intent.getLongExtra(LimitedTimeSpecialChannelService.EXTRA_REMAINING_SECONDS, 0);
// 实际倒计时消耗冲正
remainingSeconds -= 1;
// 倒计时秒数0值保底
remainingSeconds = remainingSeconds < 0? 0 : remainingSeconds;
LogUtils.i(TAG, "从销毁广播中检索到剩余秒数参数: " + remainingSeconds + "");
//ToastUtils.show("从销毁广播中检索到剩余秒数参数: " + remainingSeconds + " 秒");
// 3. 恢复所有UI状态为可用
updateUIState(true);
// 4. 设置EditText显示为倒计时剩余秒数
if (mEtSeconds != null) {
mEtSeconds.setText(String.valueOf(remainingSeconds));
LogUtils.i(TAG, "已将EditText设置为剩余秒数: " + remainingSeconds);
}
// 可选:通知用户服务已销毁
ToastUtils.show("服务已销毁,恢复初始状态");
//ToastUtils.show("服务已销毁,恢复倒计时剩余状态");
}
}
}, new IntentFilter(LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED));
}, filter);
}
/**
* 注册倒计时心跳广播监听
*/
private void registerCountdownTickListener() {
LogUtils.i(TAG, "注册倒计时心跳广播监听");
mCountdownReceiver = new android.content.BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (LimitedTimeSpecialChannelService.ACTION_COUNTDOWN_TICK.equals(intent.getAction())) {
long remainingSeconds = intent.getLongExtra(LimitedTimeSpecialChannelService.EXTRA_REMAINING_SECONDS, 0);
if (mEtSeconds != null) {
mEtSeconds.setText(String.valueOf(remainingSeconds));
}
if (mSwEnable != null) {
mSwEnable.setChecked(remainingSeconds>0);
}
}
}
};
IntentFilter filter = new IntentFilter(LimitedTimeSpecialChannelService.ACTION_COUNTDOWN_TICK);
LocalBroadcastManager.getInstance(mContext).registerReceiver(mCountdownReceiver, filter);
LogUtils.i(TAG, "倒计时广播注册成功");
}
/**
* 启动限时特殊通道服务
* 读取输入框中的秒数,转换为毫秒后启动服务
*/
private void startLimitedTimeSpecialChannelService() {
LogUtils.i(TAG, "检测到秒数输入框点击,准备启动服务");
// 1. 获取输入的秒数
LogUtils.i(TAG, "准备启动限时特殊通道服务");
String secondsStr = mEtSeconds.getText().toString().trim();
if (secondsStr.isEmpty()) {
LogUtils.w(TAG, "输入框为空,使用默认值 3600 秒");
@@ -210,43 +295,29 @@ public class LimitedTimeSpecialChannelView extends LinearLayout {
}
try {
// 2. 转换为毫秒数 (1秒 = 1000毫秒)
long seconds = Long.parseLong(secondsStr);
long delayMillis = seconds * 1000;
LogUtils.i(TAG, "输入秒数: " + seconds + " 秒,转换毫秒: " + delayMillis);
// 3. 调用服务启动方法
ToastUtils.show("调用 LimitedTimeSpecialChannelService 服务");
LogUtils.i(TAG, "启动参数 - 输入秒数: " + seconds + ", 转换毫秒: " + delayMillis);
//ToastUtils.show("调用 LimitedTimeSpecialChannelService 服务");
LimitedTimeSpecialChannelService.startService(mContext, delayMillis);
} catch (NumberFormatException e) {
LogUtils.e(TAG, "输入的秒数格式错误: " + secondsStr, e);
}
}
/**
*【可选】视图销毁时反注册广播,防止内存泄漏
*/
// @Override
// protected void onDetachedFromWindow() {
// super.onDetachedFromWindow();
// // 反注册本地广播
// LocalBroadcastManager.getInstance(mContext).unregisterregisterReceiver(mServiceDestroyedReceiver);
// LogUtils.i(TAG, "反注册服务销毁本地广播完成");
// }
// ====================== 生命周期方法 =========================
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
LogUtils.i(TAG, "视图销毁,反注册所有广播接收器");
// //【可选】定义广播接收器成员变量,方便反注册
// private android.content.BroadcastReceiver mServiceDestroyedReceiver;
//
// private void registerServiceDestroyedListener() {
// mServiceDestroyedReceiver = new android.content.BroadcastReceiver() {
// @Override
// public void onReceive(Context context, Intent intent) {
// // ... 原有onReceive逻辑 ...
// }
// };
// LocalBroadcastManager.getInstance(mContext).registerReceiver(mServiceDestroyedReceiver, new IntentFilter(LimitedTimeSpecialChannelService.ACTION_SERVICE_DESTROYED));
// }
if (mCountdownReceiver != null) {
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mCountdownReceiver);
LogUtils.i(TAG, "反注册倒计时心跳广播接收器");
}
LogUtils.i(TAG, "所有广播反注册完成");
}
}

View File

@@ -2,29 +2,67 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical|center_horizontal"
android:id="@+id/ll_main">
<EditText
android:layout_width="80dp"
android:inputType="number"
android:layout_height="wrap_content"
android:ems="10"
android:text="3600"
android:id="@+id/et_seconds"/>
<TextView
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="秒"/>
android:gravity="center_vertical">
<Switch
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="总倒计时:"/>
<EditText
android:layout_width="85dp"
android:inputType="number"
android:layout_height="wrap_content"
android:ems="10"
android:text="3600"
android:id="@+id/et_seconds"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="秒"/>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sw_enable"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/sw_enable"/>
android:layout_height="wrap_content">
<Button
android:layout_width="40dp"
android:layout_height="40dp"
android:text="-"
android:id="@+id/btn_subSeconds"/>
<Button
android:layout_width="wrap_content"
android:layout_height="40dp"
android:text="3600秒"
android:id="@+id/btn_thisSeconds"/>
<Button
android:layout_width="40dp"
android:layout_height="40dp"
android:text="+"
android:id="@+id/btn_addSeconds"/>
</LinearLayout>
</LinearLayout>