This commit is contained in:
101
.gitignore
vendored
Normal file
101
.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Built application files
|
||||||
|
*.apk
|
||||||
|
*.aar
|
||||||
|
*.ap_
|
||||||
|
*.aab
|
||||||
|
|
||||||
|
# Files for the ART/Dalvik VM
|
||||||
|
*.dex
|
||||||
|
|
||||||
|
# Java class files
|
||||||
|
*.class
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
bin/
|
||||||
|
gen/
|
||||||
|
out/
|
||||||
|
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||||
|
# release/
|
||||||
|
|
||||||
|
# Gradle files
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# Local configuration file (sdk path, etc)
|
||||||
|
local.properties
|
||||||
|
|
||||||
|
# Proguard folder generated by Eclipse
|
||||||
|
proguard/
|
||||||
|
|
||||||
|
# Log Files
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Android Studio Navigation editor temp files
|
||||||
|
.navigation/
|
||||||
|
|
||||||
|
# Android Studio captures folder
|
||||||
|
captures/
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
*.iml
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/tasks.xml
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
.idea/dictionaries
|
||||||
|
.idea/libraries
|
||||||
|
# Android Studio 3 in .gitignore file.
|
||||||
|
.idea/caches
|
||||||
|
.idea/modules.xml
|
||||||
|
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||||
|
.idea/navEditor.xml
|
||||||
|
|
||||||
|
# Keystore files
|
||||||
|
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||||
|
*.jks
|
||||||
|
*.keystore
|
||||||
|
|
||||||
|
# External native build folder generated in Android Studio 2.2 and later
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx/
|
||||||
|
|
||||||
|
# Google Services (e.g. APIs or Firebase)
|
||||||
|
# google-services.json
|
||||||
|
|
||||||
|
# Freeline
|
||||||
|
freeline.py
|
||||||
|
freeline/
|
||||||
|
freeline_project_description.json
|
||||||
|
|
||||||
|
# fastlane
|
||||||
|
fastlane/report.xml
|
||||||
|
fastlane/Preview.html
|
||||||
|
fastlane/screenshots
|
||||||
|
fastlane/test_output
|
||||||
|
fastlane/readme.md
|
||||||
|
|
||||||
|
# Version control
|
||||||
|
vcs.xml
|
||||||
|
|
||||||
|
# lint
|
||||||
|
lint/intermediates/
|
||||||
|
lint/generated/
|
||||||
|
lint/outputs/
|
||||||
|
lint/tmp/
|
||||||
|
# lint/reports/
|
||||||
|
|
||||||
|
# Android Profiling
|
||||||
|
*.hprof
|
||||||
|
|
||||||
|
# 忽略 Lint 输出文件
|
||||||
|
lint-results.xml
|
||||||
|
lint-results.html
|
||||||
|
|
||||||
|
## 忽略 AndroidIDE 临时文件夹
|
||||||
|
.androidide
|
||||||
|
|
||||||
|
## WinBoLL 基础应用(避免上传敏感配置)
|
||||||
|
/winboll.properties
|
||||||
|
/local.properties
|
||||||
|
/settings.gradle
|
||||||
|
/gradle.properties
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libjc/jcc/libs"]
|
||||||
|
path = libjc/jcc/libs
|
||||||
|
url = https://gitea.winboll.cc/Studio/APP_libjc_jcc_libs.git
|
||||||
18
.winboll/Readme.txt
Normal file
18
.winboll/Readme.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
## WinBoLL 主机编译事项提醒
|
||||||
|
|
||||||
|
## 类库类型源码发布
|
||||||
|
# 类库发布使用以下面命令
|
||||||
|
git pull && bash .winboll/bashPublishLIBAddTag.sh <类库模块文件夹名称>
|
||||||
|
|
||||||
|
## 纯应用类型源码发布
|
||||||
|
# 应用发布使用以下命令
|
||||||
|
git pull && bash .winboll/bashPublishAPKAddTag.sh <应用模块文件夹名称>
|
||||||
|
|
||||||
|
## 编译时提问。Add Github Workflows Tag? (yes/No)
|
||||||
|
回答yes: 将会添加一个 GitHub 工作流标签
|
||||||
|
GitHub 仓库会执行以该标签为标志的编译工作流。
|
||||||
|
回答No(默认): 就忽略 GitHub 标签,忽略 GitHub 工作流调用。
|
||||||
|
|
||||||
|
## Github Workflows 工作流设置注意事项
|
||||||
|
应用名称改变时需要修改.github/workflows/android.yml文件设置,
|
||||||
|
在第79行:asset_name: 处有应用包名称设置。
|
||||||
32
.winboll/bashCheckGitCommitStatus.sh
Normal file
32
.winboll/bashCheckGitCommitStatus.sh
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
# 使用 `-z` 命令检查变量是否为空
|
||||||
|
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||||
|
echo "Script parameter error: $0"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 进入项目根目录
|
||||||
|
cd ${1}
|
||||||
|
echo -e "Work dir : \n"`pwd`
|
||||||
|
|
||||||
|
git config --global --add safe.directory "${1}"
|
||||||
|
echo "Current dir : "`pwd`
|
||||||
|
versionName=${2}
|
||||||
|
|
||||||
|
## 设置要检查的标签
|
||||||
|
tag="v"${versionName}
|
||||||
|
|
||||||
|
## 如果Git已经提交了所有代码就执行标签检查操作
|
||||||
|
if [[ -n $(git diff --stat) ]]
|
||||||
|
then
|
||||||
|
echo 'Source is no commit git completely, tag action cancel.'
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo "Git status is clean."
|
||||||
|
if [ "$(git tag -l ${tag})" == "${tag}" ]; then
|
||||||
|
echo "Tag ${tag} exist."
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "${0}: Git tag is checked OK: (${tag})"
|
||||||
|
fi
|
||||||
17
.winboll/bashCommitAppPublishBuildFlagInfo.sh
Normal file
17
.winboll/bashCommitAppPublishBuildFlagInfo.sh
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
## 提交新的 APK 编译配置标志信息,并推送到Git仓库。
|
||||||
|
|
||||||
|
# 使用 `-z` 命令检查变量是否为空
|
||||||
|
if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] || [ -z "$4" ]; then
|
||||||
|
echo "$0 Script parameter error."
|
||||||
|
echo "(Script Demo : [ bashCommitAppPublishBuildFlagInfo.sh <RootProjectDir> <VersionName> <BuildType Name> <RootProject Name> ])"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 进入项目根目录
|
||||||
|
cd ${1}
|
||||||
|
echo -e "Work dir : \n"`pwd`
|
||||||
|
|
||||||
|
git add .
|
||||||
|
git commit -m "<$4>APK ${2} ${3} Publish."
|
||||||
|
git push origin && git push origin --tags
|
||||||
48
.winboll/bashCommitLibReleaseBuildFlagInfo.sh
Normal file
48
.winboll/bashCommitLibReleaseBuildFlagInfo.sh
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
## 提交新的 Library 编译配置标志信息,并推送到Git仓库。
|
||||||
|
|
||||||
|
# 检查是否指定了将要发布的类库名称
|
||||||
|
# 使用 `-z` 命令检查变量是否为空
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Library name error: $0"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
## 开始执行脚本
|
||||||
|
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 ..
|
||||||
|
fi
|
||||||
|
## 本脚本需要在项目根目录下执行
|
||||||
|
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 -e "Work dir error."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 就读取脚本 .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="v7.6.4-test1"
|
||||||
|
# 正式设置标签时使用
|
||||||
|
#tag="v"${PUBLISH_VERSION}
|
||||||
|
|
||||||
|
git add .
|
||||||
|
git commit -m "<$1>Library Release ${PUBLISH_VERSION}"
|
||||||
|
git push origin && git push origin --tags
|
||||||
223
.winboll/bashPublishAPKAddTag.sh
Normal file
223
.winboll/bashPublishAPKAddTag.sh
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# ==============================================================================
|
||||||
|
# WinBoLL 应用发布脚本
|
||||||
|
# 功能:检查Git源码状态 → 编译Stage Release包 → 添加WinBoLL标签 → 提交并推送源码
|
||||||
|
# 依赖:build.properties、app_update_description.txt(项目根目录下)
|
||||||
|
# 使用:./script_name.sh <APP_NAME>
|
||||||
|
# 作者:豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
# ==================== 常量定义 ====================
|
||||||
|
# 脚本退出码
|
||||||
|
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
|
||||||
|
echo "[ERROR] 未指定应用名称!使用方式:${0} <APP_NAME>"
|
||||||
|
exit ${EXIT_CODE_ERR_NO_APP_NAME}
|
||||||
|
fi
|
||||||
|
APP_NAME=$1
|
||||||
|
echo "[INFO] 待发布应用名称:${APP_NAME}"
|
||||||
|
|
||||||
|
# 2. 检查并切换到项目根目录(确保build.properties存在)
|
||||||
|
echo "[INFO] 当前工作目录:$(pwd)"
|
||||||
|
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
|
||||||
|
echo "[WARNING] 当前目录不存在${APP_NAME}/build.properties,尝试切换到上级目录..."
|
||||||
|
cd ..
|
||||||
|
echo "[INFO] 切换后工作目录:$(pwd)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 验证最终工作目录是否正确
|
||||||
|
if [[ ! -e "${APP_NAME}/build.properties" ]]; then
|
||||||
|
echo "[ERROR] 工作目录错误!${APP_NAME}/build.properties 文件不存在。"
|
||||||
|
exit ${EXIT_CODE_ERR_WORK_DIR}
|
||||||
|
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
|
||||||
|
echo "[ERROR] 源码与标签推送失败!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ==================== 主流程结束 ====================
|
||||||
|
echo "============================================="
|
||||||
|
echo " WinBoLL 应用发布完成!"
|
||||||
|
echo "============================================="
|
||||||
|
exit ${EXIT_CODE_SUCCESS}
|
||||||
|
|
||||||
166
.winboll/bashPublishDebugAPKAddTag.sh
Normal file
166
.winboll/bashPublishDebugAPKAddTag.sh
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
# 检查是否指定了将要发布的调试版应用名称
|
||||||
|
# 使用 `-z` 命令检查变量是否为空
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "No APP name specified : $0"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
## 定义相关函数
|
||||||
|
## 检查 Git 源码是否完全提交了,完全提交就返回0
|
||||||
|
function checkGitSources {
|
||||||
|
#local input="$1"
|
||||||
|
#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="v7.6.4-test1"
|
||||||
|
# 正式调试版设置标签时使用
|
||||||
|
tag=$1"-v"${PUBLISH_VERSION}"-debug"
|
||||||
|
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="v7.6.4-beta"
|
||||||
|
# 正式设置标签时使用
|
||||||
|
tag=$1"-"v"${BASE_BETA_VERSION}-beta-debug
|
||||||
|
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 ..
|
||||||
|
fi
|
||||||
|
## 本脚本需要在项目根目录下执行
|
||||||
|
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 -e "Work dir error."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查源码状态
|
||||||
|
result=$(checkGitSources)
|
||||||
|
if [[ $? -eq 0 ]]; then
|
||||||
|
echo $result
|
||||||
|
# 如果Git已经提交了所有代码就执行标签和应用发布操作
|
||||||
|
|
||||||
|
# 预先询问是否添加工作流标签
|
||||||
|
echo "Add Github Workflows Tag? (yes/no)"
|
||||||
|
result=$(askAddWorkflowsTag)
|
||||||
|
nAskAddWorkflowsTag=$?
|
||||||
|
echo $result
|
||||||
|
|
||||||
|
# 发布应用
|
||||||
|
echo "Publishing WinBoLL Debug APK ..."
|
||||||
|
# 脚本调试时使用
|
||||||
|
#bash gradlew :$1:assembleBetaDebug
|
||||||
|
# 正式发布调试版
|
||||||
|
bash gradlew :$1:assembleStageDebug
|
||||||
|
echo "Publishing WinBoLL Debug 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 Debug 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
|
||||||
14
.winboll/bashPublishLIBAddTag.sh
Normal file
14
.winboll/bashPublishLIBAddTag.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
|
||||||
|
# 检查是否指定了将要发布的类库名称
|
||||||
|
# 使用 `-z` 命令检查变量是否为空
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "No Library name specified : $0"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
## 正式发布使用
|
||||||
|
git pull && bash gradlew :$1:publishReleasePublicationToWinBoLLReleaseRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1
|
||||||
|
|
||||||
|
## 调试使用
|
||||||
|
#bash gradlew :$1:publishSnapshotWinBoLLPublicationToWinBoLLSnapshotRepository && bash .winboll/bashCommitLibReleaseBuildFlagInfo.sh $1
|
||||||
342
.winboll/winboll_app_build.gradle
Normal file
342
.winboll/winboll_app_build.gradle
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
// WinBoLL 应用签名配置
|
||||||
|
//
|
||||||
|
|
||||||
|
android {
|
||||||
|
def winbollProps = new Properties()
|
||||||
|
def winbollPropsFile = rootProject.file("${winbollFilePath}")
|
||||||
|
if(winbollPropsFile.exists()) {
|
||||||
|
winbollProps.load(new FileInputStream(winbollPropsFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取秘钥配置文件
|
||||||
|
//
|
||||||
|
def keyProps = new Properties()
|
||||||
|
def keyPropsFile = rootProject.file("${keyPropsFilePath}")
|
||||||
|
//println 'Test keystore path'
|
||||||
|
//println 'KeyProsFile :' + Boolean.toString(keyPropsFile.exists())
|
||||||
|
//assert(false)
|
||||||
|
if(keyPropsFile.exists()) {
|
||||||
|
keyProps.load(new FileInputStream(keyPropsFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 配置签名
|
||||||
|
signingConfigs {
|
||||||
|
if(keyPropsFile.exists()) {
|
||||||
|
winboll {
|
||||||
|
assert(keyProps['keyAlias'] != null && keyProps['keyPassword'] != null && keyProps['storeFile'] != null && keyProps['storePassword'] != null)
|
||||||
|
keyAlias keyProps['keyAlias']
|
||||||
|
keyPassword keyProps['keyPassword']
|
||||||
|
storeFile keyProps['storeFile'] ? file(keyProps['storeFile']) : null
|
||||||
|
storePassword keyProps['storePassword']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
if(keyPropsFile.exists()) {
|
||||||
|
signingConfig signingConfigs.winboll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
if(keyPropsFile.exists()) {
|
||||||
|
signingConfig signingConfigs.winboll
|
||||||
|
}
|
||||||
|
|
||||||
|
minifyEnabled true // 开启混淆(核心开关)
|
||||||
|
shrinkResources true // 可选:移除无用资源(进一步减小体积)
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // 官方默认规则(优化版)
|
||||||
|
'proguard-rules.pro' // 自定义规则文件
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
flavorDimensions "WinBoLLApp"
|
||||||
|
productFlavors {
|
||||||
|
beta {
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['buildCount'] != null)
|
||||||
|
dimension "WinBoLLApp"
|
||||||
|
applicationIdSuffix ".beta"
|
||||||
|
LocalDateTime localDateTimeNow = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
|
||||||
|
versionNameSuffix "-beta" + winbollBuildProps['buildCount'] + "_" + localDateTimeNow.format('mmss')
|
||||||
|
}
|
||||||
|
stage {
|
||||||
|
dimension "WinBoLLApp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_7
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_7
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用包输出配置
|
||||||
|
//
|
||||||
|
android.applicationVariants.all { variant ->
|
||||||
|
//
|
||||||
|
// GitHub 应用包输出配置
|
||||||
|
// 1. 配置 Beta Release 版应用包输出
|
||||||
|
// 注意 :GitHub 打包使用 android.yml 的 "bash ./gradlew assembleBetaRelease" 命令
|
||||||
|
//
|
||||||
|
if(variant.flavorName == "beta" && variant.buildType.name == "release") {
|
||||||
|
/* 后期管理预留代码 */
|
||||||
|
/* 暂时没有需要的 GitHub 应用包输出配置 */
|
||||||
|
/* GitHub 部分代码忽略 */
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// WinBoLL 应用包输出配置
|
||||||
|
// 1. 配置 Stage Release 版应用包输出
|
||||||
|
// 2. 配置 Beta Debug 版应用包输出
|
||||||
|
//
|
||||||
|
if((variant.flavorName == "beta" && variant.buildType.name == "debug")
|
||||||
|
|| (variant.flavorName == "beta" && variant.buildType.name == "release")
|
||||||
|
|| (variant.flavorName == "stage" && variant.buildType.name == "debug")
|
||||||
|
|| (variant.flavorName == "stage" && variant.buildType.name == "release")) {
|
||||||
|
println "Project root directory: " + project.rootDir.toString()
|
||||||
|
println "Project root directory name : " + project.rootDir.name
|
||||||
|
def outputPath="${project.projectDir.absolutePath}/build/outputs/apk/${variant.buildType.name}"
|
||||||
|
//def outputFileName="${rootProject.name}_${versionName}.apk"
|
||||||
|
def outputFileName=project.rootDir.name + "_${versionName}.apk"
|
||||||
|
|
||||||
|
// 创建 WinBoLL Studio 发布接口文件夹
|
||||||
|
File fWinBoLLStudioDir = file("/sdcard/WinBoLLStudio/APKs");
|
||||||
|
// 如果配置了APK接口文件夹路径,就设置应用APK输出文件夹为接口文件夹。
|
||||||
|
if(winbollProps != null && winbollProps['APKOutputPath'] != null ) {
|
||||||
|
fWinBoLLStudioDir = file(winbollProps['APKOutputPath']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!fWinBoLLStudioDir.exists()) {
|
||||||
|
println "[ WinBoLLStudio ] : " + fWinBoLLStudioDir.getAbsolutePath() + " Folder does not exist."
|
||||||
|
println '[ WinBoLLStudio ] : The APKOutputPath property is not defined in winboll.properties, please configure APK output folder first.'
|
||||||
|
} else {
|
||||||
|
/// WINBOLL 主机的 APK 发布和源码管理操作 ///
|
||||||
|
variant.getAssembleProvider().get().doFirst {
|
||||||
|
/* 后期管理预留代码 */
|
||||||
|
} //doFirst {
|
||||||
|
|
||||||
|
// 编译输出后处理文件部分
|
||||||
|
//
|
||||||
|
variant.getAssembleProvider().get().doLast {
|
||||||
|
variant.outputs.forEach{ file->
|
||||||
|
// 如果正在调试,就拷贝到 WinBoLL 备份管理文件夹
|
||||||
|
//
|
||||||
|
if(variant.flavorName == "beta"&&variant.buildType.name == "debug"){
|
||||||
|
//File outBuildBckDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/${variant.buildType.name}")
|
||||||
|
File outBuildBckDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}")
|
||||||
|
// 创建目标路径目录
|
||||||
|
if(!outBuildBckDir.exists()) {
|
||||||
|
outBuildBckDir.mkdirs();
|
||||||
|
println "Output Folder Created.(WinBoLLStudio) : " + outBuildBckDir.getAbsolutePath()
|
||||||
|
}
|
||||||
|
if(outBuildBckDir.exists()) {
|
||||||
|
copy{
|
||||||
|
from file.outputFile
|
||||||
|
into outBuildBckDir
|
||||||
|
rename {
|
||||||
|
String fileName -> "${outputFileName}"
|
||||||
|
}
|
||||||
|
println "Output APK (WinBoLLStudio): " + outBuildBckDir.getAbsolutePath() + "/${outputFileName}"
|
||||||
|
}
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['buildCount'] != null)
|
||||||
|
assert (winbollBuildProps['libraryProject'] != null)
|
||||||
|
//构建计数增加
|
||||||
|
int buildCount = Integer.parseInt(winbollBuildProps['buildCount']) + 1;
|
||||||
|
// 设置编译计数
|
||||||
|
winbollBuildProps.setProperty("buildCount", Integer.toString(buildCount));
|
||||||
|
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
println "\n\n>>> Project build.properties saved.\n\n";
|
||||||
|
|
||||||
|
if(winbollBuildProps['libraryProject'] != "") {
|
||||||
|
// 如果应用 build.properties 文件设置了类库模块项目文件名
|
||||||
|
// 就拷贝一份新的编译标志配置到类库项目文件夹
|
||||||
|
File libraryProjectBuildPropsFile = new File("$RootProjectDir/" + winbollBuildProps['libraryProject'] + "/build.properties")
|
||||||
|
assert(winbollBuildPropsFile.exists())
|
||||||
|
assert(libraryProjectBuildPropsFile.exists())
|
||||||
|
java.nio.file.Path sourceFilePath = winbollBuildPropsFile.toPath();
|
||||||
|
java.nio.file.Path targetFilePath = libraryProjectBuildPropsFile.toPath();
|
||||||
|
// 使用copyTo()方法复制文件,如果目标文件存在会被覆盖,可选参数可以选择不覆盖
|
||||||
|
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
|
||||||
|
println "\n\n>>> Library Project build.properties saved.\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果正在发布,就拷贝到 WinBoLL 标签管理文件夹,和处理 Git 仓库管理任务。
|
||||||
|
//
|
||||||
|
if(variant.flavorName == "stage"&&variant.buildType.name == "release"){
|
||||||
|
// 发布 APK 文件
|
||||||
|
//
|
||||||
|
// 截取版本号的版本字段为短版本名
|
||||||
|
String szVersionName = "${versionName}"
|
||||||
|
String[] szlistTemp = szVersionName.split("-")
|
||||||
|
String szShortVersionName = szlistTemp[0]
|
||||||
|
//String szCommonTagAPKName = "${rootProject.name}_" + szShortVersionName + ".apk"
|
||||||
|
String szCommonTagAPKName = project.rootDir.name + "_" + szShortVersionName + ".apk"
|
||||||
|
println "CommonTagAPKName is : " + szCommonTagAPKName
|
||||||
|
|
||||||
|
//File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/")
|
||||||
|
File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/tag/")
|
||||||
|
// 创建目标路径目录
|
||||||
|
if(!outTagDir.exists()) {
|
||||||
|
outTagDir.mkdirs();
|
||||||
|
println "Output Folder Created.(Tags) : " + outTagDir.getAbsolutePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outTagDir.exists()) {
|
||||||
|
File targetAPK = new File(outTagDir, "${szCommonTagAPKName}")
|
||||||
|
if(targetAPK.exists()) {
|
||||||
|
// 标签版本APK文件已经存在,构建拷贝任务停止
|
||||||
|
assert (!targetAPK.exists())
|
||||||
|
// 可选择删除并继续输出APK文件
|
||||||
|
//delete targetAPK
|
||||||
|
}
|
||||||
|
// 复制一个备份
|
||||||
|
copy{
|
||||||
|
from file.outputFile
|
||||||
|
into outTagDir
|
||||||
|
rename {
|
||||||
|
String fileName -> "${outputFileName}"
|
||||||
|
}
|
||||||
|
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${outputFileName}"
|
||||||
|
}
|
||||||
|
// 复制一个并重命名为短版本名
|
||||||
|
copy{
|
||||||
|
from file.outputFile
|
||||||
|
into outTagDir
|
||||||
|
rename {
|
||||||
|
String fileName -> "${szCommonTagAPKName}"
|
||||||
|
}
|
||||||
|
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${szCommonTagAPKName}"
|
||||||
|
}
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['publishVersion'] != null)
|
||||||
|
assert (winbollBuildProps['buildCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
assert (winbollBuildProps['baseBetaVersion'] != null)
|
||||||
|
assert (winbollBuildProps['libraryProject'] != null)
|
||||||
|
|
||||||
|
// 设置类库的默认版本名
|
||||||
|
winbollBuildProps.setProperty("publishVersion", "${versionName}");
|
||||||
|
// Stage 发布计数增加
|
||||||
|
int stageCount = Integer.parseInt(winbollBuildProps['stageCount']) + 1;
|
||||||
|
winbollBuildProps.setProperty("stageCount", Integer.toString(stageCount));
|
||||||
|
// 设置类库的默认Beta版本名
|
||||||
|
winbollBuildProps.setProperty("baseBetaVersion", winbollBuildProps['baseVersion'] + "." + Integer.toString(stageCount));
|
||||||
|
// 构建计数重置
|
||||||
|
winbollBuildProps.setProperty("buildCount", "0");
|
||||||
|
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
if(winbollBuildProps['libraryProject'] != "") {
|
||||||
|
// 如果应用 build.properties 文件设置了类库模块项目文件名
|
||||||
|
// 就拷贝一份新的编译标志配置到类库项目文件夹
|
||||||
|
File libraryProjectBuildPropsFile = new File("$RootProjectDir/" + winbollBuildProps['libraryProject'] + "/build.properties")
|
||||||
|
assert(winbollBuildPropsFile.exists())
|
||||||
|
assert(libraryProjectBuildPropsFile.exists())
|
||||||
|
java.nio.file.Path sourceFilePath = winbollBuildPropsFile.toPath();
|
||||||
|
java.nio.file.Path targetFilePath = libraryProjectBuildPropsFile.toPath();
|
||||||
|
// 使用copyTo()方法复制文件,如果目标文件存在会被覆盖,可选参数可以选择不覆盖
|
||||||
|
java.nio.file.Files.copy(sourceFilePath, targetFilePath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交新的编译标志配置
|
||||||
|
println 'exec bashCommitAppPublishBuildFlagInfoFilePath ...'
|
||||||
|
def resultCommitBuildFlag = exec {
|
||||||
|
commandLine 'bash', '--', "${RootProjectDir}/${bashCommitAppPublishBuildFlagInfoFilePath}", "${RootProjectDir}", "${versionName}", variant.buildType.name , rootProject.name
|
||||||
|
}
|
||||||
|
// 检查bash命令的返回值(假设非零表示失败)
|
||||||
|
assert(resultCommitBuildFlag.getExitValue() == 0)
|
||||||
|
}
|
||||||
|
} // if(variant.buildType.name == "release"){
|
||||||
|
|
||||||
|
// 如果正在调试发布版,就只生成和输出APK文件,不处理 Git 仓库提交与更新问题。
|
||||||
|
//
|
||||||
|
if(variant.flavorName == "stage"&&variant.buildType.name == "debug"){
|
||||||
|
// 发布 APK 文件
|
||||||
|
//
|
||||||
|
// 截取版本号的版本字段为短版本名
|
||||||
|
String szVersionName = "${versionName}"
|
||||||
|
String[] szlistTemp = szVersionName.split("-")
|
||||||
|
String szShortVersionName = szlistTemp[0]
|
||||||
|
//String szCommonTagAPKName = "${rootProject.name}_" + szShortVersionName + ".apk"
|
||||||
|
String szCommonTagAPKName = project.rootDir.name + "_" + szShortVersionName + ".apk"
|
||||||
|
println "CommonTagAPKName is : " + szCommonTagAPKName
|
||||||
|
|
||||||
|
//File outTagDir = new File(fWinBoLLStudioDir, "/${rootProject.name}/tag/")
|
||||||
|
File outTagDir = new File(fWinBoLLStudioDir, "/" + project.rootDir.name + "/${variant.buildType.name}/")
|
||||||
|
// 创建目标路径目录
|
||||||
|
if(!outTagDir.exists()) {
|
||||||
|
outTagDir.mkdirs();
|
||||||
|
println "Output Folder Created.(Tags) : " + outTagDir.getAbsolutePath()
|
||||||
|
}
|
||||||
|
|
||||||
|
if(outTagDir.exists()) {
|
||||||
|
File targetAPK = new File(outTagDir, "${szCommonTagAPKName}")
|
||||||
|
if(targetAPK.exists()) {
|
||||||
|
// 标签版本APK文件已经存在,构建拷贝任务停止
|
||||||
|
println '如果是在调试 Stage 版应用包构建,请删除(注:在debug目录)现有的 Stage 应用包('+targetAPK.getAbsolutePath()+')。再编译一次。'
|
||||||
|
assert (!targetAPK.exists())
|
||||||
|
// 可选择删除并继续输出APK文件
|
||||||
|
//delete targetAPK
|
||||||
|
}
|
||||||
|
// 复制一个备份
|
||||||
|
copy{
|
||||||
|
from file.outputFile
|
||||||
|
into outTagDir
|
||||||
|
rename {
|
||||||
|
String fileName -> "${outputFileName}"
|
||||||
|
}
|
||||||
|
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${outputFileName}"
|
||||||
|
}
|
||||||
|
// 复制一个并重命名为短版本名
|
||||||
|
copy{
|
||||||
|
from file.outputFile
|
||||||
|
into outTagDir
|
||||||
|
rename {
|
||||||
|
String fileName -> "${szCommonTagAPKName}"
|
||||||
|
}
|
||||||
|
println "Output APK (Tags): "+ outTagDir.getAbsolutePath() + "/${szCommonTagAPKName}"
|
||||||
|
}
|
||||||
|
|
||||||
|
//不保存编译标志配置
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果配置了APK额外输出路径,就复制一份拷贝到额外路径。
|
||||||
|
//
|
||||||
|
if(winbollProps != null && winbollProps['ExtraAPKOutputPath'] != null ) {
|
||||||
|
File apkFile = new File(winbollProps['ExtraAPKOutputPath'])
|
||||||
|
File outCommonDir = apkFile.getParentFile();
|
||||||
|
String commandAPKName = apkFile.getName();
|
||||||
|
if(outCommonDir.exists()) {
|
||||||
|
copy{
|
||||||
|
from file.outputFile
|
||||||
|
into outCommonDir
|
||||||
|
rename {
|
||||||
|
String fileName -> "${commandAPKName}"
|
||||||
|
}
|
||||||
|
println "Output APK (Common): " + outCommonDir.getAbsolutePath() + "/${commandAPKName}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}// End of (variant.getAssembleProvider().get().doLast {)
|
||||||
|
}/// WINBOLL 主机的 APK 发布和源码管理操作结束 ///
|
||||||
|
}
|
||||||
|
|
||||||
|
} // End of (android.applicationVariants.all { variant ->)
|
||||||
|
}
|
||||||
|
|
||||||
211
.winboll/winboll_lib_build.gradle
Normal file
211
.winboll/winboll_lib_build.gradle
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
// 本机和 WinBoLL Maven 仓库传输配置。
|
||||||
|
//
|
||||||
|
|
||||||
|
def getDefaultVersion(){
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['publishVersion'] != null)
|
||||||
|
// 返回编译版本号
|
||||||
|
return winbollBuildProps['publishVersion']
|
||||||
|
}
|
||||||
|
|
||||||
|
def siteUrl = 'https://winboll.cc/?page=studio/details.php&app=${rootProject.name}' // 项目主页
|
||||||
|
def gitUrl = 'https://gitea.winboll.cc/WinBoLL/${rootProject.name}' // 项目的git地址
|
||||||
|
def DefaultGroupId = 'cc.winboll.studio' // 类库所有者groupId
|
||||||
|
def DefaultVersion = getDefaultVersion() // 版本号
|
||||||
|
def DeveloperId='zhangsken' // 开发者账号
|
||||||
|
def DeveloperName='ZhanGSKen' // 开发者名称
|
||||||
|
def DeveloperEMail='zhangsken@188.com' // 开发者邮箱地址
|
||||||
|
def LicenseName='The Apache Software License, Version 2.0'
|
||||||
|
def LicenseUrl='http://www.apache.org/licenses/LICENSE-2.0.txt'
|
||||||
|
|
||||||
|
Properties properties = new Properties()
|
||||||
|
|
||||||
|
afterEvaluate {
|
||||||
|
publishing {
|
||||||
|
repositories {
|
||||||
|
if(file("${RootProjectDir}/${winbollFilePath}").exists()) {
|
||||||
|
properties.load(file("${RootProjectDir}/${winbollFilePath}").newDataInputStream())
|
||||||
|
def NexusUserName = properties.getProperty("Nexus.name")
|
||||||
|
def NexusPassword = properties.getProperty("Nexus.password")
|
||||||
|
// WinBoLL Release 仓库
|
||||||
|
maven{
|
||||||
|
//仓库的名字和地址
|
||||||
|
name = "WinBoLLRelease"
|
||||||
|
url="https://nexus.winboll.cc/repository/maven-releases/"
|
||||||
|
// 仓库用户名密码
|
||||||
|
credentials {
|
||||||
|
username = NexusUserName
|
||||||
|
password = NexusPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// WinBoLL Snapshot 仓库
|
||||||
|
maven{
|
||||||
|
//仓库的名字和地址
|
||||||
|
name = "WinBoLLSnapshot"
|
||||||
|
url="https://nexus.winboll.cc/repository/maven-snapshots/"
|
||||||
|
// 仓库用户名密码
|
||||||
|
credentials {
|
||||||
|
username = NexusUserName
|
||||||
|
password = NexusPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publications {
|
||||||
|
// Local Maven 仓库传输任务
|
||||||
|
//
|
||||||
|
release(MavenPublication) {
|
||||||
|
groupId = DefaultGroupId
|
||||||
|
artifactId = project.name
|
||||||
|
version = DefaultVersion
|
||||||
|
|
||||||
|
//from components.java
|
||||||
|
// 必须有这个 否则不会上传AAR包
|
||||||
|
afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
|
||||||
|
// 上传source,这样使用方可以看到方法注释
|
||||||
|
//artifact generateSourcesJar
|
||||||
|
//要上传的aar路径
|
||||||
|
//artifact "$buildDir/outputs/aar/${project.getName()}-release.aar"
|
||||||
|
//artifact "$buildDir/outputs/aar/${project.getName()}-debug.aar"
|
||||||
|
|
||||||
|
//对pom进行的操作
|
||||||
|
pom.withXml{
|
||||||
|
Node pomNode = asNode()
|
||||||
|
pomNode.dependencies.'*'.findAll() {
|
||||||
|
//将所有的默认依赖移除
|
||||||
|
//it.parent().remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pom {
|
||||||
|
name = artifactId
|
||||||
|
url = siteUrl
|
||||||
|
licenses {
|
||||||
|
license { //证书说明
|
||||||
|
name=LicenseName // 开源协议名称
|
||||||
|
url=LicenseUrl // 协议地址
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id=DeveloperId // 开发者账号
|
||||||
|
name=DeveloperName // 开发者名称
|
||||||
|
email=DeveloperEMail // 开发者邮箱地址
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//软件配置管理
|
||||||
|
scm {
|
||||||
|
connection=gitUrl
|
||||||
|
developerConnection=gitUrl
|
||||||
|
url=siteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WinBoLL Maven Release 仓库传输任务
|
||||||
|
//
|
||||||
|
releaseWinBoLL(MavenPublication) {
|
||||||
|
// 需要使用的变体,假设有free和pay两个变体,可以选择一个
|
||||||
|
//from components.free
|
||||||
|
|
||||||
|
groupId = DefaultGroupId // 文件的groupId
|
||||||
|
artifactId = project.name //文件的名字
|
||||||
|
version = DefaultVersion //版本号
|
||||||
|
|
||||||
|
//from components.java
|
||||||
|
// 必须有这个 否则不会上传AAR包
|
||||||
|
afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
|
||||||
|
// 上传source,这样使用方可以看到方法注释
|
||||||
|
//artifact generateSourcesJar
|
||||||
|
//要上传的aar路径
|
||||||
|
//artifact "$buildDir/outputs/aar/${project.getName()}-release.aar"
|
||||||
|
//artifact "$buildDir/outputs/aar/${project.getName()}-debug.aar"
|
||||||
|
|
||||||
|
//对pom进行的操作
|
||||||
|
pom.withXml{
|
||||||
|
Node pomNode = asNode()
|
||||||
|
pomNode.dependencies.'*'.findAll() {
|
||||||
|
//将所有的默认依赖移除
|
||||||
|
//it.parent().remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pom {
|
||||||
|
name = artifactId
|
||||||
|
url = siteUrl
|
||||||
|
licenses {
|
||||||
|
license { //证书说明
|
||||||
|
name=LicenseName // 开源协议名称
|
||||||
|
url=LicenseUrl // 协议地址
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id=DeveloperId // 开发者账号
|
||||||
|
name=DeveloperName // 开发者名称
|
||||||
|
email=DeveloperEMail // 开发者邮箱地址
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//软件配置管理
|
||||||
|
scm {
|
||||||
|
connection=gitUrl
|
||||||
|
developerConnection=gitUrl
|
||||||
|
url=siteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // 创建名为 release 的任务结束
|
||||||
|
|
||||||
|
// WinBoLL Maven Snapshot 仓库传输任务
|
||||||
|
//
|
||||||
|
snapshotWinBoLL(MavenPublication) {
|
||||||
|
// 需要使用的变体,假设有free和pay两个变体,可以选择一个
|
||||||
|
//from components.free
|
||||||
|
|
||||||
|
groupId = DefaultGroupId // 文件的groupId
|
||||||
|
artifactId = project.name //文件的名字
|
||||||
|
version = DefaultVersion + "-SNAPSHOT" //版本号
|
||||||
|
|
||||||
|
//from components.java
|
||||||
|
// 必须有这个 否则不会上传AAR包
|
||||||
|
afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
|
||||||
|
// 上传source,这样使用方可以看到方法注释
|
||||||
|
//artifact generateSourcesJar
|
||||||
|
//要上传的aar路径
|
||||||
|
//artifact "$buildDir/outputs/aar/${project.getName()}-release.aar"
|
||||||
|
//artifact "$buildDir/outputs/aar/${project.getName()}-debug.aar"
|
||||||
|
|
||||||
|
//对pom进行的操作
|
||||||
|
pom.withXml{
|
||||||
|
Node pomNode = asNode()
|
||||||
|
pomNode.dependencies.'*'.findAll() {
|
||||||
|
//将所有的默认依赖移除
|
||||||
|
//it.parent().remove(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pom {
|
||||||
|
name = artifactId
|
||||||
|
url = siteUrl
|
||||||
|
licenses {
|
||||||
|
license { //证书说明
|
||||||
|
name=LicenseName // 开源协议名称
|
||||||
|
url=LicenseUrl // 协议地址
|
||||||
|
}
|
||||||
|
}
|
||||||
|
developers {
|
||||||
|
developer {
|
||||||
|
id=DeveloperId // 开发者账号
|
||||||
|
name=DeveloperName // 开发者名称
|
||||||
|
email=DeveloperEMail // 开发者邮箱地址
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//软件配置管理
|
||||||
|
scm {
|
||||||
|
connection=gitUrl
|
||||||
|
developerConnection=gitUrl
|
||||||
|
url=siteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // 创建名为 snapshot 的任务结束
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
50
.winboll/winboll_lint_build.gradle
Normal file
50
.winboll/winboll_lint_build.gradle
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
android {
|
||||||
|
lintOptions {
|
||||||
|
// true--关闭lint报告的分析进度
|
||||||
|
//quiet true
|
||||||
|
// true--错误发生后停止gradle构建
|
||||||
|
abortOnError false
|
||||||
|
// true--只报告error
|
||||||
|
//ignoreWarnings true
|
||||||
|
// true--忽略有错误的文件的全/绝对路径(默认是true)
|
||||||
|
//absolutePaths true
|
||||||
|
// true--检查所有问题点,包含其他默认关闭项
|
||||||
|
checkAllWarnings true
|
||||||
|
// true--所有warning当做error
|
||||||
|
//warningsAsErrors true
|
||||||
|
// 关闭指定问题检查
|
||||||
|
//disable 'ExpiredTargetSdkVersion','HardcodedText','UnknownNullness','ButtonStyle','GradleDependency','UnusedResources'
|
||||||
|
// 打开指定问题检查
|
||||||
|
//enable 'RtlHardcoded','RtlCompat', 'RtlEnabled'
|
||||||
|
// 仅检查指定问题
|
||||||
|
//check 'NewApi', 'InlinedApi'
|
||||||
|
// true--error输出文件不包含源码行号
|
||||||
|
//noLines true
|
||||||
|
// true--显示错误的所有发生位置,不截取
|
||||||
|
showAll true
|
||||||
|
// 回退lint设置(默认规则)
|
||||||
|
//lintConfig file("default-lint.xml")
|
||||||
|
// true--生成txt格式报告(默认false)
|
||||||
|
//textReport true
|
||||||
|
// 重定向输出;可以是文件或'stdout'
|
||||||
|
//textOutput 'stdout'
|
||||||
|
// true--生成XML格式报告
|
||||||
|
xmlReport true
|
||||||
|
// 指定xml报告文档(默认lint-results.xml)
|
||||||
|
xmlOutput file("${lintXmlReportFilePath}")
|
||||||
|
// true--生成HTML报告(带问题解释,源码位置,等)
|
||||||
|
htmlReport true
|
||||||
|
// html报告可选路径(构建器默认是lint-results.html )
|
||||||
|
htmlOutput file("${lintHTMLReportFilePath}")
|
||||||
|
// true--所有正式版构建执行规则生成崩溃的lint检查,如果有崩溃问题将停止构建
|
||||||
|
checkReleaseBuilds true
|
||||||
|
// 在发布版本编译时检查(即使不包含lint目标),指定问题的规则生成崩溃
|
||||||
|
//fatal 'NewApi', 'InlineApi'
|
||||||
|
// 指定问题的规则生成错误
|
||||||
|
//error 'Wakelock', 'TextViewEdits'
|
||||||
|
// 指定问题的规则生成警告
|
||||||
|
//warning 'ResourceAsColor'
|
||||||
|
// 忽略指定问题的规则(同关闭检查)
|
||||||
|
//ignore 'TypographyQuotes'
|
||||||
|
}
|
||||||
|
}
|
||||||
49
GenKeyStore/gen_debug_keystore.sh
Normal file
49
GenKeyStore/gen_debug_keystore.sh
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# 应用秘钥创建脚本
|
||||||
|
# Linux 命令行创建JKS秘钥,alias和keyAlias可配置,文件名含时间戳
|
||||||
|
|
||||||
|
# 可配置参数(按需修改)
|
||||||
|
ALIAS="WinBoLL.CC_Debug" # 别名(与keyAlias一致)
|
||||||
|
STORE_PASS="androiddebugkey"
|
||||||
|
KEY_PASS="androiddebugkey"
|
||||||
|
COUNTRY="CN" # 国家代码
|
||||||
|
|
||||||
|
# 获取当前时间戳
|
||||||
|
TIMESTAMP=$(date +%Y%m%d%H%M%S)
|
||||||
|
FILENAME="${ALIAS}_${TIMESTAMP}.jks"
|
||||||
|
STORENAME="${ALIAS}_${TIMESTAMP}.keystore"
|
||||||
|
|
||||||
|
# 生成JKS文件(alias与keyAlias同步)
|
||||||
|
keytool -genkeypair \
|
||||||
|
-alias "${ALIAS}" \
|
||||||
|
-keyalg RSA \
|
||||||
|
-keysize 2048 \
|
||||||
|
-validity 1 \
|
||||||
|
-keystore "${FILENAME}" \
|
||||||
|
-dname "CN=WBFans, OU=Studio, O=WinBoLL, L=Shanwei, ST=Guangdong, C=${COUNTRY}" \
|
||||||
|
-storepass "${STORE_PASS}" \
|
||||||
|
-keypass "${KEY_PASS}"
|
||||||
|
|
||||||
|
# 写入配置文件
|
||||||
|
cat <<EOF > ${STORENAME}
|
||||||
|
keyAlias=${ALIAS}
|
||||||
|
keyPassword=${KEY_PASS}
|
||||||
|
storeFile=../appkey.jks
|
||||||
|
storePassword=${STORE_PASS}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "已生成秘钥:${FILENAME}"
|
||||||
|
echo "配置已写入 ${STORENAME}(keyAlias=${ALIAS})"
|
||||||
|
|
||||||
|
# 询问是否复制文件
|
||||||
|
read -p "是否需要将文件复制为 appkey.jks 和 appkey.keystore?(y/n): " CONFIRM
|
||||||
|
|
||||||
|
if [[ $CONFIRM =~ ^[Yy]$ ]]; then
|
||||||
|
# 复制 jks 文件为 appkey.jks
|
||||||
|
cp -v ${FILENAME} ../appkey.jks
|
||||||
|
# 复制 keystore 文件为 appkey.keystore
|
||||||
|
cp -v ${STORENAME} ../appkey.keystore
|
||||||
|
echo "文件复制完成"
|
||||||
|
else
|
||||||
|
echo "已取消文件复制"
|
||||||
|
fi
|
||||||
201
LICENSE
Normal file
201
LICENSE
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
97
LICENSE-Private-Demo
Normal file
97
LICENSE-Private-Demo
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# WinBoLL 源码 LICENSE-Private-Demo 规范说明书
|
||||||
|
|
||||||
|
# LICENSE-Private-Demo
|
||||||
|
|
||||||
|
# WinBoLL 源码公共转私有继承开发规范守则
|
||||||
|
|
||||||
|
## 核心声明
|
||||||
|
|
||||||
|
本文档**唯一核心设计目的**:通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
|
||||||
|
|
||||||
|
## 一、文件宗旨与风险防控说明
|
||||||
|
|
||||||
|
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
|
||||||
|
|
||||||
|
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
|
||||||
|
|
||||||
|
2. **重点防控人为操作导致的私有分支代码违规合并、回合、推送至公共 ****`winboll`**** 主流分支**,从流程上封堵合并风险
|
||||||
|
|
||||||
|
3. 明确所有开发提交者的操作责任,违规合并公共分支的行为由操作人承担全部代码泄露、合规风险
|
||||||
|
|
||||||
|
4. 规范私有分支初始化全流程,保证私有分支仅可单向同步公共分支更新,禁止任何反向代码流入公共分支
|
||||||
|
|
||||||
|
## 二、公私分支授权标识文件定义(风控核心依据)
|
||||||
|
|
||||||
|
### 1. 公共开源分支唯一标识
|
||||||
|
|
||||||
|
**文件名:LICENSE**
|
||||||
|
|
||||||
|
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
|
||||||
|
|
||||||
|
- 标识当前分支为**开源公开可贡献分支**,遵循原开源授权协议
|
||||||
|
|
||||||
|
- **严禁私有分支内保留、恢复此文件**,出现即判定分支属性异常
|
||||||
|
|
||||||
|
### 2. 私有开发分支唯一标识
|
||||||
|
|
||||||
|
**文件名:LICENSE-Private**
|
||||||
|
|
||||||
|
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
|
||||||
|
|
||||||
|
- 标识当前分支为**私有闭源分支**,代码仅限内部使用,禁止公开、禁止对外贡献
|
||||||
|
|
||||||
|
- 为本分支私有属性的法定判定依据,也是禁止合并至公共分支的核心标记
|
||||||
|
|
||||||
|
## 三、分支管理与合并风控规则(强制遵守)
|
||||||
|
|
||||||
|
1. **公共主流分支**:固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
|
||||||
|
|
||||||
|
2. **私有开发分支**:统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
|
||||||
|
|
||||||
|
3. **核心合并风控铁则**
|
||||||
|
|
||||||
|
- 私有分支 → 公共分支:**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
|
||||||
|
|
||||||
|
- 公共分支 → 私有分支:允许正常拉取、同步上游更新,不影响私有开发迭代
|
||||||
|
|
||||||
|
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
|
||||||
|
|
||||||
|
## 四、公共转私有标准化操作步骤(锁死合并风险)
|
||||||
|
|
||||||
|
请严格按顺序执行,每一步均为风控必要环节,不可跳过、不可修改顺序。
|
||||||
|
|
||||||
|
1. 基于公共主流分支 `winboll`,新建私有开发分支,严格使用 `private-demo-*` 命名,从名称上明确分支私有属性,避免人为混淆。
|
||||||
|
|
||||||
|
2. 本地仓库切换至新建私有分支,确认当前分支名称、检出来源无误。
|
||||||
|
|
||||||
|
3. **永久删除项目根目录公共授权文件 ****`LICENSE`**,彻底移除公共分支标识,断绝误合并的标识漏洞。
|
||||||
|
|
||||||
|
4. 将本规范文件 `LICENSE-Private-Demo` 复制并重命名为 `LICENSE-Private`,作为私有分支生效授权文件。
|
||||||
|
|
||||||
|
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**:
|
||||||
|
|
||||||
|
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
|
6. 提交完成后,本分支正式转为私有开发状态,后续所有代码提交、分支合并、版本迭代,均严禁指向公共 `winboll` 分支。
|
||||||
|
|
||||||
|
## 五、人为操作责任界定(核心补充条款)
|
||||||
|
|
||||||
|
1. 本分支所有开发者、代码提交者、分支合并操作者,均视为**完全知晓本分支的私有属性与合并禁令**,自愿遵守本规范全部约束。
|
||||||
|
|
||||||
|
2. **无论故意或过失,凡是人为执行私有分支向公共 ****`winboll`**** 分支的合并、推送、PR 提交、代码回合操作,全部责任由执行操作的本人独立承担**,项目方不承担任何因人为违规操作导致的代码泄露、开源合规、版本污染风险。
|
||||||
|
|
||||||
|
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
|
||||||
|
|
||||||
|
4. 分支属性校验以根目录授权文件为唯一标准:只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
|
||||||
|
|
||||||
|
## 六、分支状态校验与异常处理
|
||||||
|
|
||||||
|
- 合规公共分支:仅存在 `LICENSE`,无 `LICENSE-Private`
|
||||||
|
|
||||||
|
- 合规私有分支:仅存在 `LICENSE-Private`,无 `LICENSE`
|
||||||
|
|
||||||
|
- 异常状态:两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
|
||||||
|
|
||||||
|
> (注:文档部分内容可能由 AI 生成)
|
||||||
97
LICENSE-Private-Demo_docs/LICENSE-Private-Demo
Normal file
97
LICENSE-Private-Demo_docs/LICENSE-Private-Demo
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# WinBoLL 源码 LICENSE-Private-Demo 规范说明书
|
||||||
|
|
||||||
|
# LICENSE-Private-Demo
|
||||||
|
|
||||||
|
# WinBoLL 源码公共转私有继承开发规范守则
|
||||||
|
|
||||||
|
## 核心声明
|
||||||
|
|
||||||
|
本文档**唯一核心设计目的**:通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
|
||||||
|
|
||||||
|
## 一、文件宗旨与风险防控说明
|
||||||
|
|
||||||
|
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
|
||||||
|
|
||||||
|
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
|
||||||
|
|
||||||
|
2. **重点防控人为操作导致的私有分支代码违规合并、回合、推送至公共 ****`winboll`**** 主流分支**,从流程上封堵合并风险
|
||||||
|
|
||||||
|
3. 明确所有开发提交者的操作责任,违规合并公共分支的行为由操作人承担全部代码泄露、合规风险
|
||||||
|
|
||||||
|
4. 规范私有分支初始化全流程,保证私有分支仅可单向同步公共分支更新,禁止任何反向代码流入公共分支
|
||||||
|
|
||||||
|
## 二、公私分支授权标识文件定义(风控核心依据)
|
||||||
|
|
||||||
|
### 1. 公共开源分支唯一标识
|
||||||
|
|
||||||
|
**文件名:LICENSE**
|
||||||
|
|
||||||
|
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
|
||||||
|
|
||||||
|
- 标识当前分支为**开源公开可贡献分支**,遵循原开源授权协议
|
||||||
|
|
||||||
|
- **严禁私有分支内保留、恢复此文件**,出现即判定分支属性异常
|
||||||
|
|
||||||
|
### 2. 私有开发分支唯一标识
|
||||||
|
|
||||||
|
**文件名:LICENSE-Private**
|
||||||
|
|
||||||
|
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
|
||||||
|
|
||||||
|
- 标识当前分支为**私有闭源分支**,代码仅限内部使用,禁止公开、禁止对外贡献
|
||||||
|
|
||||||
|
- 为本分支私有属性的法定判定依据,也是禁止合并至公共分支的核心标记
|
||||||
|
|
||||||
|
## 三、分支管理与合并风控规则(强制遵守)
|
||||||
|
|
||||||
|
1. **公共主流分支**:固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
|
||||||
|
|
||||||
|
2. **私有开发分支**:统一从 `winboll` 分支检出,命名固定格式为 `private-demo-*`,与公共分支物理隔离。
|
||||||
|
|
||||||
|
3. **核心合并风控铁则**
|
||||||
|
|
||||||
|
- 私有分支 → 公共分支:**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
|
||||||
|
|
||||||
|
- 公共分支 → 私有分支:允许正常拉取、同步上游更新,不影响私有开发迭代
|
||||||
|
|
||||||
|
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
|
||||||
|
|
||||||
|
## 四、公共转私有标准化操作步骤(锁死合并风险)
|
||||||
|
|
||||||
|
请严格按顺序执行,每一步均为风控必要环节,不可跳过、不可修改顺序。
|
||||||
|
|
||||||
|
1. 基于公共主流分支 `winboll`,新建私有开发分支,严格使用 `private-demo-*` 命名,从名称上明确分支私有属性,避免人为混淆。
|
||||||
|
|
||||||
|
2. 本地仓库切换至新建私有分支,确认当前分支名称、检出来源无误。
|
||||||
|
|
||||||
|
3. **永久删除项目根目录公共授权文件 ****`LICENSE`**,彻底移除公共分支标识,断绝误合并的标识漏洞。
|
||||||
|
|
||||||
|
4. 将本规范文件 `LICENSE-Private-Demo` 复制并重命名为 `LICENSE-Private`,作为私有分支生效授权文件。
|
||||||
|
|
||||||
|
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**:
|
||||||
|
|
||||||
|
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
|
6. 提交完成后,本分支正式转为私有开发状态,后续所有代码提交、分支合并、版本迭代,均严禁指向公共 `winboll` 分支。
|
||||||
|
|
||||||
|
## 五、人为操作责任界定(核心补充条款)
|
||||||
|
|
||||||
|
1. 本分支所有开发者、代码提交者、分支合并操作者,均视为**完全知晓本分支的私有属性与合并禁令**,自愿遵守本规范全部约束。
|
||||||
|
|
||||||
|
2. **无论故意或过失,凡是人为执行私有分支向公共 ****`winboll`**** 分支的合并、推送、PR 提交、代码回合操作,全部责任由执行操作的本人独立承担**,项目方不承担任何因人为违规操作导致的代码泄露、开源合规、版本污染风险。
|
||||||
|
|
||||||
|
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
|
||||||
|
|
||||||
|
4. 分支属性校验以根目录授权文件为唯一标准:只要分支内存在 `LICENSE-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
|
||||||
|
|
||||||
|
## 六、分支状态校验与异常处理
|
||||||
|
|
||||||
|
- 合规公共分支:仅存在 `LICENSE`,无 `LICENSE-Private`
|
||||||
|
|
||||||
|
- 合规私有分支:仅存在 `LICENSE-Private`,无 `LICENSE`
|
||||||
|
|
||||||
|
- 异常状态:两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
|
||||||
|
|
||||||
|
> (注:文档部分内容可能由 AI 生成)
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,97 @@
|
|||||||
|
# WinBoLL 源码 LICENSE\-Private\-Demo 规范说明书
|
||||||
|
|
||||||
|
# LICENSE\-Private\-Demo
|
||||||
|
|
||||||
|
# WinBoLL 源码公共转私有继承开发规范守则
|
||||||
|
|
||||||
|
## 核心声明
|
||||||
|
|
||||||
|
本文档**唯一核心设计目的**:通过文件标识、分支隔离、操作规范、责任界定四重约束,**从根源规避私有开发分支代码被人为合并、推送、提交至公共开源主流分支的风险**,明确人为操作失误、违规合并的全部责任归属,同时保证私有分支可正常同步、拉取公共主流分支的上游更新。
|
||||||
|
|
||||||
|
## 一、文件宗旨与风险防控说明
|
||||||
|
|
||||||
|
本文件为 WinBoLL 项目公共开源分支转为私有独立分支开发的**强制标准化操作手册与责任界定文件**,核心风控目标:
|
||||||
|
|
||||||
|
1. 严格隔离公共开源分支与私有开发分支,通过授权文件标记实现分支属性一眼可辨,杜绝人为操作混淆
|
||||||
|
|
||||||
|
2. **重点防控人为操作导致的私有分支代码违规合并、回合、推送至公共 ****`winboll`**** 主流分支**,从流程上封堵合并风险
|
||||||
|
|
||||||
|
3. 明确所有开发提交者的操作责任,违规合并公共分支的行为由操作人承担全部代码泄露、合规风险
|
||||||
|
|
||||||
|
4. 规范私有分支初始化全流程,保证私有分支仅可单向同步公共分支更新,禁止任何反向代码流入公共分支
|
||||||
|
|
||||||
|
## 二、公私分支授权标识文件定义(风控核心依据)
|
||||||
|
|
||||||
|
### 1\. 公共开源分支唯一标识
|
||||||
|
|
||||||
|
**文件名:LICENSE**
|
||||||
|
|
||||||
|
- 仅允许存在于公共主流分支 `winboll` 及官方公共衍生分支
|
||||||
|
|
||||||
|
- 标识当前分支为**开源公开可贡献分支**,遵循原开源授权协议
|
||||||
|
|
||||||
|
- **严禁私有分支内保留、恢复此文件**,出现即判定分支属性异常
|
||||||
|
|
||||||
|
### 2\. 私有开发分支唯一标识
|
||||||
|
|
||||||
|
**文件名:LICENSE\-Private**
|
||||||
|
|
||||||
|
- 仅允许存在于私有开发分支,**绝对禁止出现在公共 ****`winboll`**** 分支**
|
||||||
|
|
||||||
|
- 标识当前分支为**私有闭源分支**,代码仅限内部使用,禁止公开、禁止对外贡献
|
||||||
|
|
||||||
|
- 为本分支私有属性的法定判定依据,也是禁止合并至公共分支的核心标记
|
||||||
|
|
||||||
|
## 三、分支管理与合并风控规则(强制遵守)
|
||||||
|
|
||||||
|
1. **公共主流分支**:固定为 `winboll`,为项目唯一开源主线,仅保留 `LICENSE` 文件,**禁止接收任何私有分支的合并、提交、推送请求**。
|
||||||
|
|
||||||
|
2. **私有开发分支**:统一从 `winboll` 分支检出,命名固定格式为 `private\-demo\-\*`,与公共分支物理隔离。
|
||||||
|
|
||||||
|
3. **核心合并风控铁则**
|
||||||
|
|
||||||
|
- 私有分支 → 公共分支:**永久禁止任何形式的合并、推送、PR 提交、代码回合,人为操作也绝不允许**
|
||||||
|
|
||||||
|
- 公共分支 → 私有分支:允许正常拉取、同步上游更新,不影响私有开发迭代
|
||||||
|
|
||||||
|
4. 所有仓库提交者、合并操作者,均视为已阅读并完全认可本规则,**人为执行私有分支向公共分支的合并操作,由操作人承担全部代码泄露、合规违约、项目安全风险**。
|
||||||
|
|
||||||
|
## 四、公共转私有标准化操作步骤(锁死合并风险)
|
||||||
|
|
||||||
|
请严格按顺序执行,每一步均为风控必要环节,不可跳过、不可修改顺序。
|
||||||
|
|
||||||
|
1. 基于公共主流分支 `winboll`,新建私有开发分支,严格使用 `private\-demo\-\*` 命名,从名称上明确分支私有属性,避免人为混淆。
|
||||||
|
|
||||||
|
2. 本地仓库切换至新建私有分支,确认当前分支名称、检出来源无误。
|
||||||
|
|
||||||
|
3. **永久删除项目根目录公共授权文件 ****`LICENSE`**,彻底移除公共分支标识,断绝误合并的标识漏洞。
|
||||||
|
|
||||||
|
4. 将本规范文件 `LICENSE\-Private\-Demo` 复制并重命名为 `LICENSE\-Private`,作为私有分支生效授权文件。
|
||||||
|
|
||||||
|
5. 将以上所有变更执行一次性 Git 提交,**提交信息必须固定使用以下内容,不可修改**:
|
||||||
|
|
||||||
|
> 初始化私有开发分支,已切换私有授权文件,本分支禁止任何人为合并、推送至 winboll 公共分支
|
||||||
|
>
|
||||||
|
>
|
||||||
|
|
||||||
|
6. 提交完成后,本分支正式转为私有开发状态,后续所有代码提交、分支合并、版本迭代,均严禁指向公共 `winboll` 分支。
|
||||||
|
|
||||||
|
## 五、人为操作责任界定(核心补充条款)
|
||||||
|
|
||||||
|
1. 本分支所有开发者、代码提交者、分支合并操作者,均视为**完全知晓本分支的私有属性与合并禁令**,自愿遵守本规范全部约束。
|
||||||
|
|
||||||
|
2. **无论故意或过失,凡是人为执行私有分支向公共 ****`winboll`**** 分支的合并、推送、PR 提交、代码回合操作,全部责任由执行操作的本人独立承担**,项目方不承担任何因人为违规操作导致的代码泄露、开源合规、版本污染风险。
|
||||||
|
|
||||||
|
3. 仓库管理员需严格校验合并请求的分支标识与授权文件,发现带有 `LICENSE\-Private` 标记的分支申请合并至公共分支,一律直接拒绝,并记录操作人信息。
|
||||||
|
|
||||||
|
4. 分支属性校验以根目录授权文件为唯一标准:只要分支内存在 `LICENSE\-Private` 文件,就绝对禁止向公共分支发起任何合并操作。
|
||||||
|
|
||||||
|
## 六、分支状态校验与异常处理
|
||||||
|
|
||||||
|
- 合规公共分支:仅存在 `LICENSE`,无 `LICENSE\-Private`
|
||||||
|
|
||||||
|
- 合规私有分支:仅存在 `LICENSE\-Private`,无 `LICENSE`
|
||||||
|
|
||||||
|
- 异常状态:两个文件同时存在 / 均不存在 → 立即停止开发与提交,按本规范重置分支状态,严禁执行任何合并操作
|
||||||
|
|
||||||
|
> (注:文档部分内容可能由 AI 生成)
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 710 KiB |
105
README.md
Normal file
105
README.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# WinBoLL 源生态计划项目说明书
|
||||||
|
|
||||||
|
## 一、项目概述
|
||||||
|
|
||||||
|
### 1. 核心定位
|
||||||
|
WinBoLL 手机源码计划,旨在通过核心项目 WinBoLL 构建手机端与服务器端的 Android 项目的开发源码生态。实现手机与服务器的源码的联合开发。
|
||||||
|
|
||||||
|
### 2. 仓库架构
|
||||||
|
#### **仓库类型:功能说明**
|
||||||
|
☆ 基础项目分支 WinBoLL:手机端安卓应用开发基础模板。
|
||||||
|
☆ 应用项目分支 APPBase、AES、PowerBell、Positions**:安卓应用单一管理系列项目。
|
||||||
|
☆ 源码汇总管理 OriginMaster**:各类分支源码合并存档,不适宜作为开发库使用。
|
||||||
|
|
||||||
|
### 3. 源码合并管理推送路线图
|
||||||
|
⚠️ **注意**:仅仅展示不同应用模块源码的综合管理路线。分支合并操作时,必须具备 Git 管理经验。
|
||||||
|
|
||||||
|
★ WinBoLL → APPBase → OriginMaster
|
||||||
|
★ WinBoLL → AES → OriginMaster
|
||||||
|
★ WinBoLL → PowerBell → OriginMaster
|
||||||
|
★ WinBoLL → Positions → OriginMaster
|
||||||
|
|
||||||
|
## 二、WinBoLL 项目核心信息
|
||||||
|
|
||||||
|
### 1. 项目简介
|
||||||
|
☆ WinBoLL 项目是为手机端开发Android 项目的需求而设计的项目。
|
||||||
|
|
||||||
|
### 2. 官方资源
|
||||||
|
#### ☆ 官方网站**:https://www.winboll.cc/
|
||||||
|
#### ☆ 源码地址:
|
||||||
|
★ Gitea:https://gitea.winboll.cc/Studio/WinBoLL.git
|
||||||
|
★ GitHub:https://github.com/ZhanGSKen/WinBoLL.git
|
||||||
|
★ 码云:https://gitee.com/zhangsken/winboll.git
|
||||||
|
|
||||||
|
## 三、应用编译环境检查问题
|
||||||
|
### 核心判断条件:
|
||||||
|
☆ WinBoLL 项目以文件夹 `"/sdcard/WinBoLLStudio/APKs"` 是否存在为判断环境编译输出条件,因为编译输出的APK文件需要一个可供保存的环境。
|
||||||
|
|
||||||
|
☆ 文件夹"/sdcard/WinBoLLStudio/APKs" 目录条件设置方法:
|
||||||
|
***Linux 服务器端方面***:建立 `/sdcard/WinBoLLStudio/APKs` 目录即可。
|
||||||
|
***手机开发端方面***:建立 `"/sdcard/WinBoLLStudio/APKs"` 目录(即 `"/storage/emulated/0/WinBoLLStudio/APKs"` 目录) 即可。
|
||||||
|
|
||||||
|
## 四、前置条件
|
||||||
|
|
||||||
|
### 1. WinBoLL APP 开发环境配置介绍
|
||||||
|
#### WinBoLL APK 编译输出内容包括:
|
||||||
|
☆ "/sdcard/WinBoLLStudio/APKs"` 目录内的所有应用分支的 APK 文件。
|
||||||
|
winboll.properties 文件的 APKOutputPath 属性可配置这个 APK 输出目录的路径。
|
||||||
|
☆ "/sdcard/AppProjects/app.apk"文件。
|
||||||
|
winboll.properties 文件的 ExtraAPKOutputPath 属性可配置这个 APK 额外输出文件的路径
|
||||||
|
#### WinBoLL APK 源码命名空间规范
|
||||||
|
☆ WinBoLL 项目使用 "cc.winboll.studio" 作为源码命名空间。在此命名空间下进行源码定义。
|
||||||
|
|
||||||
|
## 五、核心需求规划
|
||||||
|
|
||||||
|
### 1. WinBoLL 应用安全验证需求
|
||||||
|
#### ☆ 支持访问 https://console.winboll.cc/ 服务器以校验应用包签名与版本。
|
||||||
|
|
||||||
|
### 2. 手机端源码开发管理需求
|
||||||
|
#### ☆ 支持切换不同 WinBoLL 分支,以开发不同安卓应用。
|
||||||
|
|
||||||
|
## 六、编译与使用指南
|
||||||
|
|
||||||
|
### 1. 项目初始化(必须)
|
||||||
|
#### 1. 复制 `settings.gradle-demo` 为 `settings.gradle`。编辑 `settings.gradle` 文件内容,取消对应项目模块注释。
|
||||||
|
#### 2. 复制 `gradle.properties-androidx-demo` (Android X 项目) 或 `gradle.properties-android-demo` (基本 Android 项目) 为 `gradle.properties`。
|
||||||
|
#### 3. 复制(可选)`local.properties-demo` 为 `local.properties`,编辑 `local.properties` 文件内容,配置 Android SDK 目录。
|
||||||
|
#### 4. **签名设置**:
|
||||||
|
☆ **调试编译秘钥制作**:使用 Termux 应用终端,cd 进入 GenKeyStore 目录,运行 `bash gen_debug_keystore.sh` 脚本即可生成应用调试秘钥。
|
||||||
|
☆ **应用秘钥配置方法**:拷贝调试编译秘钥制作生成的 `appkey.jks` 与 `appkey.keystore` 文件到项目根目录即可。
|
||||||
|
|
||||||
|
## 七、应用编译命令介绍
|
||||||
|
|
||||||
|
### (1)类库型模块配置要点
|
||||||
|
#### 1. **优先修改配置文件**:优先修改应用测试项目(目录为 `"<WinBoLl根目录>/<类库测试应用>/"`)内 `build.properties` 文件,设置对应的类库项目名称:`libraryProject=<类库项目模块名>`。
|
||||||
|
#### 2. **编译优先启动步骤**:使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishAPKAddTag.sh <类库测试项目模块名>` 命令。运行后可生成测试项目与类库项目的编译参数文件 `build.properties`。生成的 `build.properties` 文件有两份,一份在测试项目模块的文件夹内,一份在类库项目本身的模块文件夹内。
|
||||||
|
#### 3. **最后类库编译发布步骤**:使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块名>` 命令。运行后可发布至 WinBoLL Nexus Maven 库、本地 maven 目录或者是通用默认的 Gradle Maven 库。
|
||||||
|
|
||||||
|
### (2)单一应用型模块与类库测试型模块配置要点
|
||||||
|
#### ☆ APK 编译方法:
|
||||||
|
使用 Termux 应用,进入 `"<WinBoLl根目录>"`,运行 `$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名>`。
|
||||||
|
#### ☆ 运行后的 APK 输出路径:
|
||||||
|
★ 默认路径 (`$ bash gradlew assembleBetaDebug` 任务):APK 在 `/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/debug/` 目录。
|
||||||
|
★ 默认路径 (`$ bash assembleStageRelease` 任务):APK 在 `/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/` 目录。
|
||||||
|
★ 额外输出路径:(假设 `winboll.properties` 文件已配置 `ExtraAPKOutputPath` 属性) 输出至 `ExtraAPKOutputPath` 属性配置的目录下。
|
||||||
|
|
||||||
|
### (3)手机端应用调试命令介绍
|
||||||
|
#### ☆ Beta 渠道调试命令
|
||||||
|
$bash gradlew assembleBetaDebug
|
||||||
|
|
||||||
|
#### ☆ Stage 渠道调试命令
|
||||||
|
$bash gradlew assembleStageDebug
|
||||||
|
|
||||||
|
### (4)服务器端开发命令介绍
|
||||||
|
##### ☆ Stage 渠道应用发布命令为:
|
||||||
|
("<WinBoLl根目录>/settings.gradle"文件需要配置编译模块开启参数,拷贝 settings.gradle-demo 为 settings.gradle 文件取消对应的分支配置部分即可。)
|
||||||
|
$bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名>
|
||||||
|
或者是
|
||||||
|
$bash gradlew assembleStageRelease
|
||||||
|
|
||||||
|
|
||||||
|
## 八、WinBoLL 应用 APK 版本号命名规则
|
||||||
|
### ☆ Stage 渠道:
|
||||||
|
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号> (示例: APPBase_15.7.0 )
|
||||||
|
### ☆ Beta 渠道:
|
||||||
|
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> (示例: APPBase_15.9.6-beta8_5413 )
|
||||||
1
aes/.gitignore
vendored
Normal file
1
aes/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
36
aes/README.md
Normal file
36
aes/README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# AES
|
||||||
|
[](https://jitpack.io/#ZhanGSKen/AES)
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
WinBoLL AndroidX 可视化元素类库。
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||||
|
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||||
|
|
||||||
|
|
||||||
|
#### Gradle 编译说明
|
||||||
|
调试版编译命令 :gradle assembleBetaDebug
|
||||||
|
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh aes
|
||||||
|
阶段版类库发布命令 :git pull &&bash .winboll/bashPublishLIBAddTag.sh libaes
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 新建 Feat_xxx 分支
|
||||||
|
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
|
||||||
|
4. 新建 Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### 特技
|
||||||
|
|
||||||
|
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||||
|
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||||
|
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
|
|
||||||
|
#### 参考文档
|
||||||
1
aes/app_update_description.txt
Normal file
1
aes/app_update_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
48
aes/build.gradle
Normal file
48
aes/build.gradle
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply from: '../.winboll/winboll_app_build.gradle'
|
||||||
|
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||||
|
|
||||||
|
def genVersionName(def versionName){
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
// 保存基础版本号
|
||||||
|
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
// 返回编译版本号
|
||||||
|
return "${versionName}." + winbollBuildProps['stageCount']
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
// 适配MIUI12
|
||||||
|
compileSdkVersion 30
|
||||||
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "cc.winboll.studio.aes"
|
||||||
|
minSdkVersion 26
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
// versionName 更新后需要手动设置
|
||||||
|
// 项目模块目录的 build.gradle 文件的 stageCount=0
|
||||||
|
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||||
|
versionName "15.20"
|
||||||
|
if(true) {
|
||||||
|
versionName = genVersionName("${versionName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 米盟 SDK
|
||||||
|
packagingOptions {
|
||||||
|
doNotStrip "*/*/libmimo_1011.so"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(':libaes')
|
||||||
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
}
|
||||||
8
aes/build.properties
Normal file
8
aes/build.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
|
#Tue May 12 13:11:28 HKT 2026
|
||||||
|
stageCount=4
|
||||||
|
libraryProject=libaes
|
||||||
|
baseVersion=15.20
|
||||||
|
publishVersion=15.20.3
|
||||||
|
buildCount=0
|
||||||
|
baseBetaVersion=15.20.4
|
||||||
137
aes/proguard-rules.pro
vendored
Normal file
137
aes/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# ============================== 基础通用规则 ==============================
|
||||||
|
# 保留系统组件
|
||||||
|
-keep public class * extends android.app.Activity
|
||||||
|
-keep public class * extends android.app.Service
|
||||||
|
-keep public class * extends android.content.BroadcastReceiver
|
||||||
|
-keep public class * extends android.content.ContentProvider
|
||||||
|
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||||
|
-keep public class * extends android.preference.Preference
|
||||||
|
|
||||||
|
# 保留 WinBoLL 核心包及子类(合并简化规则)
|
||||||
|
-keep class cc.winboll.studio.** { *; }
|
||||||
|
-keepclassmembers class cc.winboll.studio.** { *; }
|
||||||
|
|
||||||
|
# 保留所有类中的 public static final String TAG 字段(便于日志定位)
|
||||||
|
-keepclassmembers class * {
|
||||||
|
public static final java.lang.String TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留序列化类(避免Parcelable/Gson解析异常)
|
||||||
|
-keep class * implements android.os.Parcelable {
|
||||||
|
public static final android.os.Parcelable$Creator *;
|
||||||
|
}
|
||||||
|
-keepclassmembers class * implements java.io.Serializable {
|
||||||
|
static final long serialVersionUID;
|
||||||
|
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||||
|
private void writeObject(java.io.ObjectOutputStream);
|
||||||
|
private void readObject(java.io.ObjectInputStream);
|
||||||
|
java.lang.Object writeReplace();
|
||||||
|
java.lang.Object readResolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留 R 文件(避免资源ID混淆)
|
||||||
|
-keepclassmembers class **.R$* {
|
||||||
|
public static <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留 native 方法(避免JNI调用失败)
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留注解和泛型(避免反射/序列化异常)
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# 屏蔽 Java 8+ 警告(适配 Java 7 语法)
|
||||||
|
-dontwarn java.lang.invoke.*
|
||||||
|
-dontwarn android.support.v8.renderscript.*
|
||||||
|
-dontwarn java.util.function.**
|
||||||
|
|
||||||
|
# ============================== 第三方框架专项规则 ==============================
|
||||||
|
# OkHttp 4.4.1(米盟广告请求依赖,完善Lambda兼容)
|
||||||
|
-keep class okhttp3.** { *; }
|
||||||
|
-keep interface okhttp3.** { *; }
|
||||||
|
-keep class okhttp3.internal.** { *; }
|
||||||
|
-keep class okio.** { *; }
|
||||||
|
-dontwarn okhttp3.internal.platform.**
|
||||||
|
-dontwarn okio.**
|
||||||
|
|
||||||
|
# Glide 4.9.0(米盟广告图片加载依赖)
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
-keepclassmembers class * implements com.bumptech.glide.module.AppGlideModule {
|
||||||
|
<init>();
|
||||||
|
}
|
||||||
|
-dontwarn com.bumptech.glide.**
|
||||||
|
|
||||||
|
# Gson 2.8.5(米盟广告数据序列化依赖)
|
||||||
|
-keep class com.google.gson.** { *; }
|
||||||
|
-keep interface com.google.gson.** { *; }
|
||||||
|
-keepclassmembers class * {
|
||||||
|
@com.google.gson.annotations.SerializedName <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 米盟 SDK(核心广告组件,完整保留避免加载失败)
|
||||||
|
-keep class com.miui.zeus.** { *; }
|
||||||
|
-keep interface com.miui.zeus.** { *; }
|
||||||
|
# 保留米盟日志字段(便于广告加载失败排查)
|
||||||
|
-keepclassmembers class com.miui.zeus.mimo.sdk.** {
|
||||||
|
public static final java.lang.String TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
# RecyclerView 1.0.0(米盟广告布局渲染依赖)
|
||||||
|
-keep class androidx.recyclerview.** { *; }
|
||||||
|
-keep interface androidx.recyclerview.** { *; }
|
||||||
|
-keepclassmembers class androidx.recyclerview.widget.RecyclerView$Adapter {
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 其他第三方框架(按引入依赖保留,无则可删除)
|
||||||
|
# XXPermissions 18.63
|
||||||
|
-keep class com.hjq.permissions.** { *; }
|
||||||
|
-keep interface com.hjq.permissions.** { *; }
|
||||||
|
|
||||||
|
# ZXing 二维码(核心解析组件)
|
||||||
|
-keep class com.google.zxing.** { *; }
|
||||||
|
-keep class com.journeyapps.zxing.** { *; }
|
||||||
|
|
||||||
|
# Jsoup HTML解析
|
||||||
|
-keep class org.jsoup.** { *; }
|
||||||
|
|
||||||
|
# Pinyin4j 拼音搜索
|
||||||
|
-keep class net.sourceforge.pinyin4j.** { *; }
|
||||||
|
|
||||||
|
# JSch SSH组件
|
||||||
|
-keep class com.jcraft.jsch.** { *; }
|
||||||
|
|
||||||
|
# AndroidX 基础组件
|
||||||
|
-keep class androidx.appcompat.** { *; }
|
||||||
|
-keep interface androidx.appcompat.** { *; }
|
||||||
|
|
||||||
|
# ============================== 优化与调试配置 ==============================
|
||||||
|
# 优化级别(平衡混淆效果与性能)
|
||||||
|
-optimizationpasses 5
|
||||||
|
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||||
|
|
||||||
|
# 调试辅助(保留行号便于崩溃定位)
|
||||||
|
-verbose
|
||||||
|
-dontpreverify
|
||||||
|
-dontusemixedcaseclassnames
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
12
aes/src/beta/AndroidManifest.xml
Normal file
12
aes/src/beta/AndroidManifest.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
||||||
6
aes/src/beta/res/values/strings.xml
Normal file
6
aes/src/beta/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Put flavor specific strings here -->
|
||||||
|
<string name="app_name">AES+</string>
|
||||||
|
</resources>
|
||||||
45
aes/src/main/AndroidManifest.xml
Normal file
45
aes/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="cc.winboll.studio.aes">
|
||||||
|
|
||||||
|
<!-- 对正在运行的应用重新排序 -->
|
||||||
|
<uses-permission android:name="android.permission.REORDER_TASKS"/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".App"
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@drawable/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/MyAESTheme"
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:networkSecurityConfig="@xml/network_security_config">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.max_aspect"
|
||||||
|
android:value="4.0"/>
|
||||||
|
|
||||||
|
<activity android:name=".TestActivityManagerActivity"/>
|
||||||
|
|
||||||
|
<activity android:name=".SettingsActivity"/>
|
||||||
|
|
||||||
|
<activity android:name=".AboutActivity"/>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
78
aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java
Normal file
78
aes/src/main/java/cc/winboll/studio/aes/AboutActivity.java
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package cc.winboll.studio.aes;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.aes.R;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.models.APPInfo;
|
||||||
|
import cc.winboll.studio.libappbase.views.AboutView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/13 11:25
|
||||||
|
* @Describe 应用介绍窗口
|
||||||
|
*/
|
||||||
|
public class AboutActivity extends BaseWinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "AboutActivity";
|
||||||
|
|
||||||
|
private Toolbar mToolbar;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_about);
|
||||||
|
|
||||||
|
// 设置工具栏
|
||||||
|
initToolbar();
|
||||||
|
|
||||||
|
AboutView aboutView = findViewById(R.id.aboutview);
|
||||||
|
aboutView.setAPPInfo(genDefaultAppInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initToolbar() {
|
||||||
|
LogUtils.d(TAG, "initToolbar() 开始初始化");
|
||||||
|
mToolbar = findViewById(R.id.toolbar);
|
||||||
|
if (mToolbar == null) {
|
||||||
|
LogUtils.e(TAG, "initToolbar() | Toolbar未找到");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSupportActionBar(mToolbar);
|
||||||
|
mToolbar.setSubtitle(getTag());
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LogUtils.d(TAG, "导航栏 点击返回按钮");
|
||||||
|
WinBoLLActivityManager.getInstance().resumeActivity(MainActivity.class);
|
||||||
|
WinBoLLActivityManager.getInstance().finish(AboutActivity.this);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
LogUtils.d(TAG, "initToolbar() 配置完成");
|
||||||
|
}
|
||||||
|
|
||||||
|
private APPInfo genDefaultAppInfo() {
|
||||||
|
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
|
||||||
|
String branchName = "aes";
|
||||||
|
APPInfo appInfo = new APPInfo();
|
||||||
|
appInfo.setAppName(getString(R.string.app_name));
|
||||||
|
appInfo.setAppIcon(R.drawable.ic_winboll);
|
||||||
|
appInfo.setAppDescription(getString(R.string.app_description));
|
||||||
|
appInfo.setAppGitName("AES");
|
||||||
|
appInfo.setAppGitOwner("Studio");
|
||||||
|
appInfo.setAppGitAPPBranch(branchName);
|
||||||
|
appInfo.setAppGitAPPSubProjectFolder(branchName);
|
||||||
|
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=AES");
|
||||||
|
appInfo.setAppAPKName("AES");
|
||||||
|
appInfo.setAppAPKFolderName("AES");
|
||||||
|
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
|
||||||
|
return appInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
aes/src/main/java/cc/winboll/studio/aes/App.java
Normal file
34
aes/src/main/java/cc/winboll/studio/aes/App.java
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package cc.winboll.studio.aes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2024/06/13 19:03:58
|
||||||
|
* @Describe AES应用类
|
||||||
|
*/
|
||||||
|
import android.view.Gravity;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
|
||||||
|
|
||||||
|
public class App extends GlobalApplication {
|
||||||
|
|
||||||
|
public static final String TAG = "App";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
setIsDebugging(BuildConfig.DEBUG);
|
||||||
|
//setIsDebugging(false);
|
||||||
|
WinBoLLActivityManager.init(this);
|
||||||
|
|
||||||
|
// 初始化 Toast 框架
|
||||||
|
ToastUtils.init(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTerminate() {
|
||||||
|
super.onTerminate();
|
||||||
|
ToastUtils.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package cc.winboll.studio.aes;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||||
|
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/13 16:35
|
||||||
|
* @Describe BaseWinBollActivity 【继承AppCompatActivity,保留核心能力,不额外暴露方法】
|
||||||
|
* 继承链路:BaseWinBoLLActivity → AppCompatActivity → FragmentActivity,AppCompat能力天然继承可用
|
||||||
|
*/
|
||||||
|
public abstract class BaseWinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||||
|
public static final String TAG = "BaseWinBoLLActivity";
|
||||||
|
|
||||||
|
protected volatile AESThemeBean.ThemeType mThemeType;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
mThemeType = AESThemeBean.getThemeStyleType(AESThemeUtil.getThemeTypeID(getApplicationContext()));
|
||||||
|
setTheme(AESThemeUtil.getThemeTypeID(getApplicationContext()));
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
WinBoLLActivityManager.getInstance().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 子类必须实现getTag(),确保唯一标识
|
||||||
|
@Override
|
||||||
|
public abstract String getTag();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
196
aes/src/main/java/cc/winboll/studio/aes/MainActivity.java
Normal file
196
aes/src/main/java/cc/winboll/studio/aes/MainActivity.java
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
package cc.winboll.studio.aes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2024/06/13 19:05:52
|
||||||
|
* @Describe 应用主窗口
|
||||||
|
*/
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.aes.R;
|
||||||
|
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
|
||||||
|
import cc.winboll.studio.libaes.dialogs.LocalFileSelectDialog;
|
||||||
|
import cc.winboll.studio.libaes.dialogs.StoragePathDialog;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libaes.models.DrawerMenuBean;
|
||||||
|
import cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity;
|
||||||
|
import cc.winboll.studio.libaes.unittests.TestAButtonFragment;
|
||||||
|
import cc.winboll.studio.libaes.unittests.TestASupportToolbarActivity;
|
||||||
|
import cc.winboll.studio.libaes.unittests.TestAToolbarActivity;
|
||||||
|
import cc.winboll.studio.libaes.unittests.TestDrawerFragmentActivity;
|
||||||
|
import cc.winboll.studio.libaes.unittests.TestViewPageFragment;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import com.a4455jkjh.colorpicker.ColorPickerDialog;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
public class MainActivity extends DrawerFragmentActivity {
|
||||||
|
|
||||||
|
|
||||||
|
public static final String TAG = "MainActivity";
|
||||||
|
|
||||||
|
TestAButtonFragment mTestAButtonFragment;
|
||||||
|
TestViewPageFragment mTestViewPageFragment;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
if (mTestAButtonFragment == null) {
|
||||||
|
mTestAButtonFragment = new TestAButtonFragment();
|
||||||
|
addFragment(mTestAButtonFragment);
|
||||||
|
}
|
||||||
|
showFragment(mTestAButtonFragment);
|
||||||
|
//setSubtitle(TAG);
|
||||||
|
//ToastUtils.show("onCreate");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
|
||||||
|
super.initDrawerMenuItemList(listDrawerMenu);
|
||||||
|
LogUtils.d(TAG, "initDrawerMenuItemList");
|
||||||
|
//listDrawerMenu.clear();
|
||||||
|
// 添加抽屉菜单项
|
||||||
|
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG));
|
||||||
|
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG));
|
||||||
|
notifyDrawerMenuDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reinitDrawerMenuItemList(ArrayList<DrawerMenuBean> listDrawerMenu) {
|
||||||
|
super.reinitDrawerMenuItemList(listDrawerMenu);
|
||||||
|
LogUtils.d(TAG, "reinitDrawerMenuItemList");
|
||||||
|
//listDrawerMenu.clear();
|
||||||
|
// 添加抽屉菜单项
|
||||||
|
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestAButtonFragment.TAG));
|
||||||
|
listDrawerMenu.add(new DrawerMenuBean(R.drawable.ic_launcher, TestViewPageFragment.TAG));
|
||||||
|
notifyDrawerMenuDataChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DrawerFragmentActivity.ActivityType initActivityType() {
|
||||||
|
return DrawerFragmentActivity.ActivityType.Main;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
|
// if(App.isDebugging()) {
|
||||||
|
// getMenuInflater().inflate(cc.winboll.studio.libaes.R.menu.toolbar_studio_debug, menu);
|
||||||
|
// }
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
super.onItemClick(parent, view, position, id);
|
||||||
|
switch (position) {
|
||||||
|
case 0 : {
|
||||||
|
if (mTestAButtonFragment == null) {
|
||||||
|
mTestAButtonFragment = new TestAButtonFragment();
|
||||||
|
addFragment(mTestAButtonFragment);
|
||||||
|
}
|
||||||
|
showFragment(mTestAButtonFragment);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1 : {
|
||||||
|
if (mTestViewPageFragment == null) {
|
||||||
|
mTestViewPageFragment = new TestViewPageFragment();
|
||||||
|
addFragment(mTestViewPageFragment);
|
||||||
|
}
|
||||||
|
showFragment(mTestViewPageFragment);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int nItemId = item.getItemId();
|
||||||
|
if (item.getItemId() == R.id.item_testactivitymanager) {
|
||||||
|
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, TestActivityManagerActivity.class);
|
||||||
|
//ToastUtils.show("item_testactivitymanager");
|
||||||
|
} else
|
||||||
|
if (nItemId == R.id.item_atoast) {
|
||||||
|
Toast.makeText(getApplication(), "item_testatoast", Toast.LENGTH_SHORT).show();
|
||||||
|
} else if (nItemId == R.id.item_atoolbar) {
|
||||||
|
Intent intent = new Intent(this, TestAToolbarActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
|
||||||
|
} else if (nItemId == R.id.item_asupporttoolbar) {
|
||||||
|
Intent intent = new Intent(this, TestASupportToolbarActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
|
||||||
|
} else if (nItemId == R.id.item_colordialog) {
|
||||||
|
ColorPickerDialog dlg = new ColorPickerDialog(this, getResources().getColor(R.color.colorPrimary));
|
||||||
|
dlg.setOnColorChangedListener(new com.a4455jkjh.colorpicker.view.OnColorChangedListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeColorChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onColorChanged(int color) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterColorChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
dlg.show();
|
||||||
|
|
||||||
|
} else if (nItemId == R.id.item_dialogstoragepath) {
|
||||||
|
final StoragePathDialog dialog = new StoragePathDialog(this, 0);
|
||||||
|
dialog.setOnOKClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.show();
|
||||||
|
|
||||||
|
} else if (nItemId == R.id.item_localfileselectdialog) {
|
||||||
|
final LocalFileSelectDialog dialog = new LocalFileSelectDialog(this);
|
||||||
|
dialog.setOnOKClickListener(new LocalFileSelectDialog.OKClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onOKClick(String sz) {
|
||||||
|
Toast.makeText(getApplication(), sz, Toast.LENGTH_SHORT).show();
|
||||||
|
//dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dialog.open();
|
||||||
|
|
||||||
|
} else if (nItemId == R.id.item_secondarylibraryactivity) {
|
||||||
|
Intent intent = new Intent(this, SecondaryLibraryActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
} else if (nItemId == R.id.item_drawerfragmentactivity) {
|
||||||
|
Intent intent = new Intent(this, TestDrawerFragmentActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
} else if (nItemId == R.id.item_settings) {
|
||||||
|
Intent intent = new Intent(this, SettingsActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
} else if (nItemId == R.id.item_about) {
|
||||||
|
// Intent intent = new Intent(this, AboutActivity.class);
|
||||||
|
// startActivity(intent);
|
||||||
|
WinBoLLActivityManager.getInstance().startWinBoLLActivity(this, AboutActivity.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package cc.winboll.studio.aes;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import cc.winboll.studio.libaes.views.ADsControlView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/11/26 18:01
|
||||||
|
* @Describe SettingsActivity
|
||||||
|
*/
|
||||||
|
public class SettingsActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String TAG = "SettingsActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_settings);
|
||||||
|
ADsControlView adsControlView = (ADsControlView) findViewById(R.id.ads_control_view);
|
||||||
|
|
||||||
|
// adsControlView.setOnAdsModeSelectedListener(new ADsControlView.OnAdsModeSelectedListener() {
|
||||||
|
// @Override
|
||||||
|
// public void onModeSelected(ADsMode selectedMode) {
|
||||||
|
// if (selectedMode == ADsMode.STANDALONE) {
|
||||||
|
// // 处理单机模式逻辑(如释放米盟资源)
|
||||||
|
// ToastUtils.show("STANDALONE");
|
||||||
|
// } else if (selectedMode == ADsMode.MIMO_SDK) {
|
||||||
|
// // 处理米盟SDK模式逻辑(如初始化SDK)
|
||||||
|
// ToastUtils.show("MIMO_SDK");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package cc.winboll.studio.aes;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/09/28 21:07
|
||||||
|
* @Describe 窗口管理类测试窗口
|
||||||
|
*/
|
||||||
|
public class TestActivityManagerActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "TestActivityManagerActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_testactivitymanager);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
aes/src/main/java/cc/winboll/studio/aes/WinBoLLActivity.java
Normal file
60
aes/src/main/java/cc/winboll/studio/aes/WinBoLLActivity.java
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package cc.winboll.studio.aes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||||
|
* @Date 2025/09/29 00:11
|
||||||
|
* @Describe WinBoLL 窗口基础类
|
||||||
|
*/
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||||
|
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "WinBoLLActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Activity getActivity() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTag() {
|
||||||
|
return TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
LogUtils.d(TAG, String.format("onResume %s", getTag()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
/*if (item.getItemId() == R.id.item_log) {
|
||||||
|
WinBoLLActivityManager.getInstance().startLogActivity(this);
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == R.id.item_home) {
|
||||||
|
startActivity(new Intent(this, MainActivity.class));
|
||||||
|
return true;
|
||||||
|
}*/
|
||||||
|
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostCreate(Bundle savedInstanceState) {
|
||||||
|
super.onPostCreate(savedInstanceState);
|
||||||
|
WinBoLLActivityManager.getInstance().add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
WinBoLLActivityManager.getInstance().finish(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
32
aes/src/main/res/drawable-night/bg_frame.xml
Normal file
32
aes/src/main/res/drawable-night/bg_frame.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||||
|
<item
|
||||||
|
android:left="2dp"
|
||||||
|
android:top="2dp"
|
||||||
|
android:right="2dp"
|
||||||
|
android:bottom="2dp">
|
||||||
|
<shape android:shape="rectangle" >
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="#0F000000"
|
||||||
|
android:startColor="#0F000000" />
|
||||||
|
<corners android:radius="?attr/borderCornerRadius" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item
|
||||||
|
android:left="3dp"
|
||||||
|
android:top="3dp"
|
||||||
|
android:right="3dp"
|
||||||
|
android:bottom="5dp">
|
||||||
|
<shape android:shape="rectangle" >
|
||||||
|
<gradient
|
||||||
|
android:angle="270"
|
||||||
|
android:endColor="#00000000"
|
||||||
|
android:startColor="#1AFFFFFF" />
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#FF666666" />
|
||||||
|
<corners android:radius="?attr/borderCornerRadius" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
33
aes/src/main/res/drawable/bg_frame.xml
Normal file
33
aes/src/main/res/drawable/bg_frame.xml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?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="#0F000000"
|
||||||
|
android:startColor="#0F000000" />
|
||||||
|
<corners android:radius="?attr/borderCornerRadius" />
|
||||||
|
</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="#0FFFFFFF"
|
||||||
|
android:startColor="#FFFFFFFF" />
|
||||||
|
<corners android:radius="?attr/borderCornerRadius" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
21
aes/src/main/res/layout/activity_about.xml
Normal file
21
aes/src/main/res/layout/activity_about.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
|
||||||
|
|
||||||
|
<cc.winboll.studio.libaes.views.ASupportToolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/toolbar"/>
|
||||||
|
|
||||||
|
<cc.winboll.studio.libappbase.views.AboutView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:id="@+id/aboutview"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
17
aes/src/main/res/layout/activity_settings.xml
Normal file
17
aes/src/main/res/layout/activity_settings.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
|
||||||
|
|
||||||
|
<cc.winboll.studio.libaes.views.ADsControlView
|
||||||
|
android:id="@+id/ads_control_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_frame"
|
||||||
|
android:padding="10dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
15
aes/src/main/res/layout/activity_testactivitymanager.xml
Normal file
15
aes/src/main/res/layout/activity_testactivitymanager.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" android:background="@drawable/bg_container_border">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="窗口管理类测试窗口"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
41
aes/src/main/res/menu/toolbar_main.xml
Normal file
41
aes/src/main/res/menu/toolbar_main.xml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_testactivitymanager"
|
||||||
|
android:title="TestActivityManager"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_log"
|
||||||
|
android:title="LogActivity"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_colordialog"
|
||||||
|
android:title="ColorDialog"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_dialogstoragepath"
|
||||||
|
android:title="StoragePathDialog"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_localfileselectdialog"
|
||||||
|
android:title="LocalFileSelectDialog"/>
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_atoolbar"
|
||||||
|
android:title="Test AToolbar"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_asupporttoolbar"
|
||||||
|
android:title="Test ASupportToolbar"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_atoast"
|
||||||
|
android:title="Test AToast"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_secondarylibraryactivity"
|
||||||
|
android:title="Test SecondaryLibraryActivity"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_drawerfragmentactivity"
|
||||||
|
android:title="Test DrawerFragmentActivity"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_settings"
|
||||||
|
android:title="Settings"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_about"
|
||||||
|
android:title="About"/>
|
||||||
|
</menu>
|
||||||
16
aes/src/main/res/values-night/styles.xml
Normal file
16
aes/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="MyAESTheme" parent="AESTheme">
|
||||||
|
<item name="themeDebug">@style/MyDebugActivityTheme</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.NoActionBar">
|
||||||
|
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
|
||||||
|
<item name="colorTittle">@color/mainWindowTextColor</item>
|
||||||
|
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
|
||||||
|
<item name="colorText">@color/debugTextColor</item>
|
||||||
|
<item name="colorTextBackgound">@color/mainWindowBackgroundColor</item>
|
||||||
|
<item name="debugTextColor">@color/debugTextColor</item>
|
||||||
|
<item name="toolbarTextColor">@color/toolbarTextColor</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
7
aes/src/main/res/values/colors.xml
Normal file
7
aes/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#FF00B322</color>
|
||||||
|
<color name="colorPrimaryDark">#FF005C12</color>
|
||||||
|
<color name="colorAccent">#FF8DFFA2</color>
|
||||||
|
<color name="colorText">#FFFFFB8D</color>
|
||||||
|
</resources>
|
||||||
7
aes/src/main/res/values/strings.xml
Normal file
7
aes/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">AES</string>
|
||||||
|
<string name="app_description">WinBoLL AndroidX 可视化元素类库。</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
16
aes/src/main/res/values/styles.xml
Normal file
16
aes/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="MyAESTheme" parent="AESTheme">
|
||||||
|
<item name="themeDebug">@style/MyDebugActivityTheme</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="MyDebugActivityTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||||
|
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
|
||||||
|
<item name="colorTittle">@color/mainWindowTextColor</item>
|
||||||
|
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
|
||||||
|
<item name="colorText">@color/debugTextColor</item>
|
||||||
|
<item name="colorTextBackgound">@color/mainWindowBackgroundColor</item>
|
||||||
|
<item name="debugTextColor">@color/debugTextColor</item>
|
||||||
|
<item name="toolbarTextColor">@color/toolbarTextColor</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
6
aes/src/main/res/xml/network_security_config.xml
Normal file
6
aes/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<network-security-config>
|
||||||
|
<domain-config cleartextTrafficPermitted="true">
|
||||||
|
<domain includeSubdomains="true">winboll.cc</domain>
|
||||||
|
</domain-config>
|
||||||
|
</network-security-config>
|
||||||
12
aes/src/stage/AndroidManifest.xml
Normal file
12
aes/src/stage/AndroidManifest.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
||||||
6
aes/src/stage/res/values/strings.xml
Normal file
6
aes/src/stage/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Put flavor specific strings here -->
|
||||||
|
|
||||||
|
</resources>
|
||||||
1
appbase/.gitignore
vendored
Normal file
1
appbase/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
36
appbase/README.md
Normal file
36
appbase/README.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# APPBase
|
||||||
|
[](https://jitpack.io/#ZhanGSKen/APPBase)
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
WinBoLL 安卓手机端安卓应用开发基础类库。
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||||
|
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||||
|
|
||||||
|
|
||||||
|
#### Gradle 编译说明
|
||||||
|
调试版编译命令 :gradle assembleBetaDebug
|
||||||
|
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh appbase
|
||||||
|
阶段版类库发布命令 :git pull &&bash .winboll/bashPublishLIBAddTag.sh libappbase
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 新建 Feat_xxx 分支
|
||||||
|
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
|
||||||
|
4. 新建 Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### 特技
|
||||||
|
|
||||||
|
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||||
|
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||||
|
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
|
|
||||||
|
#### 参考文档
|
||||||
1
appbase/app_update_description.txt
Normal file
1
appbase/app_update_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
50
appbase/build.gradle
Normal file
50
appbase/build.gradle
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply from: '../.winboll/winboll_app_build.gradle'
|
||||||
|
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||||
|
|
||||||
|
def genVersionName(def versionName){
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
// 保存基础版本号
|
||||||
|
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
// 返回编译版本号
|
||||||
|
return "${versionName}." + winbollBuildProps['stageCount']
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
// 适配MIUI12
|
||||||
|
compileSdkVersion 30
|
||||||
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "cc.winboll.studio.appbase"
|
||||||
|
minSdkVersion 26
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
// versionName 更新后需要手动设置
|
||||||
|
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||||
|
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||||
|
versionName "15.20"
|
||||||
|
if(true) {
|
||||||
|
versionName = genVersionName("${versionName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保 Java 7 兼容性(已适配项目技术栈)
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_7
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_7
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api project(':libappbase')
|
||||||
|
|
||||||
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
}
|
||||||
8
appbase/build.properties
Normal file
8
appbase/build.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
|
#Tue May 12 09:17:15 HKT 2026
|
||||||
|
stageCount=10
|
||||||
|
libraryProject=libappbase
|
||||||
|
baseVersion=15.20
|
||||||
|
publishVersion=15.20.9
|
||||||
|
buildCount=0
|
||||||
|
baseBetaVersion=15.20.10
|
||||||
126
appbase/proguard-rules.pro
vendored
Normal file
126
appbase/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# ============================== 基础通用规则 ==============================
|
||||||
|
# 保留系统组件
|
||||||
|
-keep public class * extends android.app.Activity
|
||||||
|
-keep public class * extends android.app.Service
|
||||||
|
-keep public class * extends android.content.BroadcastReceiver
|
||||||
|
-keep public class * extends android.content.ContentProvider
|
||||||
|
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||||
|
-keep public class * extends android.preference.Preference
|
||||||
|
|
||||||
|
# 保留 WinBoLL 核心包及子类(适配你的两个包名)
|
||||||
|
#-keep public class * extends com.winboll.WinBoLLActivity
|
||||||
|
#-keep public class * extends com.winboll.WinBoLLFragment
|
||||||
|
# 主包名
|
||||||
|
-keep class cc.winboll.studio.*.** { *; }
|
||||||
|
# beta包名
|
||||||
|
-keep class cc.winboll.studio.*.beta.** { *; }
|
||||||
|
-keepclassmembers class cc.winboll.studio.*.** { *; }
|
||||||
|
-keepclassmembers class cc.winboll.studio.*.beta.** { *; }
|
||||||
|
|
||||||
|
# 保留所有类中的 public static final String TAG 字段
|
||||||
|
-keepclassmembers class * {
|
||||||
|
public static final java.lang.String TAG;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留序列化类
|
||||||
|
-keep class * implements android.os.Parcelable {
|
||||||
|
public static final android.os.Parcelable$Creator *;
|
||||||
|
}
|
||||||
|
-keepclassmembers class * implements java.io.Serializable {
|
||||||
|
static final long serialVersionUID;
|
||||||
|
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||||
|
private void writeObject(java.io.ObjectOutputStream);
|
||||||
|
private void readObject(java.io.ObjectInputStream);
|
||||||
|
java.lang.Object writeReplace();
|
||||||
|
java.lang.Object readResolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留 R 文件
|
||||||
|
-keepclassmembers class **.R$* {
|
||||||
|
public static <fields>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留 native 方法
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
native <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保留注解和泛型
|
||||||
|
-keepattributes *Annotation*
|
||||||
|
-keepattributes Signature
|
||||||
|
|
||||||
|
# 屏蔽 Java 8+ 警告(适配 Java 7)
|
||||||
|
-dontwarn java.lang.invoke.*
|
||||||
|
-dontwarn android.support.v8.renderscript.*
|
||||||
|
-dontwarn java.util.function.**
|
||||||
|
|
||||||
|
# ============================== 第三方框架规则 ==============================
|
||||||
|
# Retrofit + OkHttp
|
||||||
|
-keep class retrofit2.** { *; }
|
||||||
|
-keep interface retrofit2.** { *; }
|
||||||
|
-keep class okhttp3.** { *; }
|
||||||
|
-keep interface okhttp3.** { *; }
|
||||||
|
-keep class okio.** { *; }
|
||||||
|
-keepclasseswithmembers class * {
|
||||||
|
@retrofit2.http.* <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Glide 4.x
|
||||||
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
|
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||||
|
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
|
||||||
|
**[] $VALUES;
|
||||||
|
public *;
|
||||||
|
}
|
||||||
|
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||||
|
|
||||||
|
# GreenDAO 3.x
|
||||||
|
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
|
||||||
|
public static java.lang.String TABLENAME;
|
||||||
|
}
|
||||||
|
-keep class **$Properties
|
||||||
|
# 实体类包名(按实际调整)
|
||||||
|
#-keep class cc.winboll.studio.appbase.model.** { *; }
|
||||||
|
|
||||||
|
# ButterKnife 8.x
|
||||||
|
-keep class butterknife.** { *; }
|
||||||
|
-dontwarn butterknife.internal.**
|
||||||
|
-keep class **$$ViewBinder { *; }
|
||||||
|
-keepclasseswithmembernames class * {
|
||||||
|
@butterknife.BindView <fields>;
|
||||||
|
@butterknife.OnClick <methods>;
|
||||||
|
}
|
||||||
|
|
||||||
|
# EventBus 3.x
|
||||||
|
-keepclassmembers class ** {
|
||||||
|
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||||
|
}
|
||||||
|
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||||
|
|
||||||
|
# ============================== 优化与调试 ==============================
|
||||||
|
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||||
|
-optimizationpasses 5
|
||||||
|
-verbose
|
||||||
|
-dontpreverify
|
||||||
|
-dontusemixedcaseclassnames
|
||||||
|
# 保留行号(便于崩溃定位)
|
||||||
|
-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
14
appbase/src/beta/AndroidManifest.xml
Normal file
14
appbase/src/beta/AndroidManifest.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application
|
||||||
|
tools:replace="android:icon"
|
||||||
|
android:icon="@drawable/ic_winboll_beta">
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
||||||
6
appbase/src/beta/res/values/strings.xml
Normal file
6
appbase/src/beta/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">APPBase+</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
57
appbase/src/main/AndroidManifest.xml
Normal file
57
appbase/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="cc.winboll.studio.appbase">
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:name=".App"
|
||||||
|
android:icon="@drawable/ic_winboll"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/MyAPPBaseTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:process=":App"
|
||||||
|
android:sharedUserId="@string/shared_user_id"
|
||||||
|
android:sharedUserLabel="@string/shared_user_label">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:exported="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".Main2Activity"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:exported="true"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity="cc.winboll.studio.appbase.Main2Activity"
|
||||||
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
|
||||||
|
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".GlobalApplication$CrashActivity"/>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.max_aspect"
|
||||||
|
android:value="4.0"/>
|
||||||
|
|
||||||
|
<activity android:name="cc.winboll.studio.libappbase.activities.CrashCopyReceiverActivity"/>
|
||||||
|
|
||||||
|
<activity android:name=".AboutActivity"/>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package cc.winboll.studio.appbase;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.appbase.R;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.models.APPInfo;
|
||||||
|
import cc.winboll.studio.libappbase.views.AboutView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/11 12:55
|
||||||
|
* @Describe AboutActivity
|
||||||
|
*/
|
||||||
|
public class AboutActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String TAG = "AboutActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_about);
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setActionBar(toolbar);
|
||||||
|
|
||||||
|
AboutView aboutView = findViewById(R.id.aboutview);
|
||||||
|
aboutView.setAPPInfo(genDefaultAppInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private APPInfo genDefaultAppInfo() {
|
||||||
|
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
|
||||||
|
String branchName = "appbase";
|
||||||
|
APPInfo appInfo = new APPInfo();
|
||||||
|
appInfo.setAppName("APPBase");
|
||||||
|
appInfo.setAppIcon(R.drawable.ic_winboll);
|
||||||
|
appInfo.setAppDescription(getString(R.string.app_description));
|
||||||
|
appInfo.setAppGitName("WinBoLL");
|
||||||
|
appInfo.setAppGitOwner("Studio");
|
||||||
|
appInfo.setAppGitAPPBranch(branchName);
|
||||||
|
appInfo.setAppGitAPPSubProjectFolder(branchName);
|
||||||
|
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=APPBase");
|
||||||
|
appInfo.setAppAPKName("APPBase");
|
||||||
|
appInfo.setAppAPKFolderName("APPBase");
|
||||||
|
LogUtils.d(TAG, "genDefaultAppInfo: 应用信息已生成");
|
||||||
|
return appInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
appbase/src/main/java/cc/winboll/studio/appbase/App.java
Normal file
48
appbase/src/main/java/cc/winboll/studio/appbase/App.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package cc.winboll.studio.appbase;
|
||||||
|
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
import cc.winboll.studio.libappbase.BuildConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2025/01/05 09:54:42
|
||||||
|
* @Describe 应用全局入口类(继承基础库 GlobalApplication)
|
||||||
|
* 负责应用初始化、全局资源管理与生命周期回调处理,是整个应用的核心入口
|
||||||
|
*/
|
||||||
|
public class App extends GlobalApplication {
|
||||||
|
|
||||||
|
/** 当前应用类的日志 TAG(用于调试输出,标识日志来源) */
|
||||||
|
public static final String TAG = "App";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用创建时回调(全局初始化入口)
|
||||||
|
* 在应用进程启动时执行,仅调用一次,用于初始化全局工具类、第三方库等
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
// 如果应用不在调试状态,就根据编译类型设置调试状态
|
||||||
|
if (isDebugging() != true) {
|
||||||
|
setIsDebugging(BuildConfig.DEBUG);
|
||||||
|
}
|
||||||
|
// release 版调试码
|
||||||
|
//setIsDebugging(!BuildConfig.DEBUG);
|
||||||
|
|
||||||
|
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
|
||||||
|
ToastUtils.init(getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应用终止时回调(资源释放入口)
|
||||||
|
* 仅在模拟环境(如 Android Studio 模拟器)中可靠触发,真机上可能因系统回收进程不执行
|
||||||
|
* 用于释放全局资源,避免内存泄漏
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void onTerminate() {
|
||||||
|
super.onTerminate(); // 调用父类终止逻辑(如基础库资源释放)
|
||||||
|
// 释放 Toast 工具类资源(销毁全局 Toast 实例,避免内存泄漏)
|
||||||
|
ToastUtils.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package cc.winboll.studio.appbase;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
|
||||||
|
public class CrashTestActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String TAG = "CrashTestActivity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_crash_test);
|
||||||
|
LogUtils.d(TAG, "CrashTestActivity onCreate()");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onBack(View view) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTestCrash(View view) {
|
||||||
|
LogUtils.d(TAG, "onTestCrash()");
|
||||||
|
ToastUtils.show("测试布局崩溃...");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package cc.winboll.studio.appbase;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.appbase.R;
|
||||||
|
|
||||||
|
public class Main2Activity extends MainActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "Main2Activity";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main2);
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
if (toolbar != null) {
|
||||||
|
setActionBar(toolbar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package cc.winboll.studio.appbase;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.appbase.R;
|
||||||
|
import cc.winboll.studio.appbase.model.TestBean;
|
||||||
|
import cc.winboll.studio.libappbase.LogActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 未标注(建议补充创建日期)
|
||||||
|
* @Describe 应用主界面 Activity(入口界面)
|
||||||
|
* 包含功能测试按钮(崩溃测试、日志查看、Toast测试)、顶部工具栏(菜单功能),是应用交互的核心入口
|
||||||
|
*/
|
||||||
|
public class MainActivity extends Activity {
|
||||||
|
|
||||||
|
/** 当前 Activity 的日志 TAG(用于调试输出,标识日志来源) */
|
||||||
|
public static final String TAG = "MainActivity";
|
||||||
|
|
||||||
|
/** 顶部工具栏(用于展示标题、菜单,绑定布局中的 Toolbar 控件) */
|
||||||
|
private Toolbar mToolbar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity 创建时回调(初始化界面)
|
||||||
|
* 在 Activity 首次创建时执行,用于加载布局、初始化控件、设置事件监听
|
||||||
|
* @param savedInstanceState 保存 Activity 状态的 Bundle(如屏幕旋转时的数据恢复)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
//ToastUtils.show("onCreate"); // 显示 Activity 创建提示(调试用)
|
||||||
|
setContentView(R.layout.activity_main); // 加载主界面布局
|
||||||
|
|
||||||
|
// 初始化 Toolbar 并设置为 ActionBar
|
||||||
|
mToolbar = findViewById(R.id.toolbar);
|
||||||
|
setActionBar(mToolbar); // 将 Toolbar 替代系统默认 ActionBar
|
||||||
|
|
||||||
|
initTestData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initTestData() {
|
||||||
|
TestBean bean1 = new TestBean();
|
||||||
|
bean1.setTestNum1(456);
|
||||||
|
TestBean.saveBeanToFile(getFilesDir().getAbsolutePath() + getTestBeanRelativePath(), bean1);
|
||||||
|
TestBean bean2 = new TestBean();
|
||||||
|
bean2.setTestNum1(789);
|
||||||
|
TestBean.saveBeanToFile(getExternalFilesDir(null).getAbsolutePath() + getTestBeanRelativePath(), bean2);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTestBeanRelativePath() {
|
||||||
|
return "/BaseBaen/"+TestBean.class.getName()+".json";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建菜单时回调(加载工具栏菜单)
|
||||||
|
* 初始化 ActionBar 菜单,加载自定义菜单布局
|
||||||
|
* @param menu 菜单对象(用于承载菜单项)
|
||||||
|
* @return true:显示菜单;false:不显示菜单
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
// 加载菜单布局(R.menu.toolbar_main 为自定义菜单文件)
|
||||||
|
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单 item 点击时回调(处理菜单事件)
|
||||||
|
* 响应 Toolbar 菜单项的点击事件,执行对应业务逻辑
|
||||||
|
* @param item 被点击的菜单项
|
||||||
|
* @return true:消费点击事件;false:不消费(传递给父类)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.item_home:
|
||||||
|
// 点击 "首页/官网" 菜单项,唤起浏览器打开指定网站
|
||||||
|
openWebsiteInBrowser(this);
|
||||||
|
break;
|
||||||
|
// 可扩展其他菜单项(如设置、关于等)的处理逻辑
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 崩溃测试按钮点击事件(触发应用崩溃,用于调试异常捕获)
|
||||||
|
* 故意执行非法操作(循环获取不存在的字符串资源),强制应用崩溃
|
||||||
|
* @param view 触发事件的 View(对应布局中的崩溃测试按钮)
|
||||||
|
*/
|
||||||
|
public void onCrashTest(View view) {
|
||||||
|
// 循环从 Integer.MIN_VALUE 到 Integer.MAX_VALUE,获取不存在的字符串资源 ID,触发崩溃
|
||||||
|
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
|
||||||
|
getString(i); // i 超出资源 ID 范围,抛出 Resources.NotFoundException 导致崩溃
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onLogTestNewTask(View view) {
|
||||||
|
LogActivity.startLogActivity(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志测试按钮点击事件(打开日志查看界面)
|
||||||
|
* 启动 LogActivity,用于查看应用运行日志
|
||||||
|
* @param view 触发事件的 View(对应布局中的日志测试按钮)
|
||||||
|
*/
|
||||||
|
public void onLogTest(View view) {
|
||||||
|
LogActivity.startLogActivity(this, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toast 工具测试按钮点击事件(测试全局 Toast 功能)
|
||||||
|
* 测试主线程、子线程中 Toast 的显示效果,验证 ToastUtils 的可用性
|
||||||
|
* @param view 触发事件的 View(对应布局中的 Toast 测试按钮)
|
||||||
|
*/
|
||||||
|
public void onToastUtilsTest(View view) {
|
||||||
|
LogUtils.d(TAG, "onToastUtilsTest"); // 打印调试日志,标识进入 Toast 测试
|
||||||
|
ToastUtils.show("Hello, WinBoLL!"); // 主线程显示 Toast
|
||||||
|
|
||||||
|
// 开启子线程,延迟 2 秒后显示 Toast(测试子线程 Toast 兼容性)
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
Thread.sleep(2000); // 线程休眠 2 秒
|
||||||
|
// 若 ToastUtils 已处理主线程切换,此处可直接调用;否则需通过 Handler 切换到主线程
|
||||||
|
ToastUtils.show("Thread.sleep(2000);ToastUtils.show...");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// 捕获线程中断异常(如线程被销毁时),不做处理(测试场景)
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 唤起系统默认浏览器打开指定网站(跳转至应用官网)
|
||||||
|
* 通过 Intent.ACTION_VIEW 隐式意图,触发浏览器打开目标 URL
|
||||||
|
* @param context 上下文对象(如 Activity、Application,此处为 MainActivity)
|
||||||
|
*/
|
||||||
|
public void openWebsiteInBrowser(Context context) {
|
||||||
|
String url = "https://www.winboll.cc"; // 目标网站 URL(应用官网)
|
||||||
|
// 构建隐式意图:ACTION_VIEW 表示查看指定数据(Uri 为网站地址)
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||||
|
// 设置标志:在新的任务栈中启动 Activity(避免与当前应用任务栈混淆)
|
||||||
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
// 启动意图(唤起浏览器)
|
||||||
|
context.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAboutActivity(View view) {
|
||||||
|
LogUtils.d(TAG, "onAboutActivity() 调用");
|
||||||
|
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
|
||||||
|
startActivity(aboutIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void onMultiInstance(View view) {
|
||||||
|
LogUtils.d(TAG, "onMultiInstance() 多开窗口按钮已点击");
|
||||||
|
ToastUtils.show("多开窗口:已启动新窗口");
|
||||||
|
android.content.Intent intent = new android.content.Intent(this, Main2Activity.class);
|
||||||
|
intent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
LogUtils.d(TAG, "onMultiInstance() 准备启动Main2Activity");
|
||||||
|
startActivity(intent);
|
||||||
|
LogUtils.d(TAG, "onMultiInstance() Main2Activity已启动");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package cc.winboll.studio.appbase.model;
|
||||||
|
|
||||||
|
import android.util.JsonReader;
|
||||||
|
import android.util.JsonWriter;
|
||||||
|
import cc.winboll.studio.libappbase.BaseBean;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试实体类
|
||||||
|
* 继承BaseBean实现JSON序列化/反序列化能力,提供基础int类型属性的封装与数据持久化支持
|
||||||
|
* 适配Java7语法,遵循BaseBean统一的反射识别、JSON读写规范
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/01/31 19:16:00
|
||||||
|
* @LastEditTime 2026/02/01 10:46:00
|
||||||
|
*/
|
||||||
|
public class TestBean extends BaseBean {
|
||||||
|
|
||||||
|
// ====================================== 常量定义 ======================================
|
||||||
|
/** 当前类的日志 TAG(用于调试输出) */
|
||||||
|
public static final String TAG = "TestBean";
|
||||||
|
|
||||||
|
// ====================================== 成员属性 ======================================
|
||||||
|
/**
|
||||||
|
* 测试数字属性(默认值:123)
|
||||||
|
* 基础int类型属性,用于测试BaseBean的JSON序列化/反序列化能力
|
||||||
|
*/
|
||||||
|
private int testNum1;
|
||||||
|
|
||||||
|
// ====================================== 构造方法 ======================================
|
||||||
|
/**
|
||||||
|
* 无参构造器(默认初始化)
|
||||||
|
* 给testNum1赋值默认值123,满足反射实例化、JSON解析的无参构造要求
|
||||||
|
*/
|
||||||
|
public TestBean() {
|
||||||
|
this.testNum1 = 123;
|
||||||
|
LogUtils.d(TAG, "TestBean无参构造器调用,testNum1默认初始化值:" + this.testNum1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有参构造器(自定义初始化)
|
||||||
|
* @param testNum1 测试数字初始值
|
||||||
|
*/
|
||||||
|
public TestBean(int testNum1) {
|
||||||
|
this.testNum1 = testNum1;
|
||||||
|
LogUtils.d(TAG, "TestBean有参构造器调用,传入testNum1:" + testNum1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== Get/Set 方法 ======================================
|
||||||
|
/**
|
||||||
|
* 设置测试数字属性值
|
||||||
|
* @param testNum1 待设置的int类型值
|
||||||
|
*/
|
||||||
|
public void setTestNum1(int testNum1) {
|
||||||
|
LogUtils.d(TAG, "setTestNum1调用,传入参数:" + testNum1);
|
||||||
|
this.testNum1 = testNum1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取测试数字属性值
|
||||||
|
* @return 当前testNum1的int类型值
|
||||||
|
*/
|
||||||
|
public int getTestNum1() {
|
||||||
|
LogUtils.d(TAG, "getTestNum1调用,返回值:" + this.testNum1);
|
||||||
|
return testNum1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====================================== 重写父类BaseBean方法 ======================================
|
||||||
|
/**
|
||||||
|
* 重写父类方法:获取当前类的全限定名
|
||||||
|
* 用于BaseBean反射识别、类名匹配等统一逻辑
|
||||||
|
* @return 类全限定名(cc.winboll.studio.appbase.model.TestBean)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
LogUtils.d(TAG, "getName方法调用,返回类全限定名:" + TestBean.class.getName());
|
||||||
|
return TestBean.class.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写父类方法:将当前对象序列化为JSON(持久化存储专用)
|
||||||
|
* 遵循BaseBean规范,先执行父类序列化逻辑,再处理子类专属字段
|
||||||
|
* @param jsonWriter JSON写入器(外部传入的JSON流操作实例)
|
||||||
|
* @throws IOException JSON写入异常(流关闭、格式错误等)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||||
|
LogUtils.d(TAG, "writeThisToJsonWriter调用,传入参数JsonWriter:" + jsonWriter);
|
||||||
|
// 执行父类公共字段的序列化逻辑
|
||||||
|
super.writeThisToJsonWriter(jsonWriter);
|
||||||
|
// 序列化子类专属字段testNum1
|
||||||
|
jsonWriter.name("testNum1").value(this.getTestNum1());
|
||||||
|
LogUtils.d(TAG, "writeThisToJsonWriter执行完成,已序列化testNum1:" + this.getTestNum1());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写父类方法:从JSON字段初始化当前对象属性(解析JSON专用)
|
||||||
|
* 先让父类处理公共字段,再匹配子类专属字段,不匹配则返回false跳过
|
||||||
|
* @param jsonReader JSON读取器(外部传入的JSON流操作实例)
|
||||||
|
* @param name 当前解析的JSON字段名
|
||||||
|
* @return true-字段解析成功;false-字段不匹配,需跳过/父类处理
|
||||||
|
* @throws IOException JSON读取异常(字段类型不匹配、流中断等)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||||
|
LogUtils.d(TAG, "initObjectsFromJsonReader调用,传入参数:name=" + name + ",JsonReader=" + jsonReader);
|
||||||
|
// 父类优先处理公共字段,处理成功则直接返回
|
||||||
|
if (super.initObjectsFromJsonReader(jsonReader, name)) {
|
||||||
|
LogUtils.d(TAG, "initObjectsFromJsonReader:字段" + name + "由父类BaseBean处理成功");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 解析子类专属字段
|
||||||
|
if ("testNum1".equals(name)) {
|
||||||
|
this.setTestNum1(jsonReader.nextInt());
|
||||||
|
LogUtils.d(TAG, "initObjectsFromJsonReader:解析testNum1成功,值为:" + this.getTestNum1());
|
||||||
|
} else {
|
||||||
|
LogUtils.w(TAG, "initObjectsFromJsonReader:字段" + name + "不匹配,返回false跳过解析");
|
||||||
|
// 字段不匹配,返回false表示跳过
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重写父类方法:从JSON读取器完整解析并初始化当前对象(JSON解析入口)
|
||||||
|
* 负责JSON对象的开始/结束标识处理,遍历所有字段并调用字段解析方法
|
||||||
|
* 严格遵循writeThisToJsonWriter的序列化结构,保证解析一致性
|
||||||
|
* @param jsonReader JSON读取器(外部传入的JSON流操作实例)
|
||||||
|
* @return 解析后的当前TestBean实例(支持链式调用)
|
||||||
|
* @throws IOException JSON解析异常(格式错误、字段缺失、流异常等)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||||
|
LogUtils.d(TAG, "readBeanFromJsonReader调用,传入参数JsonReader:" + jsonReader);
|
||||||
|
// 开始解析JSON对象,与序列化结构保持一致
|
||||||
|
jsonReader.beginObject();
|
||||||
|
// 遍历所有JSON字段
|
||||||
|
while (jsonReader.hasNext()) {
|
||||||
|
String fieldName = jsonReader.nextName();
|
||||||
|
LogUtils.d(TAG, "readBeanFromJsonReader:开始解析字段,fieldName=" + fieldName);
|
||||||
|
// 解析字段,不匹配则跳过该值
|
||||||
|
if (!this.initObjectsFromJsonReader(jsonReader, fieldName)) {
|
||||||
|
jsonReader.skipValue();
|
||||||
|
LogUtils.w(TAG, "readBeanFromJsonReader:字段" + fieldName + "解析失败,已跳过该值");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 结束JSON对象解析,必须调用避免流异常
|
||||||
|
jsonReader.endObject();
|
||||||
|
LogUtils.d(TAG, "readBeanFromJsonReader执行完成,JSON解析结束,当前TestBean实例testNum1:" + this.getTestNum1());
|
||||||
|
// 返回当前实例,支持链式调用
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
6
appbase/src/main/res/drawable/bg_container_border.xml
Normal file
6
appbase/src/main/res/drawable/bg_container_border.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||||
|
<solid android:color="@android:color/transparent" />
|
||||||
|
<stroke android:width="1dp" android:color="#FFB0B0B0" />
|
||||||
|
<corners android:radius="?attr/borderCornerRadius" />
|
||||||
|
</shape>
|
||||||
7
appbase/src/main/res/drawable/btn_light_blue.xml
Normal file
7
appbase/src/main/res/drawable/btn_light_blue.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="#81C7F5"/> <!-- 浅蓝色填充 -->
|
||||||
|
<corners android:radius="8dp"/> <!-- 8dp 圆角 -->
|
||||||
|
</shape>
|
||||||
|
|
||||||
22
appbase/src/main/res/layout-night/activity_about.xml
Normal file
22
appbase/src/main/res/layout-night/activity_about.xml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/activityBackgroundColor">
|
||||||
|
|
||||||
|
<android.widget.Toolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:id="@+id/toolbar"/>
|
||||||
|
|
||||||
|
<cc.winboll.studio.libappbase.views.AboutView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:id="@+id/aboutview"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
106
appbase/src/main/res/layout-night/activity_main.xml
Normal file
106
appbase/src/main/res/layout-night/activity_main.xml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:background="?attr/activityBackgroundColor">
|
||||||
|
|
||||||
|
<android.widget.Toolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:id="@+id/toolbar"/>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:spacing="12dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="关于应用"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onAboutActivity"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用崩溃测试"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onCrashTest"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用日志测试"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onLogTest"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用日志测试(新窗口)"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onLogTestNewTask"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用吐司测试"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onToastUtilsTest"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="多开窗口"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onMultiInstance"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
17
appbase/src/main/res/layout-night/activity_main2.xml
Normal file
17
appbase/src/main/res/layout-night/activity_main2.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@android:color/white">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Main2Activity"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textColor="@color/gray_900"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
23
appbase/src/main/res/layout/activity_about.xml
Normal file
23
appbase/src/main/res/layout/activity_about.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="?attr/activityBackgroundColor">
|
||||||
|
|
||||||
|
<android.widget.Toolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:id="@+id/toolbar"/>
|
||||||
|
|
||||||
|
<cc.winboll.studio.libappbase.views.AboutView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0"
|
||||||
|
android:id="@+id/aboutview"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
60
appbase/src/main/res/layout/activity_crash_test.xml
Normal file
60
appbase/src/main/res/layout/activity_crash_test.xml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:background="?attr/activityBackgroundColor">
|
||||||
|
|
||||||
|
<android.widget.Toolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:id="@+id/toolbar"/>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<cc.winboll.studio.appbase.UndefinedCustomView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="返回"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onBack"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="测试崩溃"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onTestCrash"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
106
appbase/src/main/res/layout/activity_main.xml
Normal file
106
appbase/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:background="?attr/activityBackgroundColor">
|
||||||
|
|
||||||
|
<android.widget.Toolbar
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:id="@+id/toolbar"/>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1.0">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:spacing="12dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="关于应用"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onAboutActivity"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用崩溃测试"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onCrashTest"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用日志测试"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onLogTest"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用日志测试(新窗口)"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onLogTestNewTask"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="应用吐司测试"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onToastUtilsTest"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="多开窗口"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="?attr/activityTextColor"
|
||||||
|
android:background="?attr/toolbarBackgroundColor"
|
||||||
|
android:paddingVertical="12dp"
|
||||||
|
android:layout_marginHorizontal="24dp"
|
||||||
|
android:onClick="onMultiInstance"
|
||||||
|
android:layout_margin="10dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
17
appbase/src/main/res/layout/activity_main2.xml
Normal file
17
appbase/src/main/res/layout/activity_main2.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="?attr/activityBackgroundColor">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Main2Activity"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textColor="?attr/activityTextColor"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
9
appbase/src/main/res/layout/activity_nfcrsaoperate.xml
Normal file
9
appbase/src/main/res/layout/activity_nfcrsaoperate.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
9
appbase/src/main/res/menu/toolbar_main.xml
Normal file
9
appbase/src/main/res/menu/toolbar_main.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item
|
||||||
|
android:id="@+id/item_home"
|
||||||
|
android:title="Home"
|
||||||
|
android:icon="@drawable/ic_winboll"
|
||||||
|
android:showAsAction="always"/>
|
||||||
|
</menu>
|
||||||
7
appbase/src/main/res/values-night/colors.xml
Normal file
7
appbase/src/main/res/values-night/colors.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#FF1B8B29</color>
|
||||||
|
<color name="colorPrimaryDark">#FF0A5520</color>
|
||||||
|
<color name="colorAccent">#FF6EE87C</color>
|
||||||
|
<color name="colorText">#FFB8FF7D</color>
|
||||||
|
</resources>
|
||||||
14
appbase/src/main/res/values-night/styles.xml
Normal file
14
appbase/src/main/res/values-night/styles.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="MyAPPBaseTheme" parent="APPBaseTheme">
|
||||||
|
<item name="themeDebug">@style/MyDebugActivityTheme</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="MyDebugActivityTheme" parent="DebugActivityTheme">
|
||||||
|
<item name="colorTittle">?attr/mainWindowDarkTextColor</item>
|
||||||
|
<item name="colorTittleBackgound">?attr/toolbarBackgroundColor</item>
|
||||||
|
<item name="colorText">?attr/debugTextColor</item>
|
||||||
|
<item name="colorTextBackgound">?attr/mainWindowDarkBackgroundColor</item>
|
||||||
|
<item name="toolbarTextColor">@color/toolbarTextColor</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
16
appbase/src/main/res/values/attrs.xml
Normal file
16
appbase/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<attr name="borderCornerRadius" format="dimension" />
|
||||||
|
<declare-styleable name="AboutView">
|
||||||
|
<attr name="app_name" format="string" />
|
||||||
|
<attr name="app_apkfoldername" format="string" />
|
||||||
|
<attr name="app_apkname" format="string" />
|
||||||
|
<attr name="app_gitname" format="string" />
|
||||||
|
<attr name="app_gitowner" format="string" />
|
||||||
|
<attr name="app_gitappbranch" format="string" />
|
||||||
|
<attr name="app_gitappsubprojectfolder" format="string" />
|
||||||
|
<attr name="appdescription" format="string" />
|
||||||
|
<attr name="appicon" format="reference" />
|
||||||
|
<attr name="is_adddebugtools" format="boolean" />
|
||||||
|
</declare-styleable>
|
||||||
|
</resources>
|
||||||
7
appbase/src/main/res/values/colors.xml
Normal file
7
appbase/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#FF00B322</color>
|
||||||
|
<color name="colorPrimaryDark">#FF005C12</color>
|
||||||
|
<color name="colorAccent">#FF8DFFA2</color>
|
||||||
|
<color name="colorText">#FFFFFB8D</color>
|
||||||
|
</resources>
|
||||||
9
appbase/src/main/res/values/strings.xml
Normal file
9
appbase/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">APPBase</string>
|
||||||
|
<string name="app_description">WinBoLL 安卓手机端安卓应用开发基础类库。</string>
|
||||||
|
<string name="app_normal">Click here is switch to Normal APP</string>
|
||||||
|
<string name="app_debug">Click here is switch to APP DEBUG</string>
|
||||||
|
<string name="gitea_home">GITEA HOME</string>
|
||||||
|
<string name="app_update">APP UPDATE</string>
|
||||||
|
</resources>
|
||||||
14
appbase/src/main/res/values/styles.xml
Normal file
14
appbase/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<style name="MyAPPBaseTheme" parent="APPBaseTheme">
|
||||||
|
<item name="themeDebug">@style/MyDebugActivityTheme</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="MyDebugActivityTheme" parent="DebugActivityTheme">
|
||||||
|
<item name="colorTittle">?attr/mainWindowTextColor</item>
|
||||||
|
<item name="colorTittleBackgound">?attr/toolbarBackgroundColor</item>
|
||||||
|
<item name="colorText">?attr/debugTextColor</item>
|
||||||
|
<item name="colorTextBackgound">?attr/mainWindowBackgroundColor</item>
|
||||||
|
<item name="toolbarTextColor">@color/toolbarTextColor</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
12
appbase/src/stage/AndroidManifest.xml
Normal file
12
appbase/src/stage/AndroidManifest.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
||||||
6
appbase/src/stage/res/values/strings.xml
Normal file
6
appbase/src/stage/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Put flavor specific strings here -->
|
||||||
|
|
||||||
|
</resources>
|
||||||
Binary file not shown.
1
autonfc/.gitignore
vendored
Normal file
1
autonfc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
||||||
34
autonfc/README.md
Normal file
34
autonfc/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# AutoNFC
|
||||||
|
|
||||||
|
#### 介绍
|
||||||
|
NFC 卡应用,主要管理 NFC 卡接触手机的动作响应,NFC 接触状态用于作为其他应用激活活动动作的启动令牌。
|
||||||
|
|
||||||
|
#### 软件架构
|
||||||
|
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||||
|
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||||
|
|
||||||
|
|
||||||
|
#### Gradle 编译说明
|
||||||
|
调试版编译命令 :gradle assembleBetaDebug
|
||||||
|
阶段版编译命令 :bash .winboll/bashPublishAPKAddTag.sh autonfc
|
||||||
|
|
||||||
|
#### 使用说明
|
||||||
|
|
||||||
|
#### 参与贡献
|
||||||
|
|
||||||
|
1. Fork 本仓库
|
||||||
|
2. 新建 Feat_xxx 分支
|
||||||
|
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
|
||||||
|
4. 新建 Pull Request
|
||||||
|
|
||||||
|
|
||||||
|
#### 特技
|
||||||
|
|
||||||
|
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||||
|
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||||
|
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||||
|
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||||
|
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||||
|
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||||
|
|
||||||
|
#### 参考文档
|
||||||
1
autonfc/app_update_description.txt
Normal file
1
autonfc/app_update_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
119
autonfc/build.gradle
Normal file
119
autonfc/build.gradle
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply from: '../.winboll/winboll_app_build.gradle'
|
||||||
|
apply from: '../.winboll/winboll_lint_build.gradle'
|
||||||
|
|
||||||
|
def genVersionName(def versionName){
|
||||||
|
// 检查编译标志位配置
|
||||||
|
assert (winbollBuildProps['stageCount'] != null)
|
||||||
|
assert (winbollBuildProps['baseVersion'] != null)
|
||||||
|
// 保存基础版本号
|
||||||
|
winbollBuildProps.setProperty("baseVersion", "${versionName}");
|
||||||
|
//保存编译标志配置
|
||||||
|
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
|
||||||
|
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
|
||||||
|
fos.close();
|
||||||
|
|
||||||
|
// 返回编译版本号
|
||||||
|
return "${versionName}." + winbollBuildProps['stageCount']
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
// 适配MIUI12
|
||||||
|
compileSdkVersion 30
|
||||||
|
buildToolsVersion "30.0.3"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "cc.winboll.studio.autonfc"
|
||||||
|
minSdkVersion 23
|
||||||
|
// 适配MIUI12
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode 1
|
||||||
|
// versionName 更新后需要手动设置
|
||||||
|
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||||
|
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||||
|
versionName "15.11"
|
||||||
|
if(true) {
|
||||||
|
versionName = genVersionName("${versionName}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 米盟 SDK
|
||||||
|
packagingOptions {
|
||||||
|
doNotStrip "*/*/libmimo_1011.so"
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
jniLibs.srcDirs = ['libs'] // 若SO库放在libs目录下
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
api 'com.google.code.gson:gson:2.10.1'
|
||||||
|
|
||||||
|
// 下拉控件
|
||||||
|
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
|
||||||
|
|
||||||
|
// SSH
|
||||||
|
api 'com.jcraft:jsch:0.1.55'
|
||||||
|
// Html 解析
|
||||||
|
api 'org.jsoup:jsoup:1.13.1'
|
||||||
|
// 二维码类库
|
||||||
|
api 'com.google.zxing:core:3.4.1'
|
||||||
|
api 'com.journeyapps:zxing-android-embedded:3.6.0'
|
||||||
|
// 应用介绍页类库
|
||||||
|
api 'io.github.medyo:android-about-page:2.0.0'
|
||||||
|
// 网络连接类库
|
||||||
|
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||||
|
// OkHttp网络请求
|
||||||
|
implementation 'com.squareup.okhttp3:okhttp:3.14.9'
|
||||||
|
// FastJSON解析
|
||||||
|
implementation 'com.alibaba:fastjson:1.2.76'
|
||||||
|
|
||||||
|
// AndroidX 类库
|
||||||
|
/*api 'androidx.appcompat:appcompat:1.1.0'
|
||||||
|
//api 'com.google.android.material:material:1.4.0'
|
||||||
|
//api 'androidx.viewpager:viewpager:1.0.0'
|
||||||
|
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||||
|
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||||
|
//api 'androidx.fragment:fragment:1.1.0'*/
|
||||||
|
|
||||||
|
|
||||||
|
// 米盟
|
||||||
|
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||||
|
//注意:以下5个库必须要引入
|
||||||
|
//implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||||
|
api 'androidx.recyclerview:recyclerview:1.0.0'
|
||||||
|
api 'com.google.code.gson:gson:2.8.5'
|
||||||
|
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||||
|
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||||
|
|
||||||
|
implementation "androidx.annotation:annotation:1.3.0"
|
||||||
|
implementation "androidx.core:core:1.6.0"
|
||||||
|
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
|
||||||
|
implementation "androidx.preference:preference:1.1.1"
|
||||||
|
implementation "androidx.viewpager:viewpager:1.0.0"
|
||||||
|
implementation "com.google.android.material:material:1.4.0"
|
||||||
|
implementation "com.google.guava:guava:24.1-jre"
|
||||||
|
/*
|
||||||
|
implementation "io.noties.markwon:core:$markwonVersion"
|
||||||
|
implementation "io.noties.markwon:ext-strikethrough:$markwonVersion"
|
||||||
|
implementation "io.noties.markwon:linkify:$markwonVersion"
|
||||||
|
implementation "io.noties.markwon:recycler:$markwonVersion"
|
||||||
|
*/
|
||||||
|
implementation 'com.termux:terminal-emulator:0.118.0'
|
||||||
|
implementation 'com.termux:terminal-view:0.118.0'
|
||||||
|
implementation 'com.termux:termux-shared:0.118.0'
|
||||||
|
|
||||||
|
// WinBoLL库 nexus.winboll.cc 地址
|
||||||
|
api 'cc.winboll.studio:libaes:15.15.2'
|
||||||
|
api 'cc.winboll.studio:libappbase:15.15.11'
|
||||||
|
|
||||||
|
// WinBoLL备用库 jitpack.io 地址
|
||||||
|
//api 'com.github.ZhanGSKen:AES:aes-v15.15.7'
|
||||||
|
//api 'com.github.ZhanGSKen:APPBase:appbase-v15.15.4'
|
||||||
|
|
||||||
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
}
|
||||||
8
autonfc/build.properties
Normal file
8
autonfc/build.properties
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#Created by .winboll/winboll_app_build.gradle
|
||||||
|
#Mon Mar 16 18:30:19 GMT 2026
|
||||||
|
stageCount=0
|
||||||
|
libraryProject=
|
||||||
|
baseVersion=15.11
|
||||||
|
publishVersion=15.0.0
|
||||||
|
buildCount=54
|
||||||
|
baseBetaVersion=15.0.1
|
||||||
21
autonfc/proguard-rules.pro
vendored
Normal file
21
autonfc/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
||||||
12
autonfc/src/beta/AndroidManifest.xml
Normal file
12
autonfc/src/beta/AndroidManifest.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools" >
|
||||||
|
|
||||||
|
<application>
|
||||||
|
|
||||||
|
<!-- Put flavor specific code here -->
|
||||||
|
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
||||||
|
|
||||||
6
autonfc/src/beta/res/values/strings.xml
Normal file
6
autonfc/src/beta/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<string name="app_name">AutoNFC✌</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
51
autonfc/src/main/AndroidManifest.xml
Normal file
51
autonfc/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="cc.winboll.studio.autonfc">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.NFC"/>
|
||||||
|
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.nfc"
|
||||||
|
android:required="true"/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:theme="@style/MyAppTheme"
|
||||||
|
android:resizeableActivity="true"
|
||||||
|
android:name=".App">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".nfc.NFCInterfaceActivity"
|
||||||
|
android:launchMode="singleTop">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.nfc.action.NDEF_DISCOVERED"/>
|
||||||
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
|
<data android:mimeType="*/*"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<!-- NFC 绑定服务 -->
|
||||||
|
<service
|
||||||
|
android:name=".nfc.AutoNFCService"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.max_aspect"
|
||||||
|
android:value="4.0"/>
|
||||||
|
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
|
|
||||||
344
autonfc/src/main/java/cc/winboll/studio/autonfc/App.java
Normal file
344
autonfc/src/main/java/cc/winboll/studio/autonfc/App.java
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
package cc.winboll.studio.autonfc;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Gravity;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.HorizontalScrollView;
|
||||||
|
import android.widget.ScrollView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.lang.Thread.UncaughtExceptionHandler;
|
||||||
|
import java.text.DateFormat;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public class App extends GlobalApplication {
|
||||||
|
|
||||||
|
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
// 初始化 Toast 框架
|
||||||
|
// ToastUtils.init(this);
|
||||||
|
// // 设置 Toast 布局样式
|
||||||
|
// //ToastUtils.setView(R.layout.view_toast);
|
||||||
|
// ToastUtils.setStyle(new WhiteToastStyle());
|
||||||
|
// ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||||
|
//
|
||||||
|
//CrashHandler.getInstance().registerGlobal(this);
|
||||||
|
//CrashHandler.getInstance().registerPart(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(InputStream input, OutputStream output) throws IOException {
|
||||||
|
byte[] buf = new byte[1024 * 8];
|
||||||
|
int len;
|
||||||
|
while ((len = input.read(buf)) != -1) {
|
||||||
|
output.write(buf, 0, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(File file, byte[] data) throws IOException {
|
||||||
|
File parent = file.getParentFile();
|
||||||
|
if (parent != null && !parent.exists()) parent.mkdirs();
|
||||||
|
|
||||||
|
ByteArrayInputStream input = new ByteArrayInputStream(data);
|
||||||
|
FileOutputStream output = new FileOutputStream(file);
|
||||||
|
try {
|
||||||
|
write(input, output);
|
||||||
|
} finally {
|
||||||
|
closeIO(input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toString(InputStream input) throws IOException {
|
||||||
|
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||||
|
write(input, output);
|
||||||
|
try {
|
||||||
|
return output.toString("UTF-8");
|
||||||
|
} finally {
|
||||||
|
closeIO(input, output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void closeIO(Closeable... closeables) {
|
||||||
|
for (Closeable closeable : closeables) {
|
||||||
|
try {
|
||||||
|
if (closeable != null) closeable.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CrashHandler {
|
||||||
|
|
||||||
|
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||||
|
|
||||||
|
private static CrashHandler sInstance;
|
||||||
|
|
||||||
|
private PartCrashHandler mPartCrashHandler;
|
||||||
|
|
||||||
|
public static CrashHandler getInstance() {
|
||||||
|
if (sInstance == null) {
|
||||||
|
sInstance = new CrashHandler();
|
||||||
|
}
|
||||||
|
return sInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerGlobal(Context context) {
|
||||||
|
registerGlobal(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerGlobal(Context context, String crashDir) {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl(context.getApplicationContext(), crashDir));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregister() {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(DEFAULT_UNCAUGHT_EXCEPTION_HANDLER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerPart(Context context) {
|
||||||
|
unregisterPart(context);
|
||||||
|
mPartCrashHandler = new PartCrashHandler(context.getApplicationContext());
|
||||||
|
MAIN_HANDLER.postAtFrontOfQueue(mPartCrashHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterPart(Context context) {
|
||||||
|
if (mPartCrashHandler != null) {
|
||||||
|
mPartCrashHandler.isRunning.set(false);
|
||||||
|
mPartCrashHandler = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PartCrashHandler implements Runnable {
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
public AtomicBoolean isRunning = new AtomicBoolean(true);
|
||||||
|
|
||||||
|
public PartCrashHandler(Context context) {
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (isRunning.get()) {
|
||||||
|
try {
|
||||||
|
Looper.loop();
|
||||||
|
} catch (final Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
if (isRunning.get()) {
|
||||||
|
MAIN_HANDLER.post(new Runnable(){
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(mContext, e.toString(), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (e instanceof RuntimeException) {
|
||||||
|
throw (RuntimeException)e;
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
|
||||||
|
|
||||||
|
private static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss");
|
||||||
|
|
||||||
|
private final Context mContext;
|
||||||
|
|
||||||
|
private final File mCrashDir;
|
||||||
|
|
||||||
|
public UncaughtExceptionHandlerImpl(Context context, String crashDir) {
|
||||||
|
this.mContext = context;
|
||||||
|
this.mCrashDir = TextUtils.isEmpty(crashDir) ? new File(mContext.getExternalCacheDir(), "crash") : new File(crashDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||||
|
try {
|
||||||
|
|
||||||
|
String log = buildLog(throwable);
|
||||||
|
writeLog(log);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Intent intent = new Intent(mContext, CrashActivity.class);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.putExtra(Intent.EXTRA_TEXT, log);
|
||||||
|
mContext.startActivity(intent);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
writeLog(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
throwable.printStackTrace();
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
System.exit(0);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildLog(Throwable throwable) {
|
||||||
|
String time = DATE_FORMAT.format(new Date());
|
||||||
|
|
||||||
|
String versionName = "unknown";
|
||||||
|
long versionCode = 0;
|
||||||
|
try {
|
||||||
|
PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
|
||||||
|
versionName = packageInfo.versionName;
|
||||||
|
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
|
||||||
|
LinkedHashMap<String, String> head = new LinkedHashMap<String, String>();
|
||||||
|
head.put("Time Of Crash", time);
|
||||||
|
head.put("Device", String.format("%s, %s", Build.MANUFACTURER, Build.MODEL));
|
||||||
|
head.put("Android Version", String.format("%s (%d)", Build.VERSION.RELEASE, Build.VERSION.SDK_INT));
|
||||||
|
head.put("App Version", String.format("%s (%d)", versionName, versionCode));
|
||||||
|
head.put("Kernel", getKernel());
|
||||||
|
head.put("Support Abis", Build.VERSION.SDK_INT >= 21 && Build.SUPPORTED_ABIS != null ? Arrays.toString(Build.SUPPORTED_ABIS): "unknown");
|
||||||
|
head.put("Fingerprint", Build.FINGERPRINT);
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
|
||||||
|
for (String key : head.keySet()) {
|
||||||
|
if (builder.length() != 0) builder.append("\n");
|
||||||
|
builder.append(key);
|
||||||
|
builder.append(" : ");
|
||||||
|
builder.append(head.get(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.append("\n\n");
|
||||||
|
builder.append(Log.getStackTraceString(throwable));
|
||||||
|
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeLog(String log) {
|
||||||
|
String time = DATE_FORMAT.format(new Date());
|
||||||
|
File file = new File(mCrashDir, "crash_" + time + ".txt");
|
||||||
|
try {
|
||||||
|
write(file, log.getBytes("UTF-8"));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getKernel() {
|
||||||
|
try {
|
||||||
|
return App.toString(new FileInputStream("/proc/version")).trim();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class CrashActivity extends Activity {
|
||||||
|
|
||||||
|
private String mLog;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
setTheme(android.R.style.Theme_DeviceDefault);
|
||||||
|
setTitle("App Crash");
|
||||||
|
|
||||||
|
mLog = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
||||||
|
|
||||||
|
ScrollView contentView = new ScrollView(this);
|
||||||
|
contentView.setFillViewport(true);
|
||||||
|
|
||||||
|
HorizontalScrollView horizontalScrollView = new HorizontalScrollView(this);
|
||||||
|
|
||||||
|
TextView textView = new TextView(this);
|
||||||
|
int padding = dp2px(16);
|
||||||
|
textView.setPadding(padding, padding, padding, padding);
|
||||||
|
textView.setText(mLog);
|
||||||
|
textView.setTextIsSelectable(true);
|
||||||
|
textView.setTypeface(Typeface.DEFAULT);
|
||||||
|
textView.setLinksClickable(true);
|
||||||
|
|
||||||
|
horizontalScrollView.addView(textView);
|
||||||
|
contentView.addView(horizontalScrollView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||||
|
|
||||||
|
setContentView(contentView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restart() {
|
||||||
|
Intent intent = getPackageManager().getLaunchIntentForPackage(getPackageName());
|
||||||
|
if (intent != null) {
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
android.os.Process.killProcess(android.os.Process.myPid());
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int dp2px(float dpValue) {
|
||||||
|
final float scale = Resources.getSystem().getDisplayMetrics().density;
|
||||||
|
return (int) (dpValue * scale + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
menu.add(0, android.R.id.copy, 0, android.R.string.copy)
|
||||||
|
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||||
|
return super.onCreateOptionsMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.copy:
|
||||||
|
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package cc.winboll.studio.autonfc;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.view.Menu;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import cc.winboll.studio.autonfc.nfc.ActionDialog;
|
||||||
|
import cc.winboll.studio.autonfc.nfc.AutoNFCService;
|
||||||
|
import cc.winboll.studio.autonfc.nfc.NFCInterfaceActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
public class MainActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
public static final String TAG = "MainActivity";
|
||||||
|
|
||||||
|
private NfcAdapter mNfcAdapter;
|
||||||
|
private PendingIntent mPendingIntent;
|
||||||
|
private AutoNFCService mService;
|
||||||
|
private boolean mBound = false;
|
||||||
|
|
||||||
|
// 服务连接
|
||||||
|
private ServiceConnection mConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
AutoNFCService.LocalBinder binder = (AutoNFCService.LocalBinder) service;
|
||||||
|
mService = binder.getService();
|
||||||
|
mBound = true;
|
||||||
|
LogUtils.d(TAG, "onServiceConnected: 服务已绑定");
|
||||||
|
|
||||||
|
// 关键:把 Activity 传给 Service,用于回调
|
||||||
|
mService.attachActivity(MainActivity.this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
mBound = false;
|
||||||
|
mService = null;
|
||||||
|
LogUtils.d(TAG, "onServiceDisconnected: 服务已断开");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||||
|
setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
// 初始化 NFC
|
||||||
|
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||||
|
Intent nfcIntent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
mPendingIntent = PendingIntent.getActivity(this, 0, nfcIntent, 0);
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "onCreate() -> NFC 监听已绑定到 MainActivity");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
// 绑定服务
|
||||||
|
Intent intent = new Intent(this, AutoNFCService.class);
|
||||||
|
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
LogUtils.d(TAG, "onStart: 绑定服务");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
// 解绑服务
|
||||||
|
if (mBound) {
|
||||||
|
unbindService(mConnection);
|
||||||
|
mBound = false;
|
||||||
|
LogUtils.d(TAG, "onStop: 解绑服务");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
LogUtils.d(TAG, "onResume() -> 开启 NFC 前台分发");
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
mNfcAdapter.enableForegroundDispatch(this, mPendingIntent, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
LogUtils.d(TAG, "onPause() -> 关闭 NFC 前台分发");
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
mNfcAdapter.disableForegroundDispatch(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NFC 卡片靠近唯一入口
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
LogUtils.d(TAG, "onNewIntent() -> 检测到 NFC 卡片");
|
||||||
|
|
||||||
|
// 把 NFC 事件交给 Service 处理
|
||||||
|
if (mBound && mService != null) {
|
||||||
|
mService.handleNfcIntent(intent);
|
||||||
|
} else {
|
||||||
|
LogUtils.e(TAG, "服务未绑定,无法处理 NFC");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
getMenuInflater().inflate(R.menu.main_menu, menu);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
int id = item.getItemId();
|
||||||
|
|
||||||
|
if (id == R.id.menu_log) {
|
||||||
|
LogActivity.startLogActivity(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onNFCInterfaceActivity(View view) {
|
||||||
|
startActivity(new Intent(this, NFCInterfaceActivity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= 【新增】关键方法:由 Service 回调来弹出对话框 =========================
|
||||||
|
/**
|
||||||
|
* Service 解析完 NFC 数据后,回调此方法在 Activity 中弹出对话框
|
||||||
|
*/
|
||||||
|
public void showNfcActionDialog(final String nfcData) {
|
||||||
|
LogUtils.d(TAG, "showNfcActionDialog() -> Activity 存活,安全弹出对话框");
|
||||||
|
|
||||||
|
// Activity 正在运行,直接弹框,绝对不会报 BadTokenException
|
||||||
|
final ActionDialog dialog = new ActionDialog(this);
|
||||||
|
dialog.setNfcData(nfcData);
|
||||||
|
dialog.setButtonClickListener(new ActionDialog.OnButtonClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onBuildClick() {
|
||||||
|
LogUtils.d(TAG, "点击 Build");
|
||||||
|
if (mService != null) {
|
||||||
|
mService.executeTermuxCommand(AutoNFCService.ACTION_BUILD, nfcData);
|
||||||
|
}
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewClick() {
|
||||||
|
LogUtils.d(TAG, "点击 View");
|
||||||
|
if (mService != null) {
|
||||||
|
mService.executeTermuxCommand(AutoNFCService.ACTION_BUILD_VIEW, nfcData);
|
||||||
|
}
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelClick() {
|
||||||
|
dialog.dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package cc.winboll.studio.autonfc.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||||
|
* @Date 2026/03/16 09:38
|
||||||
|
*/
|
||||||
|
public class NfcTermuxCmd {
|
||||||
|
|
||||||
|
private String script; // 要执行的预制脚本名(如 auth.sh)
|
||||||
|
private String[] args; // 脚本参数
|
||||||
|
private String workDir; // 工作目录
|
||||||
|
private boolean background; // 是否后台执行
|
||||||
|
private String resultDir; // 结果输出目录(可为 null)
|
||||||
|
|
||||||
|
public NfcTermuxCmd() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public NfcTermuxCmd(String script, String[] args, String workDir, boolean background, String resultDir) {
|
||||||
|
this.script = script;
|
||||||
|
this.args = args;
|
||||||
|
this.workDir = workDir;
|
||||||
|
this.background = background;
|
||||||
|
this.resultDir = resultDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScript() {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScript(String script) {
|
||||||
|
this.script = script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getArgs() {
|
||||||
|
return args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setArgs(String[] args) {
|
||||||
|
this.args = args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWorkDir() {
|
||||||
|
return workDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWorkDir(String workDir) {
|
||||||
|
this.workDir = workDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBackground() {
|
||||||
|
return background;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBackground(boolean background) {
|
||||||
|
this.background = background;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResultDir() {
|
||||||
|
return resultDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResultDir(String resultDir) {
|
||||||
|
this.resultDir = resultDir;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package cc.winboll.studio.autonfc.nfc;
|
||||||
|
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import cc.winboll.studio.autonfc.R;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义对话框类,用于与用户交互,展示 NFC 相关操作选项
|
||||||
|
* 兼容 Java 7 语法
|
||||||
|
*
|
||||||
|
* @author 豆包&ZhanGSKen
|
||||||
|
* @create 2025-08-15
|
||||||
|
* @lastModify 2026-03-17
|
||||||
|
*/
|
||||||
|
public class ActionDialog extends Dialog {
|
||||||
|
|
||||||
|
private static final String TAG = "ActionDialog";
|
||||||
|
private String mNfcData;
|
||||||
|
private OnButtonClickListener mClickListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*/
|
||||||
|
public ActionDialog(Context context) {
|
||||||
|
super(context);
|
||||||
|
initDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置 NFC 数据
|
||||||
|
*/
|
||||||
|
public void setNfcData(String nfcData) {
|
||||||
|
this.mNfcData = nfcData;
|
||||||
|
LogUtils.d(TAG, "setNfcData() -> " + nfcData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置点击监听
|
||||||
|
*/
|
||||||
|
public void setButtonClickListener(OnButtonClickListener listener) {
|
||||||
|
this.mClickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化布局
|
||||||
|
*/
|
||||||
|
private void initDialog() {
|
||||||
|
setTitle("请选择操作");
|
||||||
|
|
||||||
|
LinearLayout layout = new LinearLayout(getContext());
|
||||||
|
layout.setOrientation(LinearLayout.VERTICAL);
|
||||||
|
layout.setPadding(20, 20, 20, 20);
|
||||||
|
|
||||||
|
addButtons(layout);
|
||||||
|
|
||||||
|
setContentView(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加按钮
|
||||||
|
*/
|
||||||
|
private void addButtons(LinearLayout layout) {
|
||||||
|
// Build 按钮
|
||||||
|
Button btnBuild = createButton("Build", new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LogUtils.d(TAG, "点击 Build");
|
||||||
|
if (mClickListener != null) {
|
||||||
|
mClickListener.onBuildClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layout.addView(btnBuild);
|
||||||
|
|
||||||
|
// View 按钮
|
||||||
|
Button btnView = createButton("View", new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LogUtils.d(TAG, "点击 View");
|
||||||
|
if (mClickListener != null) {
|
||||||
|
mClickListener.onViewClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layout.addView(btnView);
|
||||||
|
|
||||||
|
// 取消按钮
|
||||||
|
Button btnCancel = createButton("Cancel", new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
LogUtils.d(TAG, "点击 Cancel");
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layout.addView(btnCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建按钮
|
||||||
|
*/
|
||||||
|
private Button createButton(String text, View.OnClickListener listener) {
|
||||||
|
Button button = new Button(getContext());
|
||||||
|
button.setText(text);
|
||||||
|
button.setPadding(10, 10, 10, 10);
|
||||||
|
button.setOnClickListener(listener);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回调接口
|
||||||
|
*/
|
||||||
|
public interface OnButtonClickListener {
|
||||||
|
void onBuildClick();
|
||||||
|
void onViewClick();
|
||||||
|
void onCancelClick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
package cc.winboll.studio.autonfc.nfc;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.nfc.NdefMessage;
|
||||||
|
import android.nfc.NdefRecord;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.nfc.Tag;
|
||||||
|
import android.nfc.tech.Ndef;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
import cc.winboll.studio.autonfc.MainActivity;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import cc.winboll.studio.libappbase.ToastUtils;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class AutoNFCService extends Service {
|
||||||
|
|
||||||
|
public static final String TAG = "AutoNFCService";
|
||||||
|
|
||||||
|
// ================= 已修改:更新为 Beta 包名 =================
|
||||||
|
public static final String ACTION_BUILD = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD";
|
||||||
|
public static final String ACTION_BUILD_VIEW = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD_VIEW";
|
||||||
|
|
||||||
|
private final IBinder mBinder = new LocalBinder();
|
||||||
|
private String mNfcData;
|
||||||
|
private MainActivity mActivity; // 持有 Activity 引用,用于回调
|
||||||
|
|
||||||
|
// ========================= 生命周期 =========================
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
LogUtils.d(TAG, "onCreate() -> 服务创建");
|
||||||
|
// 移除:startForeground(NOTIFICATION_ID, buildNotification());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
LogUtils.d(TAG, "onDestroy() -> 服务已停止");
|
||||||
|
mActivity = null; // 释放引用
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= 服务绑定 =========================
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
LogUtils.d(TAG, "onBind() -> 服务被绑定");
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onUnbind(Intent intent) {
|
||||||
|
LogUtils.d(TAG, "onUnbind() -> 服务解绑");
|
||||||
|
// 移除:stopForeground(true);
|
||||||
|
stopSelf();
|
||||||
|
return super.onUnbind(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= 对外暴露方法 =========================
|
||||||
|
/**
|
||||||
|
* 绑定 Activity,用于回调显示对话框
|
||||||
|
*/
|
||||||
|
public void attachActivity(MainActivity activity) {
|
||||||
|
this.mActivity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理 NFC 意图
|
||||||
|
*/
|
||||||
|
public void handleNfcIntent(Intent intent) {
|
||||||
|
LogUtils.d(TAG, "handleNfcIntent() -> 开始处理");
|
||||||
|
|
||||||
|
if (intent == null) {
|
||||||
|
LogUtils.e(TAG, "handleNfcIntent() -> 参数 intent 为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String action = intent.getAction();
|
||||||
|
LogUtils.d(TAG, "handleNfcIntent() -> Action = " + action);
|
||||||
|
|
||||||
|
if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)
|
||||||
|
|| NfcAdapter.ACTION_TECH_DISCOVERED.equals(action)
|
||||||
|
|| NfcAdapter.ACTION_TAG_DISCOVERED.equals(action)) {
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "handleNfcIntent() -> 匹配 NFC 动作");
|
||||||
|
|
||||||
|
Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||||
|
if (tag == null) {
|
||||||
|
LogUtils.e(TAG, "handleNfcIntent() -> Tag 为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUtils.d(TAG, "handleNfcIntent() -> Tag ID = " + bytesToHexString(tag.getId()));
|
||||||
|
LogUtils.d(TAG, "handleNfcIntent() -> Tech List = " + Arrays.toString(tag.getTechList()));
|
||||||
|
|
||||||
|
parseNdefData(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= 内部业务 =========================
|
||||||
|
private void parseNdefData(Tag tag) {
|
||||||
|
LogUtils.d(TAG, "parseNdefData() -> 开始解析");
|
||||||
|
|
||||||
|
if (tag == null) return;
|
||||||
|
|
||||||
|
Ndef ndef = Ndef.get(tag);
|
||||||
|
if (ndef == null) {
|
||||||
|
LogUtils.e(TAG, "parseNdefData() -> 不支持 NDEF 格式");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ndef.connect();
|
||||||
|
NdefMessage msg = ndef.getNdefMessage();
|
||||||
|
|
||||||
|
if (msg == null || msg.getRecords() == null || msg.getRecords().length == 0) {
|
||||||
|
LogUtils.w(TAG, "parseNdefData() -> 卡片无数据");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NdefRecord record = msg.getRecords()[0];
|
||||||
|
byte[] payload = record.getPayload();
|
||||||
|
|
||||||
|
int langLen = payload[0] & 0x3F;
|
||||||
|
int start = 1 + langLen;
|
||||||
|
|
||||||
|
if (start < payload.length) {
|
||||||
|
mNfcData = new String(payload, start, payload.length - start, Charset.forName("UTF-8"));
|
||||||
|
LogUtils.d(TAG, "parseNdefData() -> 读卡成功: " + mNfcData);
|
||||||
|
|
||||||
|
// 关键:回调给 Activity 弹框,此时 Activity 一定是存活状态
|
||||||
|
if (mActivity != null) {
|
||||||
|
mActivity.showNfcActionDialog(mNfcData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "parseNdefData() -> 读取失败", e);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
ndef.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 忽略关闭异常
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行 Termux 命令
|
||||||
|
*/
|
||||||
|
public void executeTermuxCommand(String action, String nfcData) {
|
||||||
|
LogUtils.d(TAG, "executeTermuxCommand() -> 开始执行");
|
||||||
|
|
||||||
|
if (nfcData == null || nfcData.isEmpty()) {
|
||||||
|
ToastUtils.show("数据错误");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
LogUtils.d(TAG, "executeTermuxCommand() -> 发送指令: " + nfcData);
|
||||||
|
|
||||||
|
Intent bridgeIntent = new Intent(action);
|
||||||
|
|
||||||
|
// ================= 已修改:使用 Beta 包名 =================
|
||||||
|
bridgeIntent.setClassName(
|
||||||
|
"cc.winboll.studio.winboll.beta",
|
||||||
|
"cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity"
|
||||||
|
);
|
||||||
|
|
||||||
|
bridgeIntent.putExtra(Intent.EXTRA_TEXT, nfcData);
|
||||||
|
bridgeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
|
||||||
|
startActivity(bridgeIntent);
|
||||||
|
ToastUtils.show("指令已发送");
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "executeTermuxCommand() -> 发送失败", e);
|
||||||
|
ToastUtils.show("发送失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= 工具方法 =========================
|
||||||
|
private String bytesToHexString(byte[] bytes) {
|
||||||
|
if (bytes == null || bytes.length == 0) return "";
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte b : bytes) {
|
||||||
|
sb.append(String.format("%02X", b));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================= Binder =========================
|
||||||
|
public class LocalBinder extends Binder {
|
||||||
|
public AutoNFCService getService() {
|
||||||
|
return AutoNFCService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
package cc.winboll.studio.autonfc.nfc;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.nfc.NfcAdapter;
|
||||||
|
import android.nfc.Tag;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import cc.winboll.studio.autonfc.R;
|
||||||
|
import cc.winboll.studio.autonfc.models.NfcTermuxCmd;
|
||||||
|
import cc.winboll.studio.libappbase.LogUtils;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class NFCInterfaceActivity extends Activity {
|
||||||
|
|
||||||
|
public static final String TAG = "NFCInterfaceActivity";
|
||||||
|
|
||||||
|
private EditText et_script;
|
||||||
|
private EditText et_args;
|
||||||
|
private EditText et_workDir;
|
||||||
|
private EditText et_background;
|
||||||
|
private EditText et_resultDir;
|
||||||
|
|
||||||
|
private TextView tvResult;
|
||||||
|
private TextView tvStatus;
|
||||||
|
|
||||||
|
private NfcAdapter mNfcAdapter;
|
||||||
|
private PendingIntent mNfcPendingIntent;
|
||||||
|
private Tag mCurrentTag;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_nfc_interface);
|
||||||
|
initView();
|
||||||
|
initNfc();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initView() {
|
||||||
|
et_script = findViewById(R.id.et_script);
|
||||||
|
et_args = findViewById(R.id.et_args);
|
||||||
|
et_workDir = findViewById(R.id.et_workDir);
|
||||||
|
et_background = findViewById(R.id.et_background);
|
||||||
|
et_resultDir = findViewById(R.id.et_resultDir);
|
||||||
|
|
||||||
|
tvResult = findViewById(R.id.tv_result);
|
||||||
|
tvStatus = findViewById(R.id.tv_status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initNfc() {
|
||||||
|
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
|
||||||
|
|
||||||
|
if (mNfcAdapter == null) {
|
||||||
|
tvStatus.setText("设备不支持NFC");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mNfcAdapter.isEnabled()) {
|
||||||
|
tvStatus.setText("请开启NFC");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Intent nfcIntent = new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
mNfcPendingIntent = PendingIntent.getActivity(this, 0, nfcIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
|
||||||
|
tvStatus.setText("NFC已启动,等待卡片靠近");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (mNfcAdapter != null && mNfcAdapter.isEnabled()) {
|
||||||
|
mNfcAdapter.enableForegroundDispatch(this, mNfcPendingIntent, null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (mNfcAdapter != null) {
|
||||||
|
mNfcAdapter.disableForegroundDispatch(this);
|
||||||
|
}
|
||||||
|
mCurrentTag = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onNewIntent(Intent intent) {
|
||||||
|
super.onNewIntent(intent);
|
||||||
|
mCurrentTag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
|
||||||
|
if (mCurrentTag == null) return;
|
||||||
|
|
||||||
|
tvStatus.setText("卡片已连接,解析中...");
|
||||||
|
readNfc();
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 读取 NFC(完全委托给工具类)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
private void readNfc() {
|
||||||
|
try {
|
||||||
|
NfcTermuxCmd cmd = NfcUtils.readTag(mCurrentTag);
|
||||||
|
if (cmd == null) {
|
||||||
|
tvStatus.setText("读取成功:标签为空");
|
||||||
|
tvResult.setText("");
|
||||||
|
// 清空窗体
|
||||||
|
clearUiFields();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 核心改动:读取成功后,同时更新详情显示 和 窗体输入框
|
||||||
|
updateUiWithCmd(cmd);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "readNfc 失败", e);
|
||||||
|
tvStatus.setText("读取失败:" + e.getMessage());
|
||||||
|
// 出错时清空窗体
|
||||||
|
clearUiFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 新增:根据读取到的 Cmd 填充 UI(详情 + 窗体)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
private void updateUiWithCmd(NfcTermuxCmd cmd) {
|
||||||
|
if (cmd == null) return;
|
||||||
|
|
||||||
|
String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(new Date());
|
||||||
|
String show = "【读取时间】 " + time + "\n\n"
|
||||||
|
+ "【解析结果】\n"
|
||||||
|
+ "script: " + cmd.getScript() + "\n"
|
||||||
|
+ "args: " + (cmd.getArgs() != null ? String.join(", ", cmd.getArgs()) : "[]") + "\n"
|
||||||
|
+ "workDir: " + cmd.getWorkDir() + "\n"
|
||||||
|
+ "background: " + cmd.isBackground() + "\n"
|
||||||
|
+ "resultDir: " + cmd.getResultDir();
|
||||||
|
|
||||||
|
tvResult.setText(show);
|
||||||
|
tvStatus.setText("读取成功!");
|
||||||
|
|
||||||
|
// 👇 关键逻辑:自动填入窗体(每次读取后都会覆盖输入框)
|
||||||
|
et_script.setText(cmd.getScript() != null ? cmd.getScript() : "");
|
||||||
|
et_args.setText(cmd.getArgs() != null ? String.join(",", cmd.getArgs()) : "");
|
||||||
|
et_workDir.setText(cmd.getWorkDir() != null ? cmd.getWorkDir() : "");
|
||||||
|
et_background.setText(String.valueOf(cmd.isBackground()));
|
||||||
|
et_resultDir.setText(cmd.getResultDir() != null ? cmd.getResultDir() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 辅助:清空所有输入框
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
private void clearUiFields() {
|
||||||
|
et_script.setText("");
|
||||||
|
et_args.setText("");
|
||||||
|
et_workDir.setText("");
|
||||||
|
et_background.setText("");
|
||||||
|
et_resultDir.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 写入按钮(委托给工具类)
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
public void onWriteClick(View view) {
|
||||||
|
if (mCurrentTag == null) {
|
||||||
|
showToast("请先靠近卡片");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
NfcTermuxCmd cmd = buildCmdFromUI();
|
||||||
|
NfcUtils.writeTag(mCurrentTag, cmd);
|
||||||
|
|
||||||
|
tvStatus.setText("写入成功!");
|
||||||
|
showToast("写入成功");
|
||||||
|
readNfc(); // 写入后重读,此时会自动填入窗体
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.e(TAG, "写入失败", e);
|
||||||
|
tvStatus.setText("写入失败:" + e.getMessage());
|
||||||
|
showToast("写入失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 填充调试数据
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
public void onFillTestDataClick(View view) {
|
||||||
|
String testJson = "{\"script\":\"BuildWinBoLLProject.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
|
||||||
|
try {
|
||||||
|
NfcTermuxCmd cmd = NfcUtils.jsonToCmd(testJson);
|
||||||
|
et_script.setText(cmd.getScript());
|
||||||
|
et_args.setText(cmd.getArgs() != null ? String.join(",", cmd.getArgs()) : "");
|
||||||
|
et_workDir.setText(cmd.getWorkDir() != null ? cmd.getWorkDir() : "");
|
||||||
|
et_background.setText(String.valueOf(cmd.isBackground()));
|
||||||
|
et_resultDir.setText(cmd.getResultDir() != null ? cmd.getResultDir() : "");
|
||||||
|
|
||||||
|
showToast("调试数据已填入");
|
||||||
|
} catch (Exception e) {
|
||||||
|
showToast("解析失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
// 从 UI 构建 NfcTermuxCmd
|
||||||
|
// -------------------------------------------------------------------------
|
||||||
|
private NfcTermuxCmd buildCmdFromUI() {
|
||||||
|
String script = et_script.getText().toString().trim();
|
||||||
|
String argsStr = et_args.getText().toString().trim();
|
||||||
|
String workDir = et_workDir.getText().toString().trim();
|
||||||
|
String bgStr = et_background.getText().toString().trim();
|
||||||
|
String resultDir = et_resultDir.getText().toString().trim();
|
||||||
|
|
||||||
|
NfcTermuxCmd cmd = new NfcTermuxCmd();
|
||||||
|
cmd.setScript(script);
|
||||||
|
cmd.setArgs(argsStr.isEmpty() ? new String[0] : argsStr.split(","));
|
||||||
|
cmd.setWorkDir(workDir.isEmpty() ? null : workDir);
|
||||||
|
cmd.setBackground("true".equalsIgnoreCase(bgStr));
|
||||||
|
cmd.setResultDir(resultDir.isEmpty() ? null : resultDir);
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showToast(String msg) {
|
||||||
|
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user