Compare commits
5 Commits
contacts-v
...
contacts
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ef6141c9d | |||
| fb79b83705 | |||
| de89a326c0 | |||
| dd95e1ea6c | |||
| 0237e72c62 |
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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, "所有广播反注册完成");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user