Compare commits
25 Commits
powerbell-
...
powerbell-
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d69d4da09 | |||
| c98685440d | |||
| 7183338c97 | |||
| e18ed1b0fd | |||
| 98c334f442 | |||
| 9d42a3e1e9 | |||
| 411af44303 | |||
| 9afb072351 | |||
| b0c91f3ee4 | |||
| b21d5ecdb5 | |||
| 47c328cd25 | |||
| 7134d4e1c8 | |||
| a98c9e4214 | |||
| cc7bbcf2a6 | |||
| 8da6162632 | |||
| 02c135fd8c | |||
| 2f42334f19 | |||
| 10348d2c8d | |||
| 21cdc219c8 | |||
| 3ebf87c642 | |||
| 347a4040cd | |||
| 6fd86a2742 | |||
| 798357aedd | |||
| 778a1bc98e | |||
| bb94f87597 |
87
.github/workflows/android.yml
vendored
87
.github/workflows/android.yml
vendored
@@ -1,87 +0,0 @@
|
|||||||
name: Android CI
|
|
||||||
|
|
||||||
# 触发器
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- *-beta
|
|
||||||
pull_request:
|
|
||||||
tags:
|
|
||||||
- *-beta
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
# 设置 JDK 环境
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: set up JDK 11
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '11'
|
|
||||||
distribution: 'temurin'
|
|
||||||
cache: gradle
|
|
||||||
|
|
||||||
- name: Grant execute permission for gradlew
|
|
||||||
run: chmod +x gradlew
|
|
||||||
|
|
||||||
# 获取应用打包秘钥库
|
|
||||||
- name: Checkout Android Keystore
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
repository: zhangsken/keystore # 存储应用打包用的 keystore 的仓库(格式:用户名/仓库名)
|
|
||||||
token: ${{ secrets.APP_SECRET_TOKEN_1 }} # 连接仓库的 token , 需要单独配置
|
|
||||||
path: keystore # 仓库的根目录名
|
|
||||||
|
|
||||||
# 打包 Stage Release 版本应用
|
|
||||||
- name: Build with Gradle
|
|
||||||
run: bash ./gradlew assembleBetaRelease
|
|
||||||
# 创建release
|
|
||||||
- name: Create Release
|
|
||||||
id: create_release
|
|
||||||
uses: actions/create-release@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.APP_SECRET_TOKEN_1 }}
|
|
||||||
# GitHub 会自动创建 GITHUB_TOKEN 密码以在工作流程中使用。
|
|
||||||
# 您可以使用 GITHUB_TOKEN 在工作流程运行中进行身份验证。
|
|
||||||
# 当您启用 GitHub Actions 时,GitHub 在您的仓库中安装 GitHub 应用程序。
|
|
||||||
# GITHUB_TOKEN 密码是一种 GitHub 应用程序 安装访问令牌。
|
|
||||||
# 您可以使用安装访问令牌代表仓库中安装的 GitHub 应用程序 进行身份验证。
|
|
||||||
# 令牌的权限仅限于包含您的工作流程的仓库。 更多信息请参阅“GITHUB_TOKEN 的权限”。
|
|
||||||
# 在每个作业开始之前, GitHub 将为作业提取安装访问令牌。 令牌在作业完成后过期。
|
|
||||||
with:
|
|
||||||
tag_name: ${{ github.ref }}
|
|
||||||
release_name: Release ${{ github.ref }}
|
|
||||||
draft: false
|
|
||||||
prerelease: false
|
|
||||||
|
|
||||||
# 获取 APK 版本号
|
|
||||||
- name: Get Version Name
|
|
||||||
uses: actions/github-script@v3
|
|
||||||
id: get-version
|
|
||||||
with:
|
|
||||||
script: |
|
|
||||||
const str=process.env.GITHUB_REF;
|
|
||||||
return str.substring(str.indexOf("v"));
|
|
||||||
result-encoding: string
|
|
||||||
# 上传至 Release 的资源
|
|
||||||
- name: Upload Release Asset
|
|
||||||
id: upload-release-asset
|
|
||||||
uses: actions/upload-release-asset@v1
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.APP_SECRET_TOKEN_1 }}
|
|
||||||
with:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }} # 上传网址,无需改动
|
|
||||||
#asset_path: app/build/outputs/apk/release/app-release.apk # 上传路径(Release)
|
|
||||||
asset_path: app/build/outputs/apk/beta/release/app-beta-release.apk # 上传路径(WinBoll Stage Release)
|
|
||||||
asset_name: WinBoll-${{steps.get-version.outputs.result}}0.apk # 资源名
|
|
||||||
asset_content_type: application/vnd.android.package-archiv # 资源类型
|
|
||||||
|
|
||||||
# 存档打包的文件
|
|
||||||
- name: Archive production artifacts
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: build
|
|
||||||
path: app/build/outputs # 将打包之后的文件全部上传(里面会有混淆的 map 文件)
|
|
||||||
@@ -1,166 +1,223 @@
|
|||||||
#!/usr/bin/bash
|
#!/usr/bin/bash
|
||||||
|
# ==============================================================================
|
||||||
|
# WinBoLL 应用发布脚本
|
||||||
|
# 功能:检查Git源码状态 → 编译Stage Release包 → 添加WinBoLL标签 → 提交并推送源码
|
||||||
|
# 依赖:build.properties、app_update_description.txt(项目根目录下)
|
||||||
|
# 使用:./script_name.sh <APP_NAME>
|
||||||
|
# 作者:豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
# 检查是否指定了将要发布的应用名称
|
# ==================== 常量定义 ====================
|
||||||
# 使用 `-z` 命令检查变量是否为空
|
# 脚本退出码
|
||||||
|
EXIT_CODE_SUCCESS=0
|
||||||
|
EXIT_CODE_ERR_NO_APP_NAME=2
|
||||||
|
EXIT_CODE_ERR_WORK_DIR=1
|
||||||
|
EXIT_CODE_ERR_GIT_CHECK=1
|
||||||
|
EXIT_CODE_ERR_ADD_WINBOLL_TAG=1
|
||||||
|
|
||||||
|
# Gradle 任务(正式发布)
|
||||||
|
GRADLE_TASK_PUBLISH="assembleStageRelease"
|
||||||
|
# Gradle 任务(调试用,注释备用)
|
||||||
|
# GRADLE_TASK_DEBUG="assembleBetaDebug"
|
||||||
|
|
||||||
|
# ==================== 函数定义 ====================
|
||||||
|
# 检查Git源码是否已完全提交(无未提交变更)
|
||||||
|
# 返回值:0=已完全提交,1=存在未提交变更
|
||||||
|
function checkGitSources() {
|
||||||
|
# 配置Git安全目录(解决权限问题)
|
||||||
|
git config --global --add safe.directory "$(pwd)"
|
||||||
|
|
||||||
|
# 检查是否有未提交的变更
|
||||||
|
if [[ -n $(git diff --stat) ]]; then
|
||||||
|
echo "[ERROR] Git源码存在未提交变更,请先提交所有修改!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[INFO] Git源码检查通过:所有变更已提交。"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 询问是否添加GitHub Workflows标签(当前逻辑注释,保留扩展能力)
|
||||||
|
# 返回值:1=用户选择是,0=用户选择否
|
||||||
|
function askAddWorkflowsTag() {
|
||||||
|
read -p "是否添加GitHub Workflows标签?(Y/n) " answer
|
||||||
|
if [[ $answer =~ ^[Yy]$ ]]; then
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 添加WinBoLL正式标签
|
||||||
|
# 参数:$1=应用名称(项目根目录名)
|
||||||
|
# 返回值:0=标签添加成功,1=标签已存在/添加失败
|
||||||
|
function addWinBoLLTag() {
|
||||||
|
local app_name=$1
|
||||||
|
local build_prop_path="${app_name}/build.properties"
|
||||||
|
|
||||||
|
# 从build.properties中提取publishVersion
|
||||||
|
local publish_version=$(grep -o "publishVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}')
|
||||||
|
if [[ -z ${publish_version} ]]; then
|
||||||
|
echo "[ERROR] 未从${build_prop_path}中提取到publishVersion配置!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "[INFO] 从${build_prop_path}读取到publishVersion:${publish_version}"
|
||||||
|
|
||||||
|
# 构造WinBoLL标签(格式:<APP_NAME>-v<publishVersion>)
|
||||||
|
local tag="${app_name}-v${publish_version}"
|
||||||
|
echo "[INFO] 准备添加WinBoLL标签:${tag}"
|
||||||
|
|
||||||
|
# 检查标签是否已存在
|
||||||
|
if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then
|
||||||
|
echo "[ERROR] WinBoLL标签${tag}已存在!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加带注释的标签(注释来自app_update_description.txt)
|
||||||
|
git tag -a "${tag}" -F "${app_name}/app_update_description.txt"
|
||||||
|
echo "[INFO] WinBoLL标签${tag}添加成功!"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 添加GitHub Workflows Beta标签(当前逻辑注释,保留扩展能力)
|
||||||
|
# 参数:$1=应用名称(项目根目录名)
|
||||||
|
# 返回值:0=标签添加成功,1=标签已存在/添加失败
|
||||||
|
function addWorkflowsTag() {
|
||||||
|
local app_name=$1
|
||||||
|
local build_prop_path="${app_name}/build.properties"
|
||||||
|
|
||||||
|
# 从build.properties中提取baseBetaVersion
|
||||||
|
local base_beta_version=$(grep -o "baseBetaVersion=.*" "${build_prop_path}" | awk -F '=' '{print $2}')
|
||||||
|
if [[ -z ${base_beta_version} ]]; then
|
||||||
|
echo "[ERROR] 未从${build_prop_path}中提取到baseBetaVersion配置!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "[INFO] 从${build_prop_path}读取到baseBetaVersion:${base_beta_version}"
|
||||||
|
|
||||||
|
# 构造Workflows标签(格式:<APP_NAME>-v<baseBetaVersion>-beta)
|
||||||
|
local tag="${app_name}-v${base_beta_version}-beta"
|
||||||
|
echo "[INFO] 准备添加Workflows标签:${tag}"
|
||||||
|
|
||||||
|
# 检查标签是否已存在
|
||||||
|
if [[ "$(git tag -l ${tag})" == "${tag}" ]]; then
|
||||||
|
echo "[ERROR] Workflows标签${tag}已存在!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 添加带注释的标签(注释来自app_update_description.txt)
|
||||||
|
git tag -a "${tag}" -F "${app_name}/app_update_description.txt"
|
||||||
|
echo "[INFO] Workflows标签${tag}添加成功!"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# ==================== 主流程开始 ====================
|
||||||
|
echo "============================================="
|
||||||
|
echo " WinBoLL 应用发布脚本"
|
||||||
|
echo "============================================="
|
||||||
|
|
||||||
|
# 1. 检查应用名称参数是否指定
|
||||||
if [ -z "$1" ]; then
|
if [ -z "$1" ]; then
|
||||||
echo "No APP name specified : $0"
|
echo "[ERROR] 未指定应用名称!使用方式:${0} <APP_NAME>"
|
||||||
exit 2
|
exit ${EXIT_CODE_ERR_NO_APP_NAME}
|
||||||
fi
|
fi
|
||||||
|
APP_NAME=$1
|
||||||
|
echo "[INFO] 待发布应用名称:${APP_NAME}"
|
||||||
|
|
||||||
## 定义相关函数
|
# 2. 检查并切换到项目根目录(确保build.properties存在)
|
||||||
## 检查 Git 源码是否完全提交了,完全提交就返回0
|
echo "[INFO] 当前工作目录:$(pwd)"
|
||||||
function checkGitSources {
|
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
|
||||||
#local input="$1"
|
echo "[WARNING] 当前目录不存在${APP_NAME}/build.properties,尝试切换到上级目录..."
|
||||||
#echo "The string is: $input"
|
|
||||||
git config --global --add safe.directory `pwd`
|
|
||||||
if [[ -n $(git diff --stat) ]]
|
|
||||||
then
|
|
||||||
local result="Source is no commit completely."
|
|
||||||
echo $result
|
|
||||||
# 脚本调试时使用
|
|
||||||
#return 0
|
|
||||||
# 正式检查源码时使用
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
local result="Git Source Check OK."
|
|
||||||
echo $result
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function askAddWorkflowsTag {
|
|
||||||
read answer
|
|
||||||
if [[ $answer =~ ^[Yy]$ ]]; then
|
|
||||||
#echo "You chose yes."
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
#echo "You chose no."
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function addWinBoLLTag {
|
|
||||||
# 就读取脚本 .winboll/winboll_app_build.gradle 生成的 publishVersion。
|
|
||||||
# 如果文件中有 publishVersion 这一项,
|
|
||||||
# 使用grep找到包含"publishVersion="的那一行,然后用awk提取其后的值
|
|
||||||
PUBLISH_VERSION=$(grep -o "publishVersion=.*" $1/build.properties | awk -F '=' '{print $2}')
|
|
||||||
echo "< $1/build.properties publishVersion : ${PUBLISH_VERSION} >"
|
|
||||||
## 设新的 WinBoLL 标签
|
|
||||||
# 脚本调试时使用
|
|
||||||
#tag="projectname-v7.6.4-test1"
|
|
||||||
# 正式设置标签时使用
|
|
||||||
tag=$1"-v"${PUBLISH_VERSION}
|
|
||||||
echo "< WinBoLL Tag To: $tag >";
|
|
||||||
# 检查是否已经添加了 WinBoLL Tag
|
|
||||||
if [ "$(git tag -l ${tag})" == "${tag}" ]; then
|
|
||||||
echo -e "< WinBoLL Tag ${tag} exist! >"
|
|
||||||
return 1 # WinBoLL标签重复
|
|
||||||
fi
|
|
||||||
# 添加WinBoLL标签
|
|
||||||
git tag -a ${tag} -F $1/app_update_description.txt
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function addWorkflowsTag {
|
|
||||||
# 就读取脚本 .winboll/winboll_app_build.gradle 生成的 baseBetaVersion。
|
|
||||||
# 如果文件中有 baseBetaVersion 这一项,
|
|
||||||
# 使用grep找到包含"baseBetaVersion="的那一行,然后用awk提取其后的值
|
|
||||||
BASE_BETA_VERSION=$(grep -o "baseBetaVersion=.*" $1/build.properties | awk -F '=' '{print $2}')
|
|
||||||
echo "< $1/build.properties baseBetaVersion : ${BASE_BETA_VERSION} >"
|
|
||||||
## 设新的 workflows 标签
|
|
||||||
# 脚本调试时使用
|
|
||||||
#tag="projectname-v7.6.4-beta"
|
|
||||||
# 正式设置标签时使用
|
|
||||||
tag=$1"-v"${BASE_BETA_VERSION}-beta
|
|
||||||
echo "< Workflows Tag To: $tag >";
|
|
||||||
# 检查是否已经添加了工作流 Tag
|
|
||||||
if [ "$(git tag -l ${tag})" == "${tag}" ]; then
|
|
||||||
echo -e "< Github Workflows Tag ${tag} exist! >"
|
|
||||||
return 1 # 工作流标签重复
|
|
||||||
fi
|
|
||||||
# 添加工作流标签
|
|
||||||
git tag -a ${tag} -F $1/app_update_description.txt
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
## 开始执行脚本
|
|
||||||
echo -e "Current dir : \n"`pwd`
|
|
||||||
# 检查当前目录是否是项目根目录
|
|
||||||
if [[ -e $1/build.properties ]]; then
|
|
||||||
echo "The $1/build.properties file exists."
|
|
||||||
echo -e "Work dir correctly."
|
|
||||||
else
|
|
||||||
echo "The $1/build.properties file does not exist."
|
|
||||||
echo "尝试进入根目录"
|
|
||||||
# 进入项目根目录
|
|
||||||
cd ..
|
cd ..
|
||||||
|
echo "[INFO] 切换后工作目录:$(pwd)"
|
||||||
fi
|
fi
|
||||||
## 本脚本需要在项目根目录下执行
|
|
||||||
echo -e "Current dir : \n"`pwd`
|
# 验证最终工作目录是否正确
|
||||||
# 检查当前目录是否是项目根目录
|
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
|
||||||
if [[ -e $1/build.properties ]]; then
|
echo "[ERROR] 工作目录错误!${APP_NAME}/build.properties 文件不存在。"
|
||||||
echo "The $1/build.properties file exists."
|
exit ${EXIT_CODE_ERR_WORK_DIR}
|
||||||
echo -e "Work dir correctly."
|
fi
|
||||||
|
echo "[INFO] 工作目录验证通过:${APP_NAME}/build.properties 存在。"
|
||||||
|
|
||||||
|
# 3. 检查Git源码状态
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo " 步骤1:检查Git源码状态"
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
checkGitSources
|
||||||
|
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
|
||||||
|
echo "[ERROR] Git源码检查失败,脚本终止!"
|
||||||
|
exit ${EXIT_CODE_ERR_GIT_CHECK}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. 编译Stage Release版本APK
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo " 步骤2:编译Stage Release APK"
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo "[INFO] 开始执行Gradle任务:${GRADLE_TASK_PUBLISH}"
|
||||||
|
# 调试用(注释正式任务,启用调试任务)
|
||||||
|
# bash gradlew :${APP_NAME}:${GRADLE_TASK_DEBUG}
|
||||||
|
bash gradlew :${APP_NAME}:${GRADLE_TASK_PUBLISH}
|
||||||
|
|
||||||
|
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
|
||||||
|
echo "[ERROR] Gradle编译任务失败!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[INFO] Stage Release APK编译成功!"
|
||||||
|
|
||||||
|
# 5. 添加WinBoLL正式标签
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo " 步骤3:添加WinBoLL标签"
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
addWinBoLLTag ${APP_NAME}
|
||||||
|
if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
|
||||||
|
echo "[ERROR] WinBoLL标签添加失败,脚本终止!"
|
||||||
|
exit ${EXIT_CODE_ERR_ADD_WINBOLL_TAG}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 6. (可选)添加GitHub Workflows标签(当前逻辑注释,保留扩展能力)
|
||||||
|
# echo "---------------------------------------------"
|
||||||
|
# echo " 步骤4:添加Workflows标签(可选)"
|
||||||
|
# echo "---------------------------------------------"
|
||||||
|
# echo "是否添加GitHub Workflows Beta标签?(Y/n) "
|
||||||
|
# askAddWorkflowsTag
|
||||||
|
# nAskAddWorkflowsTag=$?
|
||||||
|
# if [[ ${nAskAddWorkflowsTag} -eq 1 ]]; then
|
||||||
|
# addWorkflowsTag ${APP_NAME}
|
||||||
|
# if [[ $? -ne ${EXIT_CODE_SUCCESS} ]]; then
|
||||||
|
# echo "[ERROR] Workflows标签添加失败,脚本终止!"
|
||||||
|
# exit 1
|
||||||
|
# fi
|
||||||
|
# fi
|
||||||
|
|
||||||
|
# 7. 清理更新描述文件
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo " 步骤5:清理更新描述文件"
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo "" > "${APP_NAME}/app_update_description.txt"
|
||||||
|
echo "[INFO] 已清空${APP_NAME}/app_update_description.txt"
|
||||||
|
|
||||||
|
# 8. 提交并推送源码与标签
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
echo " 步骤6:提交并推送源码"
|
||||||
|
echo "---------------------------------------------"
|
||||||
|
git add .
|
||||||
|
git commit -m "<${APP_NAME}> 开始新的Stage版本开发。"
|
||||||
|
echo "[INFO] 源码提交成功,开始推送..."
|
||||||
|
|
||||||
|
# 推送源码到远程仓库
|
||||||
|
git push origin
|
||||||
|
# 推送标签到远程仓库
|
||||||
|
git push origin --tags
|
||||||
|
|
||||||
|
if [[ $? -eq ${EXIT_CODE_SUCCESS} ]]; then
|
||||||
|
echo "[INFO] 源码与标签推送成功!"
|
||||||
else
|
else
|
||||||
echo "The $1/build.properties file does not exist."
|
echo "[ERROR] 源码与标签推送失败!"
|
||||||
echo -e "Work dir error."
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 检查源码状态
|
# ==================== 主流程结束 ====================
|
||||||
result=$(checkGitSources)
|
echo "============================================="
|
||||||
if [[ $? -eq 0 ]]; then
|
echo " WinBoLL 应用发布完成!"
|
||||||
echo $result
|
echo "============================================="
|
||||||
# 如果Git已经提交了所有代码就执行标签和应用发布操作
|
exit ${EXIT_CODE_SUCCESS}
|
||||||
|
|
||||||
# 预先询问是否添加工作流标签
|
|
||||||
#echo "Add Github Workflows Tag? (yes/No)"
|
|
||||||
#result=$(askAddWorkflowsTag)
|
|
||||||
#nAskAddWorkflowsTag=$?
|
|
||||||
#echo $result
|
|
||||||
|
|
||||||
# 发布应用
|
|
||||||
echo "Publishing WinBoLL APK ..."
|
|
||||||
# 脚本调试时使用
|
|
||||||
#bash gradlew :$1:assembleBetaDebug
|
|
||||||
# 正式发布
|
|
||||||
bash gradlew :$1:assembleStageRelease
|
|
||||||
echo "Publishing WinBoLL APK OK."
|
|
||||||
|
|
||||||
# 添加 WinBoLL 标签
|
|
||||||
result=$(addWinBoLLTag $1)
|
|
||||||
echo $result
|
|
||||||
if [[ $? -eq 0 ]]; then
|
|
||||||
echo $result
|
|
||||||
# WinBoLL 标签添加成功
|
|
||||||
else
|
|
||||||
echo -e "${0}: addWinBoLLTag $1\n${result}\nAdd WinBoLL tag cancel."
|
|
||||||
exit 1 # addWinBoLLTag 异常
|
|
||||||
fi
|
|
||||||
|
|
||||||
# 添加 GitHub 工作流标签
|
|
||||||
#if [[ $nAskAddWorkflowsTag -eq 1 ]]; then
|
|
||||||
# 如果用户选择添加工作流标签
|
|
||||||
#result=$(addWorkflowsTag $1)
|
|
||||||
#if [[ $? -eq 0 ]]; then
|
|
||||||
# echo $result
|
|
||||||
# 工作流标签添加成功
|
|
||||||
#else
|
|
||||||
#echo -e "${0}: addWorkflowsTag $1\n${result}\nAdd workflows tag cancel."
|
|
||||||
#exit 1 # addWorkflowsTag 异常
|
|
||||||
#fi
|
|
||||||
#fi
|
|
||||||
|
|
||||||
## 清理更新描述文件内容
|
|
||||||
echo "" > $1/app_update_description.txt
|
|
||||||
|
|
||||||
# 设置新版本开发参数配置
|
|
||||||
# 提交配置
|
|
||||||
git add .
|
|
||||||
git commit -m "<$1>Start New Stage Version."
|
|
||||||
echo "Push sources to git repositories ..."
|
|
||||||
# 推送源码到所有仓库
|
|
||||||
git push origin && git push origin --tags
|
|
||||||
else
|
|
||||||
echo -e "${0}: checkGitSources\n${result}\nShell cancel."
|
|
||||||
exit 1 # checkGitSources 异常
|
|
||||||
fi
|
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -5,10 +5,11 @@
|
|||||||
## ☁ ☁ ☁ WinBoLL APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
## ☁ ☁ ☁ WinBoLL APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
# ☁ ☁ ☁ WinBoLL 网站地址 https://www.winboll.cc/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
# ☁ ☁ ☁ WinBoLL 网站地址 https://www.winboll.cc/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
# ☁ ☁ ☁ WinBoLL 源码地址 <https://gitea.winboll.cc/Studio/APPBase> ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
# ☁ ☁ ☁ WinBoLL 源码地址 <https://gitea.winboll.cc/Studio/WinBoLL.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
# ☁ ☁ ☁ GitHub 源码地址 <https://github.com/ZhanGSKen/APPBase.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
# ☁ ☁ ☁ GitHub 源码地址 <https://github.com/ZhanGSKen/WinBoLL.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
# ☁ ☁ ☁ 码云 源码地址 <https://gitee.com/zhangsken/appbase.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
# ☁ ☁ ☁ 码云 源码地址 <https://gitee.com/zhangsken/winboll.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ 在 jitpack.io 托管的 APPBase 类库源码<https://github.com/ZhanGSKen/APPBase.git> ☁ ☁ ☁ ☁
|
||||||
|
# ☁ ☁ ☁ 在 jitpack.io 托管的 AES 类库源码<https://github.com/ZhanGSKen/AES.git> ☁ ☁ ☁ ☁
|
||||||
## WinBoLL 提问
|
## WinBoLL 提问
|
||||||
同样是 /sdcard 目录,在开发 Android 应用时,
|
同样是 /sdcard 目录,在开发 Android 应用时,
|
||||||
能否实现手机编译与电脑编译的源码同步。
|
能否实现手机编译与电脑编译的源码同步。
|
||||||
@@ -154,3 +155,11 @@ $ bash gradlew assembleBetaDebug
|
|||||||
$ bash gradlew assembleStageDebug
|
$ bash gradlew assembleStageDebug
|
||||||
|
|
||||||
### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
||||||
|
|
||||||
|
# 应用版本号命名方式
|
||||||
|
## statge 渠道
|
||||||
|
V<应用开发环境编号><应用功能变更号><应用调试阶段号>
|
||||||
|
如:APPBase_15.7.0
|
||||||
|
## beta 渠道
|
||||||
|
V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)>
|
||||||
|
如:APPBase_15.9.6-beta8_5413
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ dependencies {
|
|||||||
//api 'cc.winboll.studio:libappbase:15.12.2'
|
//api 'cc.winboll.studio:libappbase:15.12.2'
|
||||||
|
|
||||||
// WinBoLL备用库 jitpack.io 地址
|
// WinBoLL备用库 jitpack.io 地址
|
||||||
api 'com.github.ZhanGSKen:AES:aes-v15.12.8'
|
api 'com.github.ZhanGSKen:AES:aes-v15.12.9'
|
||||||
api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
|
api 'com.github.ZhanGSKen:APPBase:appbase-v15.14.1'
|
||||||
|
|
||||||
//api fileTree(dir: 'libs', include: ['*.aar'])
|
//api fileTree(dir: 'libs', include: ['*.aar'])
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Sun Dec 28 20:43:38 HKT 2025
|
#Mon Jan 05 19:22:24 HKT 2026
|
||||||
stageCount=41
|
stageCount=47
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.14
|
baseVersion=15.14
|
||||||
publishVersion=15.14.40
|
publishVersion=15.14.46
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.14.41
|
baseBetaVersion=15.14.47
|
||||||
|
|||||||
@@ -4,55 +4,84 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="cc.winboll.studio.powerbell">
|
package="cc.winboll.studio.powerbell">
|
||||||
|
|
||||||
<!-- 前台服务权限 -->
|
<!-- 此应用可显示在其他应用上方 -->
|
||||||
|
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||||
|
|
||||||
|
<!-- 运行前台服务 -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
|
<!-- 运行“specialUse”类型的前台服务 -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
||||||
|
|
||||||
<!-- 系统事件权限 -->
|
<!-- 开机启动 -->
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
|
|
||||||
<!-- 通知权限 -->
|
<!-- 显示通知 -->
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
|
||||||
<!-- 应用统计与查询权限 -->
|
<!-- PACKAGE_USAGE_STATS -->
|
||||||
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
|
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
|
||||||
|
|
||||||
|
<!-- BATTERY_STATS -->
|
||||||
|
<uses-permission android:name="android.permission.BATTERY_STATS"/>
|
||||||
|
|
||||||
|
<!-- 计算应用存储空间 -->
|
||||||
|
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||||
|
|
||||||
|
<!-- 请求忽略电池优化 -->
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
|
|
||||||
|
<!-- 读取您共享存储空间中的内容 -->
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<!-- 修改或删除您共享存储空间中的内容 -->
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<!-- MANAGE_EXTERNAL_STORAGE -->
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<!-- 拍摄照片和视频 -->
|
||||||
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS"
|
android:name="android.permission.ACCESS_PACKAGE_USAGE_STATS"
|
||||||
tools:ignore="ProtectedPermissions"/>
|
tools:ignore="ProtectedPermissions"/>
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.QUERY_ALL_PACKAGES"
|
android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||||
tools:ignore="QueryAllPackagesPermission"/>
|
tools:ignore="QueryAllPackagesPermission"/>
|
||||||
|
|
||||||
<!-- 电池与存储统计权限 -->
|
|
||||||
<uses-permission android:name="android.permission.BATTERY_STATS"/>
|
<uses-permission android:name="android.permission.BATTERY_STATS"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
|
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<!-- 相机权限 -->
|
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
<uses-permission android:name="android.permission.CAMERA"/>
|
||||||
|
|
||||||
<!-- 硬件特性声明 -->
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera"
|
android:name="android.hardware.camera"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.camera.autofocus"
|
android:name="android.hardware.camera.autofocus"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
|
||||||
<!-- 应用查询 -->
|
|
||||||
<queries>
|
<queries>
|
||||||
|
|
||||||
<package android:name="com.miui.securitycenter"/>
|
<package android:name="com.miui.securitycenter"/>
|
||||||
|
|
||||||
</queries>
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".App"
|
android:name=".App"
|
||||||
android:process=":main"
|
android:process=":main"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
@@ -64,7 +93,6 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
|
||||||
|
|
||||||
<!-- 主活动(主进程) -->
|
|
||||||
<activity
|
<activity
|
||||||
android:process=":main"
|
android:process=":main"
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
@@ -72,7 +100,6 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTask"/>
|
android:launchMode="singleTask"/>
|
||||||
|
|
||||||
<!-- 活动别名(启动器,主进程) -->
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
android:name=".MainActivityEN1"
|
android:name=".MainActivityEN1"
|
||||||
android:targetActivity=".MainActivity"
|
android:targetActivity=".MainActivity"
|
||||||
@@ -80,13 +107,19 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:enabled="true">
|
android:enabled="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcutsmainen1"/>
|
android:resource="@xml/shortcutsmainen1"/>
|
||||||
|
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
@@ -96,13 +129,19 @@
|
|||||||
android:label="@string/app_name_cn1"
|
android:label="@string/app_name_cn1"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:enabled="false">
|
android:enabled="false">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcutsmaincn1"/>
|
android:resource="@xml/shortcutsmaincn1"/>
|
||||||
|
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<activity-alias
|
<activity-alias
|
||||||
@@ -112,16 +151,21 @@
|
|||||||
android:label="@string/app_name_cn2"
|
android:label="@string/app_name_cn2"
|
||||||
android:icon="@drawable/ic_launcher"
|
android:icon="@drawable/ic_launcher"
|
||||||
android:enabled="false">
|
android:enabled="false">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.shortcuts"
|
android:name="android.app.shortcuts"
|
||||||
android:resource="@xml/shortcutsmaincn2"/>
|
android:resource="@xml/shortcutsmaincn2"/>
|
||||||
|
|
||||||
</activity-alias>
|
</activity-alias>
|
||||||
|
|
||||||
<!-- 功能活动(主进程) -->
|
|
||||||
<activity
|
<activity
|
||||||
android:process=":main"
|
android:process=":main"
|
||||||
android:name=".activities.CrashActivity"
|
android:name=".activities.CrashActivity"
|
||||||
@@ -140,15 +184,25 @@
|
|||||||
android:parentActivityName="cc.winboll.studio.powerbell.MainActivity"
|
android:parentActivityName="cc.winboll.studio.powerbell.MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:launchMode="singleTask">
|
android:launchMode="singleTask">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|
||||||
<action android:name="android.intent.action.SEND"/>
|
<action android:name="android.intent.action.SEND"/>
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
|
||||||
<data android:mimeType="image/jpeg"/>
|
<data android:mimeType="image/jpeg"/>
|
||||||
|
|
||||||
<data android:mimeType="image/jpg"/>
|
<data android:mimeType="image/jpg"/>
|
||||||
|
|
||||||
<data android:mimeType="image/png"/>
|
<data android:mimeType="image/png"/>
|
||||||
|
|
||||||
<data android:mimeType="image/webp"/>
|
<data android:mimeType="image/webp"/>
|
||||||
|
|
||||||
<data android:mimeType="image/*"/>
|
<data android:mimeType="image/*"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
@@ -186,39 +240,44 @@
|
|||||||
android:name="cc.winboll.studio.powerbell.unittest.MainUnitTest2Activity"
|
android:name="cc.winboll.studio.powerbell.unittest.MainUnitTest2Activity"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<!-- 第三方活动(主进程) -->
|
|
||||||
<activity
|
<activity
|
||||||
android:process=":main"
|
android:process=":main"
|
||||||
android:name="com.yalantis.ucrop.UCropActivity"
|
android:name="com.yalantis.ucrop.UCropActivity"
|
||||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||||
android:exported="true"/>
|
android:exported="true"/>
|
||||||
|
|
||||||
<!-- 广播接收器(主进程) -->
|
|
||||||
<receiver
|
<receiver
|
||||||
android:process=":main"
|
android:process=":main"
|
||||||
android:name=".receivers.MainReceiver"
|
android:name=".receivers.MainReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:directBootAware="true">
|
android:directBootAware="true">
|
||||||
|
|
||||||
<intent-filter android:priority="1000">
|
<intent-filter android:priority="1000">
|
||||||
|
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
<action android:name="android.intent.action.BOOT_COMPLETED"/>
|
||||||
|
|
||||||
<action android:name="android.intent.action.POWER_CONNECTED"/>
|
<action android:name="android.intent.action.POWER_CONNECTED"/>
|
||||||
|
|
||||||
<action android:name="android.intent.action.USER_PRESENT"/>
|
<action android:name="android.intent.action.USER_PRESENT"/>
|
||||||
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<!-- 服务(ControlCenterService主进程,AssistantService独立进程) -->
|
|
||||||
<service
|
<service
|
||||||
android:name=".services.ControlCenterService"
|
android:name=".services.ControlCenterService"
|
||||||
android:priority="1000"
|
android:priority="1000"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:process=":main"
|
android:process=":main"
|
||||||
android:stopWithTask="false"
|
android:stopWithTask="false"
|
||||||
android:foregroundServiceType="dataSync">
|
android:foregroundServiceType="dataSync">
|
||||||
|
|
||||||
<property
|
<property
|
||||||
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
||||||
android:value="后台核心功能运行、持续保活"/>
|
android:value="后台核心功能运行、持续保活"/>
|
||||||
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
@@ -226,26 +285,41 @@
|
|||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:process=":assistant"
|
android:process=":assistant"
|
||||||
android:stopWithTask="false"
|
android:stopWithTask="false"
|
||||||
android:foregroundServiceType="dataSync">
|
android:foregroundServiceType="dataSync">
|
||||||
|
|
||||||
<property
|
<property
|
||||||
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
android:name="android.app.PROPERTY_SPECIAL_USE_FOREGROUND_SERVICE"
|
||||||
android:value="辅助核心功能运行"/>
|
android:value="辅助核心功能运行"/>
|
||||||
|
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<!-- 内容提供者(主进程) -->
|
<service
|
||||||
|
android:name=".services.TTSPlayService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:process=":main"
|
||||||
|
android:stopWithTask="false"/>
|
||||||
|
|
||||||
|
<service android:name=".services.ThoughtfulService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:process=":main"
|
||||||
|
android:stopWithTask="false"/>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true"
|
android:grantUriPermissions="true"
|
||||||
android:process=":main">
|
android:process=":main">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
android:resource="@xml/file_provider"/>
|
android:resource="@xml/file_provider"/>
|
||||||
|
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<!-- 元数据 -->
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.max_aspect"
|
android:name="android.max_aspect"
|
||||||
android:value="4.0"/>
|
android:value="4.0"/>
|
||||||
@@ -253,4 +327,3 @@
|
|||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,22 @@ import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
|
|||||||
import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView;
|
import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用全局入口类(适配Android API 30,基于Java 7编写)
|
* 应用全局入口类
|
||||||
|
* 适配:Java7 语法规范 | Android API30 系统版本
|
||||||
* 核心策略:极致强制缓存 - 无论内存紧张程度,永不自动清理任何缓存(Bitmap/视图控件/路径记录)
|
* 核心策略:极致强制缓存 - 无论内存紧张程度,永不自动清理任何缓存(Bitmap/视图控件/路径记录)
|
||||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
* @Version 1.0.0
|
* @Date 2025-12-29 15:30:00
|
||||||
* @Date 2025-12-25
|
* @LastModified 2026-01-02 19:01:00
|
||||||
*/
|
*/
|
||||||
public class App extends GlobalApplication {
|
public class App extends GlobalApplication {
|
||||||
// ===================== 常量定义区(按功能分类排序) =====================
|
|
||||||
public static final String TAG = "App";
|
// ====================================== 常量区 - 置顶排序 (按功能归类) ======================================
|
||||||
|
// 基础日志TAG
|
||||||
|
private static final String TAG = "App";
|
||||||
|
// 缓存保护专用TAG
|
||||||
|
private static final String CACHE_PROTECT_TAG = "FORCE_CACHE_PROTECT";
|
||||||
|
// 电池无效值常量(修复拼写错误:INVALID_BATTERY_VALUE)
|
||||||
|
private static final int INVALID_BATTERY_VALUE = -1;
|
||||||
|
|
||||||
// 组件跳转常量
|
// 组件跳转常量
|
||||||
public static final String COMPONENT_EN1 = "cc.winboll.studio.powerbell.MainActivityEN1";
|
public static final String COMPONENT_EN1 = "cc.winboll.studio.powerbell.MainActivityEN1";
|
||||||
@@ -35,286 +42,279 @@ public class App extends GlobalApplication {
|
|||||||
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1";
|
public static final String ACTION_SWITCHTO_CN1 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN1";
|
||||||
public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2";
|
public static final String ACTION_SWITCHTO_CN2 = "cc.winboll.studio.powerbell.App.ACTION_SWITCHTO_CN2";
|
||||||
|
|
||||||
// 缓存防护常量
|
// ====================================== 静态属性区 - 全局单例/状态 (按核心程度排序) ======================================
|
||||||
private static final String CACHE_PROTECT_TAG = "FORCE_CACHE_PROTECT";
|
|
||||||
private static final int INVALID_BATTERY_VALUE = -1;
|
|
||||||
|
|
||||||
// ===================== 静态属性区(按工具类优先级排序) =====================
|
|
||||||
// 应用单例
|
// 应用单例
|
||||||
private static App sApp;
|
private static App sApp;
|
||||||
|
|
||||||
// 数据配置工具
|
// 配置与缓存工具 (全局单例)
|
||||||
public static AppConfigUtils sAppConfigUtils;
|
public static AppConfigUtils sAppConfigUtils;
|
||||||
private static AppCacheUtils sAppCacheUtils;
|
private static AppCacheUtils sAppCacheUtils;
|
||||||
|
|
||||||
// 全局Bitmap缓存工具(极致强制保持:一旦初始化,永不销毁)
|
// 资源与视图缓存 (强制驻留,极致缓存核心)
|
||||||
public static BackgroundSourceUtils sBackgroundSourceUtils;
|
public static BackgroundSourceUtils sBackgroundSourceUtils;
|
||||||
public static BitmapCacheUtils sBitmapCacheUtils;
|
public static BitmapCacheUtils sBitmapCacheUtils;
|
||||||
|
|
||||||
// 全局视图控件缓存工具(极致强制保持:一旦初始化,永不销毁)
|
|
||||||
private static MemoryCachedBackgroundView sMemoryCachedBackgroundView;
|
private static MemoryCachedBackgroundView sMemoryCachedBackgroundView;
|
||||||
|
|
||||||
// 电池状态
|
// 系统状态 (电池电量)
|
||||||
public static volatile int sQuantityOfElectricity = INVALID_BATTERY_VALUE;
|
public static volatile int sQuantityOfElectricity = INVALID_BATTERY_VALUE;
|
||||||
|
|
||||||
// 通知管理工具
|
// 系统工具 (通知管理器)
|
||||||
private static NotificationManagerUtils sNotificationManagerUtils;
|
private static NotificationManagerUtils sNotificationManagerUtils;
|
||||||
|
|
||||||
// ===================== 成员属性区(按生命周期关联度排序) =====================
|
// ====================================== 成员属性区 - 非静态成员 (广播接收器) ======================================
|
||||||
// 全局广播接收器
|
|
||||||
private GlobalApplicationReceiver mGlobalReceiver;
|
private GlobalApplicationReceiver mGlobalReceiver;
|
||||||
|
|
||||||
// ===================== 公共静态方法区(单例/工具类实例获取) =====================
|
// ====================================== 公共静态方法 - 单例/工具获取 (对外入口) ======================================
|
||||||
/**
|
/**
|
||||||
* 获取应用单例实例
|
* 获取应用全局单例实例
|
||||||
|
* @return 应用单例App实例
|
||||||
*/
|
*/
|
||||||
public static App getInstance() {
|
public static App getInstance() {
|
||||||
LogUtils.d(TAG, "getInstance() 调用 | 返回实例:" + sApp);
|
LogUtils.d(TAG, "【getInstance】应用单例获取方法调用 | 当前实例:" + sApp);
|
||||||
return sApp;
|
return sApp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取应用配置工具实例
|
* 获取配置工具类单例实例
|
||||||
|
* @param context 上下文对象
|
||||||
|
* @return 配置工具类AppConfigUtils实例
|
||||||
*/
|
*/
|
||||||
public static AppConfigUtils getAppConfigUtils(Context context) {
|
public static AppConfigUtils getAppConfigUtils(Context context) {
|
||||||
String contextType = context != null ? context.getClass().getSimpleName() : "null";
|
String contextClass = context != null ? context.getClass().getSimpleName() : "null";
|
||||||
LogUtils.d(TAG, "getAppConfigUtils() 调用 | 入参Context类型:" + contextType);
|
LogUtils.d(TAG, "【getAppConfigUtils】配置工具获取方法调用 | 入参Context类型:" + contextClass);
|
||||||
|
|
||||||
if (sAppConfigUtils == null) {
|
if (sAppConfigUtils == null) {
|
||||||
sAppConfigUtils = AppConfigUtils.getInstance(context);
|
sAppConfigUtils = AppConfigUtils.getInstance(context);
|
||||||
LogUtils.d(TAG, "getAppConfigUtils() | AppConfigUtils实例初始化完成");
|
LogUtils.d(TAG, "【getAppConfigUtils】配置工具实例为空,已初始化新实例");
|
||||||
}
|
}
|
||||||
LogUtils.d(TAG, "getAppConfigUtils() | 返回实例:" + sAppConfigUtils);
|
|
||||||
return sAppConfigUtils;
|
return sAppConfigUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取应用缓存工具实例
|
* 获取缓存工具类单例实例
|
||||||
|
* @param context 上下文对象
|
||||||
|
* @return 缓存工具类AppCacheUtils实例
|
||||||
*/
|
*/
|
||||||
public static AppCacheUtils getAppCacheUtils(Context context) {
|
public static AppCacheUtils getAppCacheUtils(Context context) {
|
||||||
String contextType = context != null ? context.getClass().getSimpleName() : "null";
|
String contextClass = context != null ? context.getClass().getSimpleName() : "null";
|
||||||
LogUtils.d(TAG, "getAppCacheUtils() 调用 | 入参Context类型:" + contextType);
|
LogUtils.d(TAG, "【getAppCacheUtils】缓存工具获取方法调用 | 入参Context类型:" + contextClass);
|
||||||
|
|
||||||
if (sAppCacheUtils == null) {
|
if (sAppCacheUtils == null) {
|
||||||
sAppCacheUtils = AppCacheUtils.getInstance(context);
|
sAppCacheUtils = AppCacheUtils.getInstance(context);
|
||||||
LogUtils.d(TAG, "getAppCacheUtils() | AppCacheUtils实例初始化完成");
|
LogUtils.d(TAG, "【getAppCacheUtils】缓存工具实例为空,已初始化新实例");
|
||||||
}
|
}
|
||||||
LogUtils.d(TAG, "getAppCacheUtils() | 返回实例:" + sAppCacheUtils);
|
|
||||||
return sAppCacheUtils;
|
return sAppCacheUtils;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 公共成员方法区(业务功能) =====================
|
// ====================================== 公共成员方法 - 业务逻辑 (实例方法) ======================================
|
||||||
/**
|
/**
|
||||||
* 清除电池历史数据
|
* 清除电池历史数据
|
||||||
*/
|
*/
|
||||||
public void clearBatteryHistory() {
|
public void clearBatteryHistory() {
|
||||||
LogUtils.d(TAG, "clearBatteryHistory() 调用");
|
LogUtils.d(TAG, "【clearBatteryHistory】清除电池历史数据方法调用");
|
||||||
if (sAppCacheUtils != null) {
|
if (sAppCacheUtils != null) {
|
||||||
sAppCacheUtils.clearBatteryHistory();
|
sAppCacheUtils.clearBatteryHistory();
|
||||||
LogUtils.d(TAG, "clearBatteryHistory() | 电池历史数据清除成功");
|
LogUtils.d(TAG, "【clearBatteryHistory】电池历史数据清除成功");
|
||||||
} else {
|
} else {
|
||||||
LogUtils.w(TAG, "clearBatteryHistory() | 失败:AppCacheUtils未初始化");
|
LogUtils.w(TAG, "【clearBatteryHistory】电池历史数据清除失败 | 缓存工具实例sAppCacheUtils为空");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 手动清理所有缓存(带严格权限控制,仅主动调用生效)
|
* 获取视图缓存实例
|
||||||
* 极致强制缓存策略下,仅提供手动清理入口,永不自动调用
|
* @return 视图缓存MemoryCachedBackgroundView实例
|
||||||
|
*/
|
||||||
|
public MemoryCachedBackgroundView getMemoryCachedBackgroundView() {
|
||||||
|
LogUtils.d(TAG, "【getMemoryCachedBackgroundView】视图缓存获取方法调用 | 当前实例:" + sMemoryCachedBackgroundView);
|
||||||
|
return sMemoryCachedBackgroundView;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 公共静态方法 - 业务逻辑 (全局工具方法) ======================================
|
||||||
|
/**
|
||||||
|
* 手动清理所有缓存(仅主动调用生效,符合极致缓存策略)
|
||||||
*/
|
*/
|
||||||
public static void manualClearAllCache() {
|
public static void manualClearAllCache() {
|
||||||
LogUtils.w(CACHE_PROTECT_TAG, "manualClearAllCache() 调用 | 极致缓存策略下谨慎使用");
|
LogUtils.w(CACHE_PROTECT_TAG, "【manualClearAllCache】手动清理缓存方法调用 | 仅主动触发生效");
|
||||||
|
|
||||||
// 清理Bitmap缓存
|
// 清理Bitmap缓存
|
||||||
if (sBitmapCacheUtils != null) {
|
if (sBitmapCacheUtils != null) {
|
||||||
sBitmapCacheUtils.clearAllCache();
|
sBitmapCacheUtils.clearAllCache();
|
||||||
LogUtils.d(CACHE_PROTECT_TAG, "manualClearAllCache() | Bitmap缓存手动清理完成");
|
LogUtils.d(CACHE_PROTECT_TAG, "【manualClearAllCache】Bitmap缓存已清理");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清理视图控件缓存(仅清除静态引用,不销毁实例)
|
// 仅置空视图缓存引用,不销毁实例(极致缓存策略)
|
||||||
if (sMemoryCachedBackgroundView != null) {
|
if (sMemoryCachedBackgroundView != null) {
|
||||||
LogUtils.d(CACHE_PROTECT_TAG, "manualClearAllCache() | 视图缓存保留实例,仅清除静态引用");
|
LogUtils.d(CACHE_PROTECT_TAG, "【manualClearAllCache】视图缓存引用已置空 | 实例保留");
|
||||||
sMemoryCachedBackgroundView = null;
|
sMemoryCachedBackgroundView = null;
|
||||||
}
|
}
|
||||||
LogUtils.w(CACHE_PROTECT_TAG, "manualClearAllCache() | 手动清理完成 | 部分缓存实例仍驻留内存");
|
|
||||||
|
LogUtils.w(CACHE_PROTECT_TAG, "【manualClearAllCache】手动清理缓存操作完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取视图控件缓存实例(非通用:仅通过App实例调用,避免全局直接访问)
|
* 发送通知消息(仅调试模式下生效)
|
||||||
*/
|
* @param title 通知标题
|
||||||
public MemoryCachedBackgroundView getMemoryCachedBackgroundView() {
|
* @param content 通知内容
|
||||||
LogUtils.d(TAG, "getMemoryCachedBackgroundView() 调用 | 当前实例:" + sMemoryCachedBackgroundView);
|
|
||||||
return sMemoryCachedBackgroundView;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送调试通知
|
|
||||||
*/
|
*/
|
||||||
public static void notifyMessage(String title, String content) {
|
public static void notifyMessage(String title, String content) {
|
||||||
LogUtils.d(TAG, "notifyMessage() 调用 | 入参title:" + title + " | content:" + content);
|
LogUtils.d(TAG, "【notifyMessage】发送通知消息方法调用 | 标题:" + title + " | 内容:" + content);
|
||||||
|
|
||||||
boolean canNotify = isDebugging() && sApp != null && sNotificationManagerUtils != null;
|
boolean canSend = isDebugging() && sApp != null && sNotificationManagerUtils != null;
|
||||||
if (canNotify) {
|
if (canSend) {
|
||||||
NotificationMessage message = new NotificationMessage(title, content, "");
|
NotificationMessage message = new NotificationMessage(title, content, "");
|
||||||
sNotificationManagerUtils.showMessageNotification(sApp, message);
|
sNotificationManagerUtils.showMessageNotification(sApp, message);
|
||||||
LogUtils.d(TAG, "notifyMessage() | 调试通知发送成功");
|
LogUtils.d(TAG, "【notifyMessage】通知消息发送成功");
|
||||||
} else {
|
} else {
|
||||||
LogUtils.d(TAG, "notifyMessage() | 发送失败:调试模式未开启/工具类未初始化");
|
LogUtils.d(TAG, "【notifyMessage】通知消息发送失败 | 条件不满足:调试模式=" + isDebugging() + " | 应用实例=" + (sApp != null) + " | 通知工具=" + (sNotificationManagerUtils != null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 生命周期方法区(按执行顺序排序) =====================
|
// ====================================== 生命周期方法 - 应用全局生命周期 (重写父类方法) ======================================
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
LogUtils.d(TAG, "onCreate() | 应用启动,开始初始化流程");
|
LogUtils.d(TAG, "【onCreate】应用启动生命周期方法调用 | 开始初始化应用...");
|
||||||
sApp = this;
|
|
||||||
|
|
||||||
// 初始化调试模式
|
// 初始化应用单例与调试模式
|
||||||
|
sApp = this;
|
||||||
setIsDebugging(BuildConfig.DEBUG);
|
setIsDebugging(BuildConfig.DEBUG);
|
||||||
LogUtils.d(TAG, "onCreate() | 调试模式状态:" + BuildConfig.DEBUG);
|
LogUtils.d(TAG, "【onCreate】应用单例已初始化 | 调试模式:" + BuildConfig.DEBUG);
|
||||||
|
|
||||||
// 初始化核心组件
|
// 初始化核心组件
|
||||||
initBaseTools();
|
initBaseTools();
|
||||||
initUtils();
|
initUtils();
|
||||||
initReceiver();
|
initReceiver();
|
||||||
|
|
||||||
LogUtils.d(TAG, "onCreate() | 应用初始化完成 | 极致强制缓存策略已启用");
|
LogUtils.d(TAG, "【onCreate】应用初始化完成 | 极致强制缓存策略已激活");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTerminate() {
|
public void onTerminate() {
|
||||||
super.onTerminate();
|
super.onTerminate();
|
||||||
LogUtils.d(TAG, "onTerminate() | 应用终止,释放非缓存资源");
|
LogUtils.d(TAG, "【onTerminate】应用终止生命周期方法调用 | 开始释放非缓存资源...");
|
||||||
|
|
||||||
// 释放轻量级工具
|
// 释放非缓存资源
|
||||||
ToastUtils.release();
|
ToastUtils.release();
|
||||||
LogUtils.d(TAG, "onTerminate() | Toast工具资源释放完成");
|
|
||||||
|
|
||||||
releaseNotificationManager();
|
releaseNotificationManager();
|
||||||
releaseReceiver();
|
releaseReceiver();
|
||||||
|
|
||||||
// 核心策略:应用终止不清理任何缓存
|
// 核心策略:不清理任何缓存
|
||||||
LogUtils.w(CACHE_PROTECT_TAG, "onTerminate() | 极致缓存策略生效 | 所有缓存实例保持驻留");
|
LogUtils.w(CACHE_PROTECT_TAG, "【onTerminate】极致缓存策略生效 | 所有缓存将保留在内存中");
|
||||||
LogUtils.d(TAG, "onTerminate() | 非缓存资源释放完成");
|
LogUtils.d(TAG, "【onTerminate】非缓存资源释放完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrimMemory(int level) {
|
public void onTrimMemory(int level) {
|
||||||
super.onTrimMemory(level);
|
super.onTrimMemory(level);
|
||||||
// 极致缓存策略:拒绝系统触发的缓存清理
|
LogUtils.w(CACHE_PROTECT_TAG, "【onTrimMemory】系统内存修剪回调 | 内存等级:" + level + " | 忽略修剪,缓存强制保护");
|
||||||
LogUtils.w(CACHE_PROTECT_TAG, "onTrimMemory() 调用 | 内存等级:" + level + " | 强制保持所有缓存");
|
|
||||||
logDetailedCacheStatus();
|
logDetailedCacheStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLowMemory() {
|
public void onLowMemory() {
|
||||||
super.onLowMemory();
|
super.onLowMemory();
|
||||||
// 低内存场景:不清理缓存,仅记录状态
|
LogUtils.w(CACHE_PROTECT_TAG, "【onLowMemory】系统低内存回调 | 极致缓存策略生效 | 不执行任何缓存清理操作");
|
||||||
LogUtils.w(CACHE_PROTECT_TAG, "onLowMemory() 调用 | 极致缓存策略:不执行任何清理操作");
|
|
||||||
logDetailedCacheStatus();
|
logDetailedCacheStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 私有初始化方法区(按初始化顺序排序) =====================
|
// ====================================== 私有初始化方法 - 组件初始化 (按依赖顺序排序) ======================================
|
||||||
/**
|
/**
|
||||||
* 初始化基础工具(Activity管理、Toast、通知工具)
|
* 初始化基础工具类(Activity管理、Toast、通知管理器)
|
||||||
*/
|
*/
|
||||||
private void initBaseTools() {
|
private void initBaseTools() {
|
||||||
LogUtils.d(TAG, "initBaseTools() | 开始初始化基础工具集");
|
LogUtils.d(TAG, "【initBaseTools】基础工具类初始化开始...");
|
||||||
WinBoLLActivityManager.init(this);
|
WinBoLLActivityManager.init(this);
|
||||||
ToastUtils.init(this);
|
ToastUtils.init(this);
|
||||||
sNotificationManagerUtils = new NotificationManagerUtils(this);
|
sNotificationManagerUtils = new NotificationManagerUtils(this);
|
||||||
LogUtils.d(TAG, "initBaseTools() | ActivityManager/Toast/Notification工具初始化完成");
|
LogUtils.d(TAG, "【initBaseTools】基础工具类初始化完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化核心工具类(极致强制缓存:一旦初始化永不销毁)
|
* 初始化核心工具与缓存(极致强制驻留,缓存核心)
|
||||||
*/
|
*/
|
||||||
private void initUtils() {
|
private void initUtils() {
|
||||||
LogUtils.d(TAG, "initUtils() | 开始初始化核心工具类 | 启用极致强制缓存策略");
|
LogUtils.d(TAG, "【initUtils】核心工具与缓存初始化开始 | 极致缓存策略激活");
|
||||||
|
|
||||||
// 初始化配置与缓存工具
|
// 1. 配置与基础缓存工具初始化
|
||||||
sAppConfigUtils = getAppConfigUtils(this);
|
sAppConfigUtils = getAppConfigUtils(this);
|
||||||
sAppCacheUtils = getAppCacheUtils(this);
|
sAppCacheUtils = getAppCacheUtils(this);
|
||||||
|
|
||||||
// 初始化背景资源与Bitmap缓存
|
// 2. 资源与Bitmap缓存工具初始化(永久驻留)
|
||||||
sBackgroundSourceUtils = BackgroundSourceUtils.getInstance(this);
|
sBackgroundSourceUtils = BackgroundSourceUtils.getInstance(this);
|
||||||
sBackgroundSourceUtils.loadSettings();
|
sBackgroundSourceUtils.loadSettings();
|
||||||
sBitmapCacheUtils = BitmapCacheUtils.getInstance();
|
sBitmapCacheUtils = BitmapCacheUtils.getInstance();
|
||||||
LogUtils.d(TAG, "initUtils() | BackgroundSource/BitmapCache工具初始化完成 | 永久驻留内存");
|
LogUtils.d(TAG, "【initUtils】资源与Bitmap缓存工具初始化完成 | 永久驻留内存");
|
||||||
|
|
||||||
// 初始化视图控件缓存
|
// 3. 视图缓存初始化(永久驻留,无实例则创建)
|
||||||
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getLastInstance(this);
|
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getLastInstance(this);
|
||||||
if (sMemoryCachedBackgroundView == null) {
|
if (sMemoryCachedBackgroundView == null) {
|
||||||
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getInstance(this, sBackgroundSourceUtils.getCurrentBackgroundBean(), true);
|
sMemoryCachedBackgroundView = MemoryCachedBackgroundView.getInstance(this, sBackgroundSourceUtils.getCurrentBackgroundBean(), true);
|
||||||
LogUtils.d(TAG, "initUtils() | 视图缓存工具:新建实例完成");
|
LogUtils.d(TAG, "【initUtils】视图缓存无现有实例,已创建新实例");
|
||||||
}
|
}
|
||||||
LogUtils.d(TAG, "initUtils() | MemoryCachedBackgroundView初始化完成 | 永久驻留内存");
|
LogUtils.d(TAG, "【initUtils】视图缓存初始化完成 | 永久驻留内存");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化全局广播接收器
|
* 注册全局广播接收器
|
||||||
*/
|
*/
|
||||||
private void initReceiver() {
|
private void initReceiver() {
|
||||||
LogUtils.d(TAG, "initReceiver() | 开始初始化广播接收器");
|
LogUtils.d(TAG, "【initReceiver】全局广播接收器注册开始...");
|
||||||
mGlobalReceiver = new GlobalApplicationReceiver(this);
|
mGlobalReceiver = new GlobalApplicationReceiver(this);
|
||||||
mGlobalReceiver.registerAction();
|
mGlobalReceiver.registerAction();
|
||||||
LogUtils.d(TAG, "initReceiver() | 广播接收器注册完成");
|
LogUtils.d(TAG, "【initReceiver】全局广播接收器注册完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 私有释放方法区(按资源重要性排序) =====================
|
// ====================================== 私有释放方法 - 资源释放 (按创建逆序排序) ======================================
|
||||||
/**
|
/**
|
||||||
* 释放广播接收器资源
|
* 释放全局广播接收器
|
||||||
*/
|
*/
|
||||||
private void releaseReceiver() {
|
private void releaseReceiver() {
|
||||||
LogUtils.d(TAG, "releaseReceiver() | 开始释放广播接收器");
|
LogUtils.d(TAG, "【releaseReceiver】全局广播接收器释放开始...");
|
||||||
if (mGlobalReceiver != null) {
|
if (mGlobalReceiver != null) {
|
||||||
mGlobalReceiver.unregisterAction();
|
mGlobalReceiver.unregisterAction();
|
||||||
mGlobalReceiver = null;
|
mGlobalReceiver = null;
|
||||||
LogUtils.d(TAG, "releaseReceiver() | 广播接收器资源释放完成");
|
LogUtils.d(TAG, "【releaseReceiver】全局广播接收器释放完成");
|
||||||
} else {
|
|
||||||
LogUtils.d(TAG, "releaseReceiver() | 无需释放:广播接收器未初始化");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 释放通知管理工具资源
|
* 释放通知管理器资源
|
||||||
*/
|
*/
|
||||||
private void releaseNotificationManager() {
|
private void releaseNotificationManager() {
|
||||||
LogUtils.d(TAG, "releaseNotificationManager() | 开始释放通知工具");
|
LogUtils.d(TAG, "【releaseNotificationManager】通知管理器资源释放开始...");
|
||||||
if (sNotificationManagerUtils != null) {
|
if (sNotificationManagerUtils != null) {
|
||||||
sNotificationManagerUtils.release();
|
sNotificationManagerUtils.release();
|
||||||
sNotificationManagerUtils = null;
|
sNotificationManagerUtils = null;
|
||||||
LogUtils.d(TAG, "releaseNotificationManager() | 通知工具资源释放完成");
|
LogUtils.d(TAG, "【releaseNotificationManager】通知管理器资源释放完成");
|
||||||
} else {
|
|
||||||
LogUtils.d(TAG, "releaseNotificationManager() | 无需释放:通知工具未初始化");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== 私有工具方法区(辅助功能) =====================
|
// ====================================== 私有辅助方法 - 日志/工具 (辅助功能) ======================================
|
||||||
/**
|
/**
|
||||||
* 记录详细缓存状态(用于调试,监控极致强制缓存效果)
|
* 记录当前缓存详细状态(用于调试监控,极致缓存策略监控)
|
||||||
*/
|
*/
|
||||||
private void logDetailedCacheStatus() {
|
private void logDetailedCacheStatus() {
|
||||||
LogUtils.d(TAG, "logDetailedCacheStatus() | 开始记录缓存状态");
|
LogUtils.d(TAG, "【logDetailedCacheStatus】缓存状态监控日志开始...");
|
||||||
|
|
||||||
// 记录Bitmap缓存状态
|
// Bitmap缓存状态
|
||||||
if (sBitmapCacheUtils != null) {
|
if (sBitmapCacheUtils != null) {
|
||||||
LogUtils.d(CACHE_PROTECT_TAG, "Bitmap缓存工具:实例有效(永久驻留)");
|
LogUtils.d(CACHE_PROTECT_TAG, "【缓存状态】BitmapCache - 有效");
|
||||||
try {
|
try {
|
||||||
int cacheCount = sBitmapCacheUtils.getCacheCount();
|
LogUtils.d(CACHE_PROTECT_TAG, "【缓存详情】Bitmap缓存数量:" + sBitmapCacheUtils.getCacheCount());
|
||||||
LogUtils.d(CACHE_PROTECT_TAG, "Bitmap缓存数量:" + cacheCount);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LogUtils.d(CACHE_PROTECT_TAG, "Bitmap缓存数量获取失败 | 异常信息:" + e.getMessage());
|
LogUtils.e(CACHE_PROTECT_TAG, "【缓存详情】获取Bitmap缓存数量失败", e);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.d(CACHE_PROTECT_TAG, "【缓存状态】BitmapCache - 未初始化");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录视图缓存状态
|
// 视图缓存状态
|
||||||
if (sMemoryCachedBackgroundView != null) {
|
if (sMemoryCachedBackgroundView != null) {
|
||||||
LogUtils.d(CACHE_PROTECT_TAG, "视图缓存工具:实例有效(永久驻留)");
|
LogUtils.d(CACHE_PROTECT_TAG, "【缓存状态】ViewCache - 有效");
|
||||||
int viewCount = MemoryCachedBackgroundView.getInstanceCount();
|
LogUtils.d(CACHE_PROTECT_TAG, "【缓存详情】视图实例数量:" + MemoryCachedBackgroundView.getInstanceCount());
|
||||||
LogUtils.d(CACHE_PROTECT_TAG, "视图缓存实例总数:" + viewCount);
|
} else {
|
||||||
|
LogUtils.d(CACHE_PROTECT_TAG, "【缓存状态】ViewCache - 引用已置空(实例可能保留)");
|
||||||
}
|
}
|
||||||
LogUtils.d(TAG, "logDetailedCacheStatus() | 缓存状态记录完成");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,24 @@
|
|||||||
package cc.winboll.studio.powerbell.activities;
|
package cc.winboll.studio.powerbell.activities;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.provider.Settings;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
import cc.winboll.studio.libappbase.LogUtils;
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
import cc.winboll.studio.powerbell.R;
|
import cc.winboll.studio.powerbell.R;
|
||||||
|
import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 应用设置窗口,提供应用配置项的统一入口
|
* 应用设置窗口,提供应用配置项的统一入口
|
||||||
@@ -44,6 +56,13 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
|
|||||||
|
|
||||||
// 初始化工具栏
|
// 初始化工具栏
|
||||||
initToolbar();
|
initToolbar();
|
||||||
|
|
||||||
|
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
|
||||||
|
if (thoughtfulServiceBean == null) {
|
||||||
|
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||||
|
}
|
||||||
|
((CheckBox)findViewById(R.id.activitysettingsCheckBox1)).setChecked(thoughtfulServiceBean.isEnableUsePowerTts());
|
||||||
|
((CheckBox)findViewById(R.id.activitysettingsCheckBox2)).setChecked(thoughtfulServiceBean.isEnableChargeTts());
|
||||||
|
|
||||||
LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化完成");
|
LogUtils.d(TAG, "【onCreate】SettingsActivity 初始化完成");
|
||||||
}
|
}
|
||||||
@@ -70,5 +89,96 @@ public class SettingsActivity extends WinBoLLActivity implements IWinBoLLActivit
|
|||||||
});
|
});
|
||||||
LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
|
LogUtils.d(TAG, "【initToolbar】工具栏初始化完成");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onCheckTTSDrawOverlaysPermission(View view) {
|
||||||
|
canDrawOverlays();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEnableChargeTts(View view) {
|
||||||
|
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
|
||||||
|
if (thoughtfulServiceBean == null) {
|
||||||
|
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||||
|
}
|
||||||
|
thoughtfulServiceBean.setIsEnableChargeTts(((CheckBox)view).isChecked());
|
||||||
|
ThoughtfulServiceBean.saveBean(this, thoughtfulServiceBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onEnableUsePowerTts(View view) {
|
||||||
|
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(this, ThoughtfulServiceBean.class);
|
||||||
|
if (thoughtfulServiceBean == null) {
|
||||||
|
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||||
|
}
|
||||||
|
thoughtfulServiceBean.setIsEnableUsePowerTts(((CheckBox)view).isChecked());
|
||||||
|
ThoughtfulServiceBean.saveBean(this, thoughtfulServiceBean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 悬浮窗权限检查与请求
|
||||||
|
*/
|
||||||
|
void canDrawOverlays() {
|
||||||
|
LogUtils.d(TAG, "onCanDrawOverlays: 检查悬浮窗权限");
|
||||||
|
// API6.0+校验权限
|
||||||
|
if (Build.VERSION.SDK_INT >= 23 && !Settings.canDrawOverlays(this)) {
|
||||||
|
LogUtils.d(TAG, "onCanDrawOverlays: 未开启悬浮窗权限,发起请求");
|
||||||
|
showDrawOverlayRequestDialog();
|
||||||
|
} else {
|
||||||
|
ToastUtils.show("悬浮窗权限已开启");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示悬浮窗权限请求对话框
|
||||||
|
*/
|
||||||
|
private void showDrawOverlayRequestDialog() {
|
||||||
|
AlertDialog dialog = new AlertDialog.Builder(this)
|
||||||
|
.setTitle("权限请求")
|
||||||
|
.setMessage("为保证通话监听功能正常,需开启悬浮窗权限")
|
||||||
|
.setPositiveButton("去设置", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
jumpToDrawOverlaySettings();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton("稍后", new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.create();
|
||||||
|
|
||||||
|
// 解决对话框焦点问题
|
||||||
|
if (dialog.getWindow() != null) {
|
||||||
|
dialog.getWindow().setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||||
|
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
|
||||||
|
}
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跳转悬浮窗权限设置页面(反射适配低版本)
|
||||||
|
*/
|
||||||
|
private void jumpToDrawOverlaySettings() {
|
||||||
|
LogUtils.d(TAG, "jumpToDrawOverlaySettings: 跳转悬浮窗权限设置");
|
||||||
|
try {
|
||||||
|
// 反射获取设置页面Action(避免高版本API依赖)
|
||||||
|
Class<?> settingsClazz = Settings.class;
|
||||||
|
Field actionField = settingsClazz.getDeclaredField("ACTION_MANAGE_OVERLAY_PERMISSION");
|
||||||
|
String action = (String) actionField.get(null);
|
||||||
|
|
||||||
|
// 跳转当前应用权限设置页
|
||||||
|
Intent intent = new Intent(action);
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.setData(Uri.parse("package:" + getPackageName()));
|
||||||
|
startActivity(intent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "jumpToDrawOverlaySettings: 跳转权限设置失败", e);
|
||||||
|
Toast.makeText(this, "请手动在设置中开启悬浮窗权限", Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package cc.winboll.studio.powerbell.models;
|
||||||
|
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTS 语音播放文本内容实体类
|
||||||
|
* 适配:Java7 语法规范 | Android API30 系统版本
|
||||||
|
* 特性:实现序列化接口,支持跨页面/进程传递,属性默认值初始化
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/12/29 19:13
|
||||||
|
*/
|
||||||
|
public class TTSSpeakTextBean implements Serializable {
|
||||||
|
|
||||||
|
// ====================================== 常量区 - 置顶排序 ======================================
|
||||||
|
/** 日志TAG 瞬态修饰,不参与序列化,减少序列化体积 */
|
||||||
|
transient public static final String TAG = "TTSSpeakTextBean";
|
||||||
|
|
||||||
|
// ====================================== 成员属性区 - 业务属性排序 ======================================
|
||||||
|
/** 延迟播放时长 单位:毫秒,默认值0:无延迟播放 */
|
||||||
|
public int mnDelay = 0;
|
||||||
|
/** TTS语音播放文本内容,默认值空字符串:防止空指针 */
|
||||||
|
public String mszSpeakContent = "";
|
||||||
|
|
||||||
|
// ====================================== 构造方法区 - 无参+有参 完整实现 ======================================
|
||||||
|
/**
|
||||||
|
* 无参构造方法
|
||||||
|
* Java7序列化规范必备 + 兼容反射实例化场景
|
||||||
|
*/
|
||||||
|
public TTSSpeakTextBean() {
|
||||||
|
LogUtils.d(TAG, "【无参构造】TTSSpeakTextBean 实例化,使用默认值 | 延迟:" + mnDelay + " | 文本:" + mszSpeakContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有参构造方法【主构造】
|
||||||
|
* @param nDelay 延迟播放时长(ms)
|
||||||
|
* @param szSpeakContent 语音播放文本内容
|
||||||
|
*/
|
||||||
|
public TTSSpeakTextBean(int nDelay, String szSpeakContent) {
|
||||||
|
LogUtils.d(TAG, "【有参构造】TTSSpeakTextBean 实例化,入参 | 延迟:" + nDelay + " | 文本:" + szSpeakContent);
|
||||||
|
this.mnDelay = nDelay;
|
||||||
|
this.mszSpeakContent = szSpeakContent;
|
||||||
|
LogUtils.d(TAG, "【有参构造】赋值完成 | 最终延迟:" + this.mnDelay + " | 最终文本:" + this.mszSpeakContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package cc.winboll.studio.powerbell.models;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/12/29 20:59
|
||||||
|
* @Describe 贴心服务配置实体类 (适配API30 / Java7)
|
||||||
|
*/
|
||||||
|
public class ThoughtfulServiceBean extends BaseBean implements Parcelable, Serializable {
|
||||||
|
|
||||||
|
// ====================== 常量区 - 置顶统一管理 ======================
|
||||||
|
public static final String TAG = ThoughtfulServiceBean.class.getSimpleName();
|
||||||
|
private static final long serialVersionUID = 1L; // Serializable 序列化兼容必备
|
||||||
|
// JSON序列化字段常量 杜绝硬编码
|
||||||
|
public static final String JSON_FIELD_IS_ENABLE_CHARGE_TTS = "isEnableChargeTts";
|
||||||
|
public static final String JSON_FIELD_IS_ENABLE_USE_POWER_TTS = "isEnableUsePowerTts";
|
||||||
|
|
||||||
|
// ====================== 核心成员变量 - 私有封装 ======================
|
||||||
|
private boolean isEnableChargeTts = false; // 是否启用 充电TTS贴心语音服务
|
||||||
|
private boolean isEnableUsePowerTts = false; // 是否启用 用电TTS贴心语音服务
|
||||||
|
|
||||||
|
// ====================== Parcelable 静态创建器 (API30标准写法 必须public static final) ======================
|
||||||
|
public static final Creator<ThoughtfulServiceBean> CREATOR = new Creator<ThoughtfulServiceBean>() {
|
||||||
|
@Override
|
||||||
|
public ThoughtfulServiceBean createFromParcel(Parcel source) {
|
||||||
|
return new ThoughtfulServiceBean(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ThoughtfulServiceBean[] newArray(int size) {
|
||||||
|
LogUtils.d(TAG, "newArray: 初始化数组,size = " + size);
|
||||||
|
return new ThoughtfulServiceBean[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====================== 构造方法区 (无参+有参+Parcel构造 全覆盖) ======================
|
||||||
|
/**
|
||||||
|
* 无参构造 - JSON解析/反射实例化 必备
|
||||||
|
*/
|
||||||
|
public ThoughtfulServiceBean() {
|
||||||
|
LogUtils.d(TAG, "ThoughtfulServiceBean: 无参构造,初始化默认禁用所有TTS服务");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全参构造 - 手动配置所有服务状态
|
||||||
|
* @param isEnableChargeTts 充电TTS服务开关
|
||||||
|
* @param isEnableUsePowerTts 用电TTS服务开关
|
||||||
|
*/
|
||||||
|
public ThoughtfulServiceBean(boolean isEnableChargeTts, boolean isEnableUsePowerTts) {
|
||||||
|
this.isEnableChargeTts = isEnableChargeTts;
|
||||||
|
this.isEnableUsePowerTts = isEnableUsePowerTts;
|
||||||
|
LogUtils.d(TAG, "ThoughtfulServiceBean: 全参构造 | isEnableChargeTts=" + isEnableChargeTts + " | isEnableUsePowerTts=" + isEnableUsePowerTts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parcel反序列化构造 - Parcelable必备 私有私有化
|
||||||
|
*/
|
||||||
|
private ThoughtfulServiceBean(Parcel in) {
|
||||||
|
this.isEnableChargeTts = in.readByte() != 0;
|
||||||
|
this.isEnableUsePowerTts = in.readByte() != 0;
|
||||||
|
LogUtils.d(TAG, "ThoughtfulServiceBean: Parcel构造解析完成 | isEnableChargeTts=" + isEnableChargeTts + " | isEnableUsePowerTts=" + isEnableUsePowerTts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== Getter/Setter 方法区 (封装成员变量 统一访问) ======================
|
||||||
|
public boolean isEnableChargeTts() {
|
||||||
|
return isEnableChargeTts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsEnableChargeTts(boolean isEnableChargeTts) {
|
||||||
|
LogUtils.d(TAG, "setIsEnableChargeTts: 旧值=" + this.isEnableChargeTts + " | 新值=" + isEnableChargeTts);
|
||||||
|
this.isEnableChargeTts = isEnableChargeTts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableUsePowerTts() {
|
||||||
|
return isEnableUsePowerTts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsEnableUsePowerTts(boolean isEnableUsePowerTts) {
|
||||||
|
LogUtils.d(TAG, "setIsEnableUsePowerTts: 旧值=" + this.isEnableUsePowerTts + " | 新值=" + isEnableUsePowerTts);
|
||||||
|
this.isEnableUsePowerTts = isEnableUsePowerTts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 重写父类 BaseBean 核心方法 (JSON序列化/反序列化 业务核心) ======================
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
String className = ThoughtfulServiceBean.class.getName();
|
||||||
|
LogUtils.d(TAG, "getName: 返回当前实体类名 = " + className);
|
||||||
|
return className;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON序列化 - 写入所有字段 适配持久化/网络传输
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
jsonWriter.name(JSON_FIELD_IS_ENABLE_CHARGE_TTS).value(this.isEnableChargeTts);
|
||||||
|
jsonWriter.name(JSON_FIELD_IS_ENABLE_USE_POWER_TTS).value(this.isEnableUsePowerTts);
|
||||||
|
LogUtils.d(TAG, "writeThisToJsonWriter: JSON序列化完成,所有TTS服务状态已写入");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON反序列化 - 读取字段生成实体 适配数据恢复
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||||
|
ThoughtfulServiceBean bean = new ThoughtfulServiceBean();
|
||||||
|
jsonReader.beginObject();
|
||||||
|
while (jsonReader.hasNext()) {
|
||||||
|
String fieldName = jsonReader.nextName();
|
||||||
|
switch (fieldName) {
|
||||||
|
case JSON_FIELD_IS_ENABLE_CHARGE_TTS:
|
||||||
|
bean.setIsEnableChargeTts(jsonReader.nextBoolean());
|
||||||
|
break;
|
||||||
|
case JSON_FIELD_IS_ENABLE_USE_POWER_TTS:
|
||||||
|
bean.setIsEnableUsePowerTts(jsonReader.nextBoolean());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
jsonReader.skipValue();
|
||||||
|
LogUtils.w(TAG, "readBeanFromJsonReader: 跳过未知JSON字段 = " + fieldName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonReader.endObject();
|
||||||
|
LogUtils.d(TAG, "readBeanFromJsonReader: JSON反序列化完成,生成实体对象");
|
||||||
|
return bean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================== 实现 Parcelable 接口方法 (组件间Intent传递必备 API30/Java7完美适配) ======================
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0; // 无文件描述符等特殊内容,固定返回0即可
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parcel序列化 - boolean用byte存储(Java7/API30标准写法 避免兼容性问题)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeByte((byte) (isEnableChargeTts ? 1 : 0));
|
||||||
|
dest.writeByte((byte) (isEnableUsePowerTts ? 1 : 0));
|
||||||
|
LogUtils.d(TAG, "writeToParcel: Parcel序列化完成,所有TTS服务状态已写入");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
|||||||
import cc.winboll.studio.powerbell.utils.BatteryUtils;
|
import cc.winboll.studio.powerbell.utils.BatteryUtils;
|
||||||
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
|
import cc.winboll.studio.powerbell.utils.NotificationManagerUtils;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import cc.winboll.studio.powerbell.services.ThoughtfulService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 控制中心广播接收器
|
* 控制中心广播接收器
|
||||||
@@ -35,9 +36,10 @@ public class ControlCenterServiceReceiver extends BroadcastReceiver {
|
|||||||
private static final int BROADCAST_PRIORITY = IntentFilter.SYSTEM_HIGH_PRIORITY - 10;
|
private static final int BROADCAST_PRIORITY = IntentFilter.SYSTEM_HIGH_PRIORITY - 10;
|
||||||
private static final int BATTERY_LEVEL_MIN = 0;
|
private static final int BATTERY_LEVEL_MIN = 0;
|
||||||
private static final int BATTERY_LEVEL_MAX = 100;
|
private static final int BATTERY_LEVEL_MAX = 100;
|
||||||
|
private static final int INVALID_BATTERY = -1; // 无效电量标识
|
||||||
|
|
||||||
// ====================== 静态状态标记(volatile保证多线程可见性) ======================
|
// ====================== 静态状态标记(volatile保证多线程可见性) ======================
|
||||||
private static volatile int sLastBatteryLevel = -1; // 上次电量(多线程可见)
|
private static volatile int sLastBatteryLevel = INVALID_BATTERY; // 上次电量(多线程可见)
|
||||||
private static volatile boolean sIsCharging = false; // 上次充电状态(多线程可见)
|
private static volatile boolean sIsCharging = false; // 上次充电状态(多线程可见)
|
||||||
|
|
||||||
// ====================== 成员变量区(弱引用防泄漏,按功能分层) ======================
|
// ====================== 成员变量区(弱引用防泄漏,按功能分层) ======================
|
||||||
@@ -110,6 +112,16 @@ public class ControlCenterServiceReceiver extends BroadcastReceiver {
|
|||||||
LogUtils.d(TAG, "handleBatteryStateChanged() 跳过 | 电池状态无变化");
|
LogUtils.d(TAG, "handleBatteryStateChanged() 跳过 | 电池状态无变化");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在插拔充电线时,执行贴心服务
|
||||||
|
if(currentCharging != sIsCharging && sLastBatteryLevel != INVALID_BATTERY) {
|
||||||
|
//App.notifyMessage(TAG, String.format("sLastBatteryLevel %d", sLastBatteryLevel));
|
||||||
|
if(currentCharging) {
|
||||||
|
ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.CHARGE_STATE);
|
||||||
|
} else {
|
||||||
|
ThoughtfulService.startServiceWithType(service, ThoughtfulService.ServiceType.DISCHARGE_STATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 更新静态缓存状态,保证多线程可见
|
// 3. 更新静态缓存状态,保证多线程可见
|
||||||
sIsCharging = currentCharging;
|
sIsCharging = currentCharging;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package cc.winboll.studio.powerbell.services;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.powerbell.models.TTSSpeakTextBean;
|
||||||
|
import cc.winboll.studio.powerbell.utils.TextToSpeechUtils;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTS 语音播放后台服务组件
|
||||||
|
* 适配:Java7 语法规范 | Android API30 系统版本
|
||||||
|
* 功能:后台承载TTS语音播放,解耦页面生命周期,避免页面销毁中断播放
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/12/29 19:12
|
||||||
|
*/
|
||||||
|
public class TTSPlayService extends Service {
|
||||||
|
|
||||||
|
// ====================================== 常量区 - 静态全局常量 置顶排序 ======================================
|
||||||
|
public static final String TAG = "TTSPlayService";
|
||||||
|
public static final String EXTRA_SPEAKDATA = "EXTRA_SPEAKDATA";
|
||||||
|
|
||||||
|
// ====================================== 对外公开静态快捷调用方法【新增核心】======================================
|
||||||
|
/**
|
||||||
|
* 公开静态方法:一键启动TTS播放服务,播放指定文本内容
|
||||||
|
* @param context 上下文对象
|
||||||
|
* @param speakText 需要播放的语音文本内容
|
||||||
|
*/
|
||||||
|
public static void startPlayTTS(Context context, String speakText) {
|
||||||
|
LogUtils.d(TAG, "【startPlayTTS】静态快捷调用方法 | 入参Context=" + context + " | 播放文本=" + speakText);
|
||||||
|
if (context != null && speakText != null && !speakText.isEmpty()) {
|
||||||
|
// 初始化播放数据集合
|
||||||
|
ArrayList<TTSSpeakTextBean> ttsBeanList = new ArrayList<>();
|
||||||
|
// 添加播放文本,延迟时间为0:无延迟立即播放
|
||||||
|
ttsBeanList.add(new TTSSpeakTextBean(0, speakText));
|
||||||
|
LogUtils.d(TAG, "【startPlayTTS】封装播放数据完成,创建启动服务意图");
|
||||||
|
|
||||||
|
// 创建意图并封装序列化参数
|
||||||
|
Intent intent = new Intent(context, TTSPlayService.class);
|
||||||
|
intent.putExtra(EXTRA_SPEAKDATA, ttsBeanList);
|
||||||
|
|
||||||
|
// 启动当前服务
|
||||||
|
context.startService(intent);
|
||||||
|
LogUtils.d(TAG, "【startPlayTTS】已调用startService,TTS播放服务启动成功");
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【startPlayTTS】上下文为空 或 播放文本为空/空字符串,跳过启动服务");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 生命周期方法 - 绑定服务 (无绑定逻辑) ======================================
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
LogUtils.d(TAG, "【onBind】服务绑定方法调用,入参Intent:" + intent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 生命周期方法 - 启动服务【核心方法】 ======================================
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】服务启动方法调用 | 入参Intent:" + intent + " | flags:" + flags + " | startId:" + startId);
|
||||||
|
// 解析播放数据并执行播放
|
||||||
|
if (intent != null) {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】Intent不为空,开始解析序列化播放数据");
|
||||||
|
ArrayList<TTSSpeakTextBean> listTTSSpeakTextBean = (ArrayList<TTSSpeakTextBean>) intent.getSerializableExtra(EXTRA_SPEAKDATA);
|
||||||
|
if (listTTSSpeakTextBean != null && listTTSSpeakTextBean.size() > 0) {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】解析播放数据成功,队列长度:" + listTTSSpeakTextBean.size() + ",调用TTS播放工具类");
|
||||||
|
TextToSpeechUtils.getInstance(this).speekTTSList(listTTSSpeakTextBean);
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】播放数据为空/长度0,跳过语音播放逻辑");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】Intent为空,无播放数据可解析");
|
||||||
|
}
|
||||||
|
// 返回默认值,保持原服务启动策略不变
|
||||||
|
int result = super.onStartCommand(intent, flags, startId);
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】方法执行完成,返回值:" + result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package cc.winboll.studio.powerbell.services;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.powerbell.models.AppConfigBean;
|
||||||
|
import cc.winboll.studio.powerbell.models.ThoughtfulServiceBean;
|
||||||
|
import cc.winboll.studio.powerbell.utils.AppConfigUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 智能电池服务(充电/放电状态处理)
|
||||||
|
* 适配:Java7 语法规范 | Android API30 系统版本
|
||||||
|
* 功能:接收充电/放电状态指令,根据不同状态执行对应业务任务
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/12/29 19:29
|
||||||
|
*/
|
||||||
|
public class ThoughtfulService extends Service {
|
||||||
|
|
||||||
|
// ====================================== 常量区 - 置顶排序 ======================================
|
||||||
|
public static final String TAG = "ThoughtfulService";
|
||||||
|
/** Intent传递 服务类型 的Key值 */
|
||||||
|
public static final String EXTRA_SERVICE_TYPE = "EXTRA_SERVICE_TYPE";
|
||||||
|
|
||||||
|
// ====================================== 枚举类 - 服务类型 充电/放电状态 ======================================
|
||||||
|
/**
|
||||||
|
* 服务执行类型枚举
|
||||||
|
* CHARGE_STATE : 充电状态服务
|
||||||
|
* DISCHARGE_STATE : 放电(耗电)状态服务
|
||||||
|
*/
|
||||||
|
public enum ServiceType {
|
||||||
|
CHARGE_STATE, //充电状态服务
|
||||||
|
DISCHARGE_STATE //放电状态服务
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 对外公开静态启动函数【新增核心】入参Context + 枚举 ======================================
|
||||||
|
/**
|
||||||
|
* 公开静态方法:传入上下文+服务类型枚举,一键构建意图并启动当前服务
|
||||||
|
* @param context 上下文对象
|
||||||
|
* @param serviceType 服务类型枚举【充电/放电】
|
||||||
|
*/
|
||||||
|
public static void startServiceWithType(Context context, ServiceType serviceType) {
|
||||||
|
LogUtils.d(TAG, "【startServiceWithType】静态启动方法调用 | Context=" + context + " | ServiceType=" + (serviceType == null ? "null" : serviceType.name()));
|
||||||
|
|
||||||
|
ThoughtfulServiceBean thoughtfulServiceBean = ThoughtfulServiceBean.loadBean(context, ThoughtfulServiceBean.class);
|
||||||
|
if (thoughtfulServiceBean == null) {
|
||||||
|
thoughtfulServiceBean = new ThoughtfulServiceBean();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应TTS服务提醒没有启用,就退出
|
||||||
|
if((serviceType == ServiceType.CHARGE_STATE && !thoughtfulServiceBean.isEnableChargeTts())
|
||||||
|
||(serviceType == ServiceType.DISCHARGE_STATE && !thoughtfulServiceBean.isEnableUsePowerTts())){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 判空健壮性校验
|
||||||
|
if (context != null && serviceType != null) {
|
||||||
|
// 构建意图 + 封装枚举参数
|
||||||
|
Intent intent = new Intent(context, ThoughtfulService.class);
|
||||||
|
intent.putExtra(EXTRA_SERVICE_TYPE, serviceType);
|
||||||
|
// 启动服务
|
||||||
|
context.startService(intent);
|
||||||
|
LogUtils.d(TAG, "【startServiceWithType】服务启动成功,执行[" + serviceType.name() + "]任务");
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【startServiceWithType】上下文为空 或 服务类型枚举为空,跳过启动服务");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 生命周期方法 - 绑定服务 (原逻辑保留) ======================================
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
LogUtils.d(TAG, "【onBind】服务绑定方法调用,入参Intent:" + intent);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 生命周期方法 - 启动服务【核心逻辑】接收枚举+分支执行任务 ======================================
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】服务启动方法调用 | intent=" + intent + " | flags=" + flags + " | startId=" + startId);
|
||||||
|
// 判断意图非空,解析服务类型参数
|
||||||
|
if (intent != null) {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】Intent不为空,开始解析服务类型枚举参数");
|
||||||
|
// 获取传递的服务类型枚举
|
||||||
|
ServiceType serviceType = (ServiceType) intent.getSerializableExtra(EXTRA_SERVICE_TYPE);
|
||||||
|
// 根据服务类型,执行对应任务
|
||||||
|
if (serviceType != null) {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】解析到服务类型:" + serviceType.name());
|
||||||
|
switch (serviceType) {
|
||||||
|
case CHARGE_STATE:
|
||||||
|
// 执行【充电状态】对应的业务任务
|
||||||
|
executeChargeStateTask();
|
||||||
|
break;
|
||||||
|
case DISCHARGE_STATE:
|
||||||
|
// 执行【放电状态】对应的业务任务
|
||||||
|
executeDischargeStateTask();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】未知的服务类型,不执行任何任务");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】未解析到有效服务类型参数,参数为空");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】启动服务的Intent为空,直接返回");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回默认策略,与原生逻辑一致
|
||||||
|
int result = super.onStartCommand(intent, flags, startId);
|
||||||
|
LogUtils.d(TAG, "【onStartCommand】服务执行完成,返回值:" + result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 私有业务方法 充电/放电 分任务执行 ======================================
|
||||||
|
/**
|
||||||
|
* 执行【充电状态】的业务任务
|
||||||
|
* 可在此方法内编写 充电时的逻辑(语音提醒/电量监控/弹窗等)
|
||||||
|
*/
|
||||||
|
private void executeChargeStateTask() {
|
||||||
|
LogUtils.d(TAG, "【executeChargeStateTask】执行【充电状态】业务任务 >>> ");
|
||||||
|
//ToastUtils.show("【executeChargeStateTask】执行【充电状态】业务任务 >>> ");
|
||||||
|
// TODO 此处添加充电状态需要执行的业务逻辑代码
|
||||||
|
// 加载最新配置
|
||||||
|
AppConfigBean latestConfig = AppConfigUtils.getInstance(this).loadAppConfig();
|
||||||
|
if (latestConfig == null) {
|
||||||
|
LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 终止 | 最新配置为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestConfig.isEnableChargeReminder()) {
|
||||||
|
int nChargeReminderValue = latestConfig.getChargeReminderValue();
|
||||||
|
String szRemind = String.format("限量充电提醒已启用,限量值为百分之%d。", nChargeReminderValue);
|
||||||
|
szRemind = szRemind + szRemind + szRemind;
|
||||||
|
TTSPlayService.startPlayTTS(this, szRemind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行【放电(耗电)状态】的业务任务
|
||||||
|
* 可在此方法内编写 放电时的逻辑(语音提醒/电量监控/弹窗等)
|
||||||
|
*/
|
||||||
|
private void executeDischargeStateTask() {
|
||||||
|
LogUtils.d(TAG, "【executeDischargeStateTask】执行【放电状态】业务任务 >>> ");
|
||||||
|
//ToastUtils.show("【executeDischargeStateTask】执行【放电状态】业务任务 >>> ");
|
||||||
|
// TODO 此处添加放电状态需要执行的业务逻辑代码
|
||||||
|
// 加载最新配置
|
||||||
|
AppConfigBean latestConfig = AppConfigUtils.getInstance(this).loadAppConfig();
|
||||||
|
if (latestConfig == null) {
|
||||||
|
LogUtils.e(TAG, "handleNotifyAppConfigUpdate() 终止 | 最新配置为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestConfig.isEnableUsageReminder()) {
|
||||||
|
int nUsageReminderValue = latestConfig.getUsageReminderValue();
|
||||||
|
String szRemind = String.format("电量不足提醒已启用,低电值为百分之%d。", nUsageReminderValue);
|
||||||
|
//szRemind = szRemind + szRemind + szRemind;
|
||||||
|
TTSPlayService.startPlayTTS(this, szRemind);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
package cc.winboll.studio.powerbell.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.PixelFormat;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.speech.tts.TextToSpeech;
|
||||||
|
import android.speech.tts.UtteranceProgressListener;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.powerbell.R;
|
||||||
|
import cc.winboll.studio.powerbell.models.TTSSpeakTextBean;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TTS语音播放工具类 (单例实现)
|
||||||
|
* 适配:Java7 语法规范 | Android API36 系统版本【修复崩溃】
|
||||||
|
* 功能:队列播放语音文本 + 播放悬浮窗展示 + 点击悬浮窗停止播放/关闭悬浮窗
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/12/29 19:03
|
||||||
|
*/
|
||||||
|
public class TextToSpeechUtils {
|
||||||
|
|
||||||
|
// ====================================== 常量区 - 静态全局常量 (置顶) ======================================
|
||||||
|
public static final String TAG = "TextToSpeechUtils";
|
||||||
|
public static final String UNIQUE_ID = "UNIQUE_ID";
|
||||||
|
|
||||||
|
// ====================================== 单例实例 - 静态私有 (饿汉式优化) ======================================
|
||||||
|
private static volatile TextToSpeechUtils sTextToSpeechUtils;
|
||||||
|
|
||||||
|
// ====================================== 成员属性区 - 私有成员变量 (按功能归类 有序排列) ======================================
|
||||||
|
private Context mContext;
|
||||||
|
private WindowManager mWindowManager;
|
||||||
|
private TextToSpeech mTextToSpeech;
|
||||||
|
private View mView;
|
||||||
|
private volatile boolean isExist = false;
|
||||||
|
private UtteranceProgressListener mUtteranceProgressListener;
|
||||||
|
|
||||||
|
// ====================================== 构造方法 - 私有私有化 (单例模式) ======================================
|
||||||
|
private TextToSpeechUtils(Context context) {
|
||||||
|
LogUtils.d(TAG, "【构造方法】初始化TextToSpeechUtil实例");
|
||||||
|
this.mContext = context.getApplicationContext();
|
||||||
|
this.mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
|
||||||
|
this.initUtteranceProgressListener();
|
||||||
|
LogUtils.d(TAG, "【构造方法】初始化完成,获取WindowManager实例:"+mWindowManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 对外暴露方法 - 单例获取入口 (线程安全) ======================================
|
||||||
|
public static synchronized TextToSpeechUtils getInstance(Context context) {
|
||||||
|
LogUtils.d(TAG, "【getInstance】获取单例实例,入参Context:" + context);
|
||||||
|
if (sTextToSpeechUtils == null) {
|
||||||
|
LogUtils.d(TAG, "【getInstance】实例为空,创建新的TextToSpeechUtil对象");
|
||||||
|
sTextToSpeechUtils = new TextToSpeechUtils(context);
|
||||||
|
}
|
||||||
|
return sTextToSpeechUtils;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 核心对外业务方法 - 播放TTS语音队列 【主入口】 ======================================
|
||||||
|
public void speekTTSList(final ArrayList<TTSSpeakTextBean> listTTSSpeakTextBean) {
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】播放语音队列调用,入参队列长度:" + (listTTSSpeakTextBean == null ? 0 : listTTSSpeakTextBean.size()));
|
||||||
|
// 重置播放退出标志位
|
||||||
|
isExist = false;
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】重置播放退出标志位 isExist = " + isExist);
|
||||||
|
|
||||||
|
// TTS实例为空 → 初始化TTS后重放
|
||||||
|
if (mTextToSpeech == null) {
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】TextToSpeech实例为空,开始初始化TTS");
|
||||||
|
mTextToSpeech = new TextToSpeech(mContext, new TextToSpeech.OnInitListener() {
|
||||||
|
@Override
|
||||||
|
public void onInit(int initStatus) {
|
||||||
|
LogUtils.d(TAG, "【onInit】TTS初始化回调,初始化状态码:" + initStatus);
|
||||||
|
if (initStatus == TextToSpeech.SUCCESS) {
|
||||||
|
LogUtils.d(TAG, "【onInit】TTS初始化成功,重新调用语音播放方法");
|
||||||
|
speekTTSList(listTTSSpeakTextBean);
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【onInit】TTS init failed : " + initStatus + ". The app [https://play.google.com/store/apps/details?id=com.google.android.tts] maybe fix this TTS probrem. ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mTextToSpeech.setOnUtteranceProgressListener(mUtteranceProgressListener);
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】已为TTS绑定播放进度监听器");
|
||||||
|
} else {
|
||||||
|
// TTS实例就绪 → 执行播放逻辑
|
||||||
|
if (listTTSSpeakTextBean != null && listTTSSpeakTextBean.size() > 0) {
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】TTS实例就绪,语音队列数据有效,开始播放逻辑处理");
|
||||||
|
// 清理过期的悬浮窗 - 防止内存泄漏/重复添加
|
||||||
|
clearFloatWindow();
|
||||||
|
|
||||||
|
// ========== 修复1:添加悬浮窗权限检查,有权限才初始化悬浮窗,无权限则只播语音不崩溃 ==========
|
||||||
|
if (checkOverlayPermission()) {
|
||||||
|
initWindow();
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】悬浮窗初始化并显示完成");
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】悬浮窗权限未授予,跳过悬浮窗显示,仅播放语音");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一条语音的延迟时间并休眠
|
||||||
|
int nDelay = listTTSSpeakTextBean.get(0).mnDelay;
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】获取播放延迟时间:" + nDelay + "ms,开始休眠等待");
|
||||||
|
try {
|
||||||
|
Thread.sleep(nDelay);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】休眠等待被中断", e);
|
||||||
|
}
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】休眠等待完成,开始循环播放语音队列");
|
||||||
|
|
||||||
|
// 循环播放语音队列
|
||||||
|
for (int speakPosition = 0; speakPosition < listTTSSpeakTextBean.size() && !isExist; speakPosition++) {
|
||||||
|
String szSpeakContent = listTTSSpeakTextBean.get(speakPosition).mszSpeakContent;
|
||||||
|
isExist = (listTTSSpeakTextBean.size() - 2 < speakPosition);
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】播放索引:" + speakPosition + " | 播放文本:" + szSpeakContent + " | 当前退出标记位:" + isExist);
|
||||||
|
|
||||||
|
// 第一条语音清空队列播放,后续语音追加播放
|
||||||
|
if (speakPosition == 0) {
|
||||||
|
mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_FLUSH, null, UNIQUE_ID);
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】执行清空队列播放 → QUEUE_FLUSH");
|
||||||
|
} else {
|
||||||
|
mTextToSpeech.speak(szSpeakContent, TextToSpeech.QUEUE_ADD, null, UNIQUE_ID);
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】执行追加队列播放 → QUEUE_ADD");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】语音队列循环播放逻辑执行完毕");
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【speekTTSList】语音队列为空/长度0,跳过播放逻辑");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 私有工具方法 - 初始化播放监听器 ======================================
|
||||||
|
private void initUtteranceProgressListener() {
|
||||||
|
LogUtils.d(TAG, "【initUtteranceProgressListener】初始化TTS播放进度监听器");
|
||||||
|
mUtteranceProgressListener = new UtteranceProgressListener() {
|
||||||
|
@Override
|
||||||
|
public void onStart(String utteranceId) {
|
||||||
|
LogUtils.d(TAG, "【onStart】TTS语音播放开始,唯一标识ID:" + utteranceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDone(String utteranceId) {
|
||||||
|
LogUtils.d(TAG, "【onDone】TTS语音播放结束,唯一标识ID:" + utteranceId + " | 退出标志位:" + isExist);
|
||||||
|
// 播放完成 关闭悬浮窗
|
||||||
|
if (isExist && mWindowManager != null && mView != null) {
|
||||||
|
LogUtils.d(TAG, "【onDone】满足关闭条件,执行悬浮窗移除操作");
|
||||||
|
clearFloatWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(String utteranceId) {
|
||||||
|
LogUtils.d(TAG, "【onError】TTS语音播放出错,唯一标识ID:" + utteranceId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 私有核心方法 - 初始化并添加悬浮窗 【核心修复 根治崩溃】 ======================================
|
||||||
|
private void initWindow() {
|
||||||
|
LogUtils.d(TAG, "【initWindow】开始初始化播放悬浮窗");
|
||||||
|
// 创建Window布局参数
|
||||||
|
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
|
||||||
|
// ========== 修复2 重中之重:Android 12(API31)+ 彻底废弃TYPE_PHONE,统一用TYPE_APPLICATION_OVERLAY 适配API36 ==========
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||||
|
LogUtils.d(TAG, "【initWindow】系统版本>=API26,悬浮窗类型:TYPE_APPLICATION_OVERLAY");
|
||||||
|
} else {
|
||||||
|
// 仅低版本用TYPE_PHONE,高版本不再走这里
|
||||||
|
params.type = WindowManager.LayoutParams.TYPE_PHONE;
|
||||||
|
LogUtils.d(TAG, "【initWindow】系统版本<API26,悬浮窗类型:TYPE_PHONE");
|
||||||
|
}
|
||||||
|
// 悬浮窗样式配置
|
||||||
|
params.alpha = 0.9f;
|
||||||
|
params.gravity = Gravity.RIGHT | Gravity.BOTTOM;
|
||||||
|
params.x = 20;
|
||||||
|
params.y = 20;
|
||||||
|
params.format = PixelFormat.RGBA_8888;
|
||||||
|
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
|
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
|
||||||
|
// 核心Flag:无焦点+不阻塞触摸事件穿透
|
||||||
|
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||||
|
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
|
||||||
|
|
||||||
|
// 加载悬浮窗布局
|
||||||
|
mView = View.inflate(mContext, R.layout.view_tts_back, null);
|
||||||
|
LinearLayout llMain = mView.findViewById(R.id.viewttsbackLinearLayout1);
|
||||||
|
llMain.setOnClickListener(new View.OnClickListener(){
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
LogUtils.d(TAG, "【onClick】悬浮窗被点击,执行停止播放+关闭悬浮窗");
|
||||||
|
isExist = true;
|
||||||
|
if (mTextToSpeech != null) {
|
||||||
|
mTextToSpeech.stop();
|
||||||
|
LogUtils.d(TAG, "【onClick】已调用TTS.stop()停止播放");
|
||||||
|
}
|
||||||
|
clearFloatWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// ========== 修复3:添加异常捕获+重复添加判断,防止addView时报错导致整个TTS播放崩溃 ==========
|
||||||
|
try {
|
||||||
|
if (mWindowManager != null && mView != null && mView.getWindowToken() == null) {
|
||||||
|
mWindowManager.addView(mView, params);
|
||||||
|
LogUtils.d(TAG, "【initWindow】悬浮窗添加到Window成功,布局加载完成");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, "【initWindow】悬浮窗添加异常(权限/系统限制),不影响语音播放:", e);
|
||||||
|
mView = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 私有工具方法 - 清理悬浮窗 (通用封装) 【优化修复】 ======================================
|
||||||
|
private void clearFloatWindow() {
|
||||||
|
LogUtils.d(TAG, "【clearFloatWindow】执行悬浮窗清理操作");
|
||||||
|
if (mWindowManager != null && mView != null) {
|
||||||
|
try {
|
||||||
|
mWindowManager.removeView(mView);
|
||||||
|
LogUtils.d(TAG, "【clearFloatWindow】悬浮窗移除成功");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.d(TAG, "【clearFloatWindow】悬浮窗移除异常", e);
|
||||||
|
} finally {
|
||||||
|
mView = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LogUtils.d(TAG, "【clearFloatWindow】无需清理,WindowManager或View为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== ✅ 新增:悬浮窗权限检查【必须】Android6.0+ 强制校验 防止崩溃 ✅ ======================================
|
||||||
|
private boolean checkOverlayPermission() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
boolean hasPermission = android.provider.Settings.canDrawOverlays(mContext);
|
||||||
|
LogUtils.d(TAG, "【checkOverlayPermission】Android6.0+ 悬浮窗权限校验结果:" + hasPermission);
|
||||||
|
return hasPermission;
|
||||||
|
} else {
|
||||||
|
// 低版本默认有权限
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== ✅ 新增:释放资源方法【根治内存泄漏】建议在Service/Activity销毁时调用 ✅ ======================================
|
||||||
|
public void release() {
|
||||||
|
LogUtils.d(TAG, "【release】释放TTS资源和悬浮窗");
|
||||||
|
clearFloatWindow();
|
||||||
|
if (mTextToSpeech != null) {
|
||||||
|
mTextToSpeech.stop();
|
||||||
|
mTextToSpeech.shutdown();
|
||||||
|
mTextToSpeech = null;
|
||||||
|
}
|
||||||
|
sTextToSpeechUtils = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
41
powerbell/src/main/res/drawable/bg_frame_white.xml
Normal file
41
powerbell/src/main/res/drawable/bg_frame_white.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<!-- 阴影部分 -->
|
||||||
|
<!-- 个人觉得更形象的表达:top代表下边的阴影高度,left代表右边的阴影宽度。其实也就是相对应的offset,solid中的颜色是阴影的颜色,也可以设置角度等等 -->
|
||||||
|
<item
|
||||||
|
android:left="2dp"
|
||||||
|
android:top="2dp"
|
||||||
|
android:right="2dp"
|
||||||
|
android:bottom="2dp">
|
||||||
|
<shape android:shape="rectangle" >
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="#0FFFFFFF"
|
||||||
|
android:startColor="#0FFFFFFF" />
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="6dip"
|
||||||
|
android:bottomRightRadius="6dip"
|
||||||
|
android:topLeftRadius="6dip"
|
||||||
|
android:topRightRadius="6dip" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<!-- 背景部分 -->
|
||||||
|
<!-- 形象的表达:bottom代表背景部分在上边缘超出阴影的高度,right代表背景部分在左边超出阴影的宽度(相对应的offset) -->
|
||||||
|
<item
|
||||||
|
android:left="3dp"
|
||||||
|
android:top="3dp"
|
||||||
|
android:right="3dp"
|
||||||
|
android:bottom="5dp">
|
||||||
|
<shape android:shape="rectangle" >
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="#CFFFFFFF"
|
||||||
|
android:startColor="#CFFFFFFF" />
|
||||||
|
<corners
|
||||||
|
android:bottomLeftRadius="6dip"
|
||||||
|
android:bottomRightRadius="6dip"
|
||||||
|
android:topLeftRadius="6dip"
|
||||||
|
android:topRightRadius="6dip" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
11
powerbell/src/main/res/drawable/speaker.xml
Normal file
11
powerbell/src/main/res/drawable/speaker.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#ff000000"
|
||||||
|
android:pathData="M14,3.23V5.29C16.89,6.15 19,8.83 19,12C19,15.17 16.89,17.84 14,18.7V20.77C18,19.86 21,16.28 21,12C21,7.72 18,4.14 14,3.23M16.5,12C16.5,10.23 15.5,8.71 14,7.97V16C15.5,15.29 16.5,13.76 16.5,12M3,9V15H7L12,20V4L7,9H3Z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
@@ -13,40 +13,131 @@
|
|||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
style="@style/DefaultAToolbar"/>
|
style="@style/DefaultAToolbar"/>
|
||||||
|
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="right">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="CheckPermission"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:onClick="onCheckPermission"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<cc.winboll.studio.libaes.views.ADsControlView
|
|
||||||
android:id="@+id/ads_control_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1.0">
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
<cc.winboll.studio.powerbell.views.BatteryStyleView
|
<LinearLayout
|
||||||
android:id="@+id/battery_style_view"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content">
|
||||||
app:batteryPreviewColor="@color/colorPrimary"
|
|
||||||
app:previewBatteryValue="100"
|
|
||||||
app:defaultSelectedStyle="zebra_style"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="right">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_frame"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="TTS语音服务设置:"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="<仅限在切换充电状态时,发送的TTS语音提醒。用于提醒用户当前的服务设置状态。>"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="用电提醒启用时的TTS贴心服务"
|
||||||
|
android:onClick="onEnableUsePowerTts"
|
||||||
|
android:id="@+id/activitysettingsCheckBox1"/>
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="充电提醒启用时的TTS贴心服务"
|
||||||
|
android:onClick="onEnableChargeTts"
|
||||||
|
android:id="@+id/activitysettingsCheckBox2"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="right">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="检查TTS语音悬浮窗权限"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:onClick="onCheckTTSDrawOverlaysPermission"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/bg_frame"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="电量图表绘制风格设置:"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<cc.winboll.studio.powerbell.views.BatteryStyleView
|
||||||
|
android:id="@+id/battery_style_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1200dp"
|
||||||
|
app:batteryPreviewColor="@color/colorPrimary"
|
||||||
|
app:previewBatteryValue="100"
|
||||||
|
app:defaultSelectedStyle="zebra_style"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_frame"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="米盟广告SDK设置:"/>
|
||||||
|
|
||||||
|
<cc.winboll.studio.libaes.views.ADsControlView
|
||||||
|
android:id="@+id/ads_control_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
43
powerbell/src/main/res/layout/view_tts_back.xml
Normal file
43
powerbell/src/main/res/layout/view_tts_back.xml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@drawable/bg_frame_white"
|
||||||
|
android:layout_gravity="center_vertical|center_horizontal"
|
||||||
|
android:id="@+id/viewttsbackLinearLayout1">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="#FF000000"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:background="@drawable/speaker"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Click To Stop TTS Play"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="#FF000000"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
#Created by .winboll/winboll_app_build.gradle
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
#Sun Dec 07 03:42:32 HKT 2025
|
#Wed Dec 31 20:28:06 HKT 2025
|
||||||
stageCount=8
|
stageCount=9
|
||||||
libraryProject=
|
libraryProject=
|
||||||
baseVersion=15.11
|
baseVersion=15.11
|
||||||
publishVersion=15.11.7
|
publishVersion=15.11.8
|
||||||
buildCount=0
|
buildCount=0
|
||||||
baseBetaVersion=15.11.8
|
baseBetaVersion=15.11.9
|
||||||
|
|||||||
Reference in New Issue
Block a user