Compare commits

...

63 Commits

Author SHA1 Message Date
8f6b615949 <appbase>APK 15.15.21 release Publish. 2026-04-28 17:08:05 +08:00
d02d57d4dd 添加LogUtils日志文件自动裁剪功能 2026-04-28 17:05:50 +08:00
e337bb7a04 <libappbase>Library Release 15.15.20 2026-04-27 20:19:13 +08:00
9ae848e4c2 <appbase>APK 15.15.20 release Publish. 2026-04-27 20:18:59 +08:00
9c66f61891 调整MainActivity按钮顺序将关于应用置顶 2026-04-27 20:15:54 +08:00
bfaf3543b9 添加多窗口支持与LogActivity独立任务栈 2026-04-27 20:04:17 +08:00
b44fe3aaf3 添加分屏测试功能支持多窗口MainActivity 2026-04-27 19:27:25 +08:00
d518ac50a9 优化LogActivity分屏模式支持Android 11适配 2026-04-27 18:40:36 +08:00
d532eae971 调整onLogTest调用LogActivity分屏模式 2026-04-27 17:58:53 +08:00
f661acbbbc 添加LogActivity重载函数支持分屏模式切换 2026-04-27 17:33:51 +08:00
ecced75a4d 调整AboutActivity工具栏与MainActivity一致 2026-04-25 12:10:17 +08:00
5e5d34c90c 调整BaseFunctionItemView派生控件高度间隔为无间隔 2026-04-25 10:43:18 +08:00
85a0d39498 添加BaseFunctionItemView类视图1像素美化边框 2026-04-25 10:40:50 +08:00
c542d8dca7 源码整理 2026-04-25 10:25:59 +08:00
ccbdb4010e 调整一下libappbase模块中layout_about_view.xml的布局文件。缩小一下布局中控件的高度间隔。 2026-04-25 10:22:20 +08:00
2e7b9173f2 <libappbase>Library Release 15.15.19 2026-04-25 04:12:02 +08:00
4f12a5de4f <appbase>APK 15.15.19 release Publish. 2026-04-25 04:11:46 +08:00
7ab399e520 编译调试 2026-04-25 04:10:29 +08:00
dd2d9f3e55 Merge branch 'appbase' of https://gitea.winboll.cc/Studio/WinBoLL.git into appbase 2026-04-25 04:07:49 +08:00
098516d4d7 Merge branch 'winboll' into appbase 2026-04-25 04:07:03 +08:00
b1fab5ce46 <libappbase>Library Release 15.15.18 2026-04-25 03:59:02 +08:00
e68098aa10 <appbase>APK 15.15.18 release Publish. 2026-04-10 05:38:18 +08:00
d673ba46a1 更正应用验证时使用的应用包名称配置 2026-04-10 05:36:14 +08:00
6c8867e15c 更正maven库引用顺序 2026-04-09 01:38:28 +08:00
5a1342156f <appbase>APK 15.15.17 release Publish. 2026-04-06 20:39:30 +08:00
4e1784d99f 更换App为APP。以适配应用版本检查时使用的APK应用包名称APPBase。 2026-04-06 20:38:50 +08:00
069e5a66ad <appbase>APK 15.15.16 release Publish. 2026-04-06 20:25:21 +08:00
e9a1dca8ca 应用调试功能切换Logo准备完成。 2026-04-06 20:23:37 +08:00
7e3a3d1446 添加调试状态切换Logo控件 2026-04-06 19:42:34 +08:00
7414cd0f33 <appbase>APK 15.15.15 release Publish. 2026-03-25 20:52:43 +08:00
b2b3f949b7 更新Git仓库项目名称 2026-03-25 20:51:24 +08:00
83c1b888b6 <appbase>APK 15.15.14 release Publish. 2026-03-25 20:36:14 +08:00
6afc81939d <appbase>APK 15.15.13 release Publish. 2026-03-25 20:17:45 +08:00
1cf4c67b4f <appbase>APK 15.15.12 release Publish. 2026-03-25 20:17:45 +08:00
89697f8c49 Merge remote-tracking branch 'origin/winboll' into appbase 2026-03-25 20:16:31 +08:00
610d3811db 栏目字体与分段调整 2026-03-18 15:49:42 +08:00
2d949eb5a3 分类栏目排版 2026-03-18 15:44:01 +08:00
e6940805d9 明确操作优先级 2026-03-18 15:41:48 +08:00
1641424276 通顺表达句法。 2026-03-18 15:36:58 +08:00
5d1cdff283 使用Markdown语法调整说明书显示格式。 2026-03-18 15:32:49 +08:00
da66cea1e5 详细解析说明 2026-03-18 12:05:42 +08:00
5eb7441dc7 更新说明书 2026-03-18 12:03:32 +08:00
5f3168e17f 更新说明书 2026-03-18 11:49:58 +08:00
e3c4bab6c9 更正项目说明书。 2026-03-18 11:41:23 +08:00
2af6427ca8 精简nfc action 数量 2026-03-17 04:03:20 +08:00
b8c70bef98 调整NFC接口窗体根据动作类型指定对应的脚本运行。 2026-03-16 16:42:02 +08:00
7713d6c460 基本实现NFC Build View 模块功能。 2026-03-15 20:25:16 +08:00
73c69bd665 20260315_193958_991 2026-03-15 19:40:11 +08:00
a076fe50cd 调整Termux 调用模块 UI显示与环境参数配置。 2026-03-15 13:45:49 +08:00
1512b76c36 编译参数修复 2026-03-15 11:55:10 +08:00
850b9af6ec 编译参数修复 2026-03-15 11:52:51 +08:00
31c1592086 Termux终端调用接口完成 2026-03-15 11:50:01 +08:00
b3976a8633 <winboll>APK 15.11.25 release Publish. 2026-03-15 11:48:40 +08:00
ea896228d7 <winboll>APK 15.11.24 release Publish. 2026-03-15 11:46:14 +08:00
d49ecb3943 <winboll>APK 15.11.23 release Publish. 2026-03-15 11:09:40 +08:00
ad3aecf867 <winboll>APK 15.11.22 release Publish. 2026-03-15 11:07:05 +08:00
c417d9732a <winboll>APK 15.11.21 release Publish. 2026-03-15 10:52:23 +08:00
7bd1357c8c <winboll>APK 15.11.20 release Publish. 2026-03-15 10:46:25 +08:00
16a2c3c0c8 <winboll>APK 15.11.19 release Publish. 2026-03-15 10:36:01 +08:00
b747d83972 <winboll>APK 15.11.18 release Publish. 2026-03-15 10:26:55 +08:00
f2788dda96 <winboll>APK 15.11.17 release Publish. 2026-03-15 10:09:28 +08:00
ea3a66bebe <winboll>APK 15.11.16 release Publish. 2026-03-15 10:07:00 +08:00
a53a0cbcdc <winboll>APK 15.11.15 release Publish. 2026-03-13 13:41:48 +08:00
30 changed files with 860 additions and 271 deletions

205
README.md
View File

@@ -1,104 +1,103 @@
WinBoLL 源生态计划项目说明书
# 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/
#### ☆ 源码地址:
★ Giteahttps://gitea.winboll.cc/Studio/WinBoLL.git
★ GitHubhttps://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 文件。
☆ "/sdcard/AppProjects/app.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
一、项目概述
1. 核心定位
【OriginMaster】WinBoLL 源生态计划,旨在通过核心项目 WinBoLL 联动系列开发库,构建手机端 Android 项目开发与多端编译同步的完整生态,实现手机与电脑的源码同步开发。
2. 仓库架构
仓库类型 包含仓库 功能说明
开发库 WinBoLL、APPBase、AES、PowerBell、Positions 核心开发依赖库,其中 WinBoLL 可作为应用开发的基础继承模板
分支汇总存档库 OriginMaster 仅用于汇总各开发库分支,不适宜作为开发库克隆使用,非应用开发基础库
3. 源码推送路径
- WinBoLL → APPBase → OriginMaster
- WinBoLL → AES → OriginMaster
- WinBoLL → PowerBell → OriginMaster
- WinBoLL → Positions → OriginMaster
二、WinBoLL APP 核心信息
1. 项目简介
WinBoLL Studio Android 应用开源项目,专注于手机端 Android 开发与多端编译同步。
2. 官方资源
- 官方网站https://www.winboll.cc/
- 源码地址:
- Giteahttps://gitea.winboll.cc/Studio/WinBoLL.git
- GitHubhttps://github.com/ZhanGSKen/WinBoLL.git
- 码云https://gitee.com/zhangsken/winboll.git
- 托管类库源码:
- APPBasejitpack.iohttps://github.com/ZhanGSKen/APPBase.git
- AESjitpack.iohttps://github.com/ZhanGSKen/AES.git
三、通用特征文件夹前置(/sdcard
- Linux 系统文件夹直接使用  /sdcard 
- 手机 SD 卡存储( /storage/emulated/0 挂载的别名也可为  /sdcard 
四、前置条件
1. WinBoLL-APP 配置
- APK 编译输出目录: /sdcard/WinBoLLStudio/APKs/ ,以及  /sdcard/AppProjects/ (命名为  app.apk 
- 签名与命名空间:支持应用签名验证定制化,与衍生 APP 共享  cc.winboll.studio  命名空间
五、核心需求规划
1. 主机端需求
- 支持  winboll.cc  域名的用户注册登录服务
- 支持  https://console.winboll.cc/api  访问
2. APP 端需求
- 实现手机端 Android 应用开发与管理功能
六、编译与使用指南
1. 项目初始化(必须)
1. 复制  settings.gradle-demo  settings.gradle 取消对应项目模块注释
2. 复制  gradle.properties-androidx-demo  gradle.properties-android-demo  gradle.properties 
3. (可选)复制  local.properties-demo  local.properties 配置 Android SDK 目录
4. 签名设置:
- 调试编译:进入 GenKeyStore 目录执行  bash gen_debug_keystore.sh 
- 非必须clone keystore 模块,拷贝  appkey.jks  appkey.keystore  到项目根目录
2. 编译命令
1类库型项目
1. 修改测试项目  build.properties 设置  libraryProject=<类库项目模块名> 
2. 编译测试项目 bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
3. 编译类库项目 bash .winboll/bashPublishLIBAddTag.sh <类库项目模块名> (发布至 WinBoLL Nexus Maven 库)
2应用型项目
- 编译命令 bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
3调试编译
- Beta 调试 bash gradlew assembleBetaDebug 
- Stage 调试 bash gradlew assembleStageDebug
 
4发布编译
- Stage 发布bash .winboll/bashPublishAPKAddTag.sh <应用项目模块名> 
或者执行  bash gradlew assembleStageRelease
3. 编译输出路径
- 默认路径(assembleBetaDebug任务) /sdcard/WinBoLLStudio/APKs/<项目根目录名称>/debug/ 
- 默认路径(assembleStageRelease任务) /sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/ 
- 额外路径:若  winboll.properties  配置  ExtraAPKOutputPath APK 同步拷贝至该ExtraAPKOutputPath路径
4. 版本号命名规则
- Stage 渠道 V<应用开发环境编号><应用功能变更号><应用调试阶段号> 示例 APPBase_15.7.0 
- Beta 渠道 V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> 示例 APPBase_15.9.6-beta8_5413 
## 八、WinBoLL 应用 APK 版本号命名规则
### ☆ Stage 渠道:
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号> 示例 APPBase_15.7.0 
### ☆ Beta 渠道:
#### V<应用开发环境编号><应用功能变更号><应用调试阶段号>-beta<调试编译计数>_<调试编译时间(分钟+秒钟)> 示例 APPBase_15.9.6-beta8_5413 

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Mar 25 11:45:40 GMT 2026
stageCount=12
#Tue Apr 28 17:08:04 HKT 2026
stageCount=22
libraryProject=libappbase
baseVersion=15.15
publishVersion=15.15.11
buildCount=35
baseBetaVersion=15.15.12
publishVersion=15.15.21
buildCount=0
baseBetaVersion=15.15.22

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AppBase+</string>
<string name="app_name">APPBase+</string>
</resources>

View File

@@ -16,6 +16,17 @@
android:label="@string/app_name"
android:exported="true"
android:resizeableActivity="true"
android:launchMode="singleTop"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>
<activity
android:name=".MainActivityAlias"
android:label="@string/app_name"
android:exported="true"
android:resizeableActivity="true"
android:launchMode="singleTop"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
<intent-filter>
@@ -30,6 +41,17 @@
</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

View File

@@ -23,17 +23,8 @@ public class AboutActivity extends Activity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
// 设置工具栏
Toolbar toolbar = findViewById(R.id.toolbar);
setActionBar(toolbar);
getActionBar().setSubtitle(TAG);
getActionBar().setDisplayHomeAsUpEnabled(true);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish(); // 点击导航栏返回按钮,触发 finish()
}
});
setActionBar(toolbar);
AboutView aboutView = findViewById(R.id.aboutview);
aboutView.setAPPInfo(genDefaultAppInfo());
@@ -43,10 +34,10 @@ public class AboutActivity extends Activity {
LogUtils.d(TAG, "genDefaultAppInfo() 调用");
String branchName = "appbase";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getString(R.string.app_name));
appInfo.setAppName("APPBase");
appInfo.setAppIcon(R.drawable.ic_winboll);
appInfo.setAppDescription(getString(R.string.app_description));
appInfo.setAppGitName("APPBase");
appInfo.setAppGitName("WinBoLL");
appInfo.setAppGitOwner("Studio");
appInfo.setAppGitAPPBranch(branchName);
appInfo.setAppGitAPPSubProjectFolder(branchName);

View File

@@ -21,9 +21,12 @@ public class App extends GlobalApplication {
*/
@Override
public void onCreate() {
super.onCreate(); // 调用父类初始化逻辑(如基础库配置、全局上下文设置)
//setIsDebugging(false);
setIsDebugging(BuildConfig.DEBUG);
super.onCreate();
// 如果应用不在调试状态,就根据编译类型设置调试状态
if (isDebugging() != true) {
setIsDebugging(BuildConfig.DEBUG);
}
// 初始化 Toast 工具类(传入应用全局上下文,确保 Toast 可在任意地方调用)
ToastUtils.init(getApplicationContext());
}

View File

@@ -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);
}
}
}

View File

@@ -103,14 +103,17 @@ public class MainActivity extends Activity {
}
}
public void onLogTestNewTask(View view) {
LogActivity.startLogActivity(this, true);
}
/**
* 日志测试按钮点击事件(打开日志查看界面)
* 启动 LogActivity用于查看应用运行日志
* @param view 触发事件的 View对应布局中的日志测试按钮
*/
public void onLogTest(View view) {
// 启动日志查看 Activity通过静态方法传入上下文简化跳转逻辑
LogActivity.startLogActivity(this);
LogActivity.startLogActivity(this, false);
}
/**
@@ -158,5 +161,35 @@ public class MainActivity extends Activity {
Intent aboutIntent = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(aboutIntent);
}
public void onSplitScreenMode(View view) {
LogUtils.d(TAG, "onSplitScreenMode() 分屏测试按钮已点击");
ToastUtils.show("分屏测试:已启动新窗口");
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
android.graphics.Rect bounds = new android.graphics.Rect();
getWindow().getDecorView().getDisplay().getRectSize(bounds);
int height = bounds.height();
int width = bounds.width();
bounds.set(0, 0, width, height / 2);
LogUtils.d(TAG, "onSplitScreenMode() 分屏窗口范围: " + bounds);
android.content.Intent intent = new android.content.Intent(this, MainActivityAlias.class);
intent.setFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
LogUtils.d(TAG, "onSplitScreenMode() 准备启动MainActivityAlias");
android.app.ActivityOptions options = android.app.ActivityOptions.makeBasic();
options.setLaunchBounds(bounds);
startActivity(intent, options.toBundle());
LogUtils.d(TAG, "onSplitScreenMode() MainActivityAlias已启动");
}
}
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已启动");
}
}

View File

@@ -0,0 +1,17 @@
package cc.winboll.studio.appbase;
import android.os.Bundle;
import android.view.View;
import android.widget.Toolbar;
import cc.winboll.studio.appbase.R;
public class MainActivityAlias extends MainActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setActionBar(toolbar);
}
}

View File

@@ -23,6 +23,18 @@
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="@android:color/white"
android:background="#81C7F5"
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"
@@ -47,6 +59,18 @@
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="@android:color/white"
android:background="#81C7F5"
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"
@@ -62,18 +86,29 @@
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="关于应用"
android:text="分屏测试"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="#81C7F5"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onAboutActivity"
android:onClick="onSplitScreenMode"
android:layout_margin="10dp"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="多开窗口"
android:textSize="16sp"
android:textColor="@android:color/white"
android:background="#81C7F5"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onMultiInstance"
android:layout_margin="10dp"/>
</LinearLayout>
</ScrollView>
</LinearLayout>
</LinearLayout>

View 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>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AppBase</string>
<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>

View File

@@ -1,6 +1,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
mavenLocal {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
@@ -11,19 +20,6 @@ buildscript {
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoLL Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
//println "mavenLocal : ==========="
//println mavenLocal().url
//println "mavenLocal : ==========="
//mavenLocal()
}
dependencies {
// 适配MIUI12
@@ -35,6 +31,15 @@ buildscript {
allprojects {
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
mavenLocal {
// 设置本地Maven仓库路径
url 'file:///sdcard/.m2/repository/'
@@ -45,19 +50,6 @@ allprojects {
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoLL Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral()
google()
//println "mavenLocal : ==========="
//println mavenLocal().url
//println "mavenLocal : ==========="
//mavenLocal()
}
ext {
// 定义全局变量,常用于版本管理

0
gradlew vendored Normal file → Executable file
View File

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Wed Mar 25 11:45:40 GMT 2026
stageCount=12
#Tue Apr 28 17:08:04 HKT 2026
stageCount=22
libraryProject=libappbase
baseVersion=15.15
publishVersion=15.15.11
buildCount=35
baseBetaVersion=15.15.12
publishVersion=15.15.21
buildCount=0
baseBetaVersion=15.15.22

View File

@@ -38,6 +38,7 @@
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:exported="true"
android:launchMode="singleInstance"
android:taskAffinity="cc.winboll.studio.libappbase.LogActivity"
android:process=":LogActivity">
</activity>

View File

@@ -1,8 +1,11 @@
package cc.winboll.studio.libappbase;
import android.app.Activity;
import android.app.ActivityOptions;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.libappbase.R;
@@ -46,20 +49,39 @@ public class LogActivity extends Activity {
* @param context 上下文Activity/Fragment用于启动 Activity
*/
public static void startLogActivity(Context context) {
// 创建启动当前 Activity 的 Intent
startLogActivity(context, true);
}
/**
* 启动日志 Activity 的静态方法重载(外部调用入口)
* @param context 上下文Activity/Fragment用于启动 Activity
* @param newTask 是否在新窗口中启动
*/
public static void startLogActivity(Context context, boolean newTask) {
Intent intent = new Intent(context, LogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 添加 Intent 标志:支持分屏/多窗口模式API 24+
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
// 添加 Intent 标志:创建新任务栈(避免并入调用者任务栈)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 添加 Intent 标志:标记为新文档(多任务窗口中独立显示)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
// 添加 Intent 标志:允许创建多个任务实例(支持多次启动独立窗口)
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
if (newTask) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
context.startActivity(intent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
// 启动 Activity
context.startActivity(intent);
Rect bounds = new Rect();
if (context instanceof Activity) {
Activity activity = (Activity) context;
activity.getWindow().getDecorView().getDisplay().getRectSize(bounds);
bounds.set(0, bounds.height() / 2, bounds.width(), bounds.height());
}
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchBounds(bounds);
context.startActivity(intent, options.toBundle());
} else {
context.startActivity(intent);
}
}
}

View File

@@ -100,10 +100,52 @@ public class LogUtils {
// 加载当前应用下的所有类的 TAG
addClassTAGList();
loadTAGBeanSettings();
checkAndTrimLogFileSize();
_IsInited = true;
LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString()));
}
private static void checkAndTrimLogFileSize() {
if (_mfLogCatchFile == null || !_mfLogCatchFile.exists()) {
return;
}
final long MAX_FILE_SIZE = 6291456L;
final long KEEP_FILE_SIZE = 3145728L;
long fileSize = _mfLogCatchFile.length();
if (fileSize <= MAX_FILE_SIZE) {
return;
}
long needSkip = fileSize - KEEP_FILE_SIZE;
try (FileInputStream fis = new FileInputStream(_mfLogCatchFile);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis));
FileOutputStream fos = new FileOutputStream(_mfLogCatchFile);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fos))) {
StringBuilder sb = new StringBuilder();
String line;
long skippedTotal = 0;
final String lineBreak = System.lineSeparator();
while ((line = reader.readLine()) != null) {
byte[] lineBytes = line.getBytes();
skippedTotal += lineBytes.length + lineBreak.getBytes().length;
if (skippedTotal > needSkip) {
sb.append(line).append(lineBreak);
}
}
writer.write(sb.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
public static Map<String, Boolean> getMapTAGList() {
return mapTAGList;
}

View File

@@ -19,7 +19,7 @@ import cc.winboll.studio.libappbase.utils.ApkSignUtils;
* @LastEditTime 2026-01-24 18:45:00
* @Describe 签名显示+正版校验对话框展示应用签名字节位信息调用网络接口完成正版合法性校验实时返回校验结果
*/
public class AppValidationDialog extends Dialog {
public class APPValidationDialog extends Dialog {
// ===================================== 全局常量 =====================================
public static final String TAG = "AppValidationDialog";
// 签名字节位分组大小
@@ -31,18 +31,18 @@ public class AppValidationDialog extends Dialog {
private TextView tvAuthResult;
// ===================================== 业务入参属性 =====================================
private String projectName;
private String appName;
private String versionName;
private String clientSign;
private String clientHash;
// ===================================== 构造方法 =====================================
public AppValidationDialog(Context context, String projectName, String versionName) {
public APPValidationDialog(Context context, String appName, String versionName) {
super(context, R.style.DialogStyle);
this.mContext = context;
this.projectName = projectName;
this.appName = appName;
this.versionName = versionName;
LogUtils.d(TAG, "AppValidationDialog: 构造方法初始化,入参-> projectName=" + projectName + ", versionName=" + versionName);
LogUtils.d(TAG, "AppValidationDialog: 构造方法初始化,入参-> projectName=" + appName + ", versionName=" + versionName);
}
// ===================================== 生命周期方法 =====================================
@@ -104,7 +104,7 @@ public class AppValidationDialog extends Dialog {
// 调用网络校验接口
new APPUtils().checkAPKValidation(
mContext,
projectName,
appName,
versionName,
clientSign,
clientHash,

View File

@@ -49,10 +49,10 @@ public class APPUtils {
* @param clientHash 外部计算的APK SHA256哈希字符串小写16进制
* @param callback 校验结果回调(主线程调用,返回是否合法+提示信息)
*/
public void checkAPKValidation(Context context, String projectName, String versionName,
public void checkAPKValidation(Context context, String appName, String versionName,
String clientSign, String clientHash, final CheckResultCallback callback) {
// 方法调用+全量入参调试日志
LogUtils.d(TAG, "checkAPKValidation: 方法调用,入参-> projectName=" + projectName
LogUtils.d(TAG, "checkAPKValidation: 方法调用,入参-> appName=" + appName
+ ", versionName=" + versionName + ", clientSign=" + clientSign + ", clientHash=" + clientHash);
// 1. 核心入参空值校验(快速失败)
@@ -61,7 +61,7 @@ public class APPUtils {
callCallbackOnMainThread(callback, false, "上下文对象不能为空");
return;
}
if (isStringEmpty(projectName)) {
if (isStringEmpty(appName)) {
LogUtils.w(TAG, "checkAPKValidation: 入参projectName为空/空白,直接返回校验失败");
callCallbackOnMainThread(callback, false, "项目名称不能为空");
return;
@@ -85,7 +85,7 @@ public class APPUtils {
// 2. 动态参数URL编码避免特殊字符导致请求解析异常
LogUtils.d(TAG, "checkAPKValidation: 开始对动态参数进行UTF-8 URL编码");
String encodeProjectName = urlEncode(projectName);
String encodeProjectName = urlEncode(appName);
String encodeVersionName = urlEncode(versionName);
String encodeClientSign = urlEncode(clientSign);
String encodeClientHash = urlEncode(clientHash);

View File

@@ -18,7 +18,7 @@ import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.libappbase.dialogs.DebugHostDialog;
import cc.winboll.studio.libappbase.dialogs.AppValidationDialog;
import cc.winboll.studio.libappbase.dialogs.APPValidationDialog;
import cc.winboll.studio.libappbase.models.APPInfo;
/**
@@ -76,10 +76,11 @@ public class AboutView extends LinearLayout {
private EditText metDevUserPassword;
// ===================================== 页面视图控件 =====================================
private ImageView ivAppIcon;
private DebugSwitchImageView ivAppIcon;
private TextView tvAppNameVersion;
private TextView tvAppDesc;
private LinearLayout llFunctionContainer;
private ImageButton ibSebugStepOver;
private ImageButton ibSigngetDialog;
private ImageButton ibWinBoLLHostDialog;
@@ -92,14 +93,14 @@ public class AboutView extends LinearLayout {
initViewFromXml();
}
public AboutView(Context context, APPInfo appInfo) {
super(context);
LogUtils.d(TAG, "AboutView(Context,APPInfo)传入应用信息appName=" + (appInfo == null ? "null" : appInfo.getAppName()));
this.mContext = context;
this.mAPPInfo = appInfo;
initViewFromXml();
initAll();
}
// public AboutView(Context context, APPInfo appInfo) {
// super(context);
// LogUtils.d(TAG, "AboutView(Context,APPInfo)传入应用信息appName=" + (appInfo == null ? "null" : appInfo.getAppName()));
// this.mContext = context;
// this.mAPPInfo = appInfo;
// initViewFromXml();
// initAll();
// }
public AboutView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -128,10 +129,10 @@ public class AboutView extends LinearLayout {
return;
}
initDefaultParams();
initAppBaseInfo();
initAppVersionInfo();
initAPPBaseInfo();
initAPPVersionInfo();
initServerConfig();
initAppLinkInfo();
initAPPLinkInfo();
initReleaseAPKInfo();
initAboutPageView();
LogUtils.d(TAG, "initAll():所有初始化流程执行完成");
@@ -141,13 +142,13 @@ public class AboutView extends LinearLayout {
* 重置应用信息并重新初始化页面,支持动态更新关于页内容
* @param appInfo 新的应用信息实体
*/
public void setAPPInfoAndInit(APPInfo appInfo) {
LogUtils.d(TAG, "setAPPInfoAndInit()重置应用信息appName=" + (appInfo == null ? "null" : appInfo.getAppName()));
this.mAPPInfo = appInfo;
if (llFunctionContainer != null) llFunctionContainer.removeAllViews();
initAll();
LogUtils.d(TAG, "setAPPInfoAndInit():应用信息重置+页面重构完成");
}
// public void setAPPInfoAndInit(APPInfo appInfo) {
// LogUtils.d(TAG, "setAPPInfoAndInit()重置应用信息appName=" + (appInfo == null ? "null" : appInfo.getAppName()));
// this.mAPPInfo = appInfo;
// if (llFunctionContainer != null) llFunctionContainer.removeAllViews();
// initAll();
// LogUtils.d(TAG, "setAPPInfoAndInit():应用信息重置+页面重构完成");
// }
/**
* 设置应用信息,兼容旧调用逻辑,设置后自动重构页面
@@ -185,30 +186,35 @@ public class AboutView extends LinearLayout {
* 加载XML布局并绑定所有视图控件初始化按钮点击事件
*/
private void initViewFromXml() {
LogUtils.d(TAG, "initViewFromXml():开始加载布局并绑定控件");
View.inflate(mContext, R.layout.layout_about_view, this);
// 基础控件绑定
ivAppIcon = findViewById(R.id.iv_app_icon);
tvAppNameVersion = findViewById(R.id.tv_app_name_version);
tvAppDesc = findViewById(R.id.tv_app_desc);
llFunctionContainer = findViewById(R.id.ll_function_container);
// 功能按钮绑定
ibSigngetDialog = findViewById(R.id.ib_signgetdialog);
ibWinBoLLHostDialog = findViewById(R.id.ib_winbollhostdialog);
// 调试地址按钮动态显隐
ibWinBoLLHostDialog.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
// 绑定按钮点击事件
setBtnClickListener();
LogUtils.d(TAG, "initViewFromXml():布局加载+控件绑定+事件初始化完成");
}
LogUtils.d(TAG, "initViewFromXml():开始加载布局并绑定控件");
View.inflate(mContext, R.layout.layout_about_view, this);
// 基础控件绑定
ivAppIcon = findViewById(R.id.iv_app_icon);
tvAppNameVersion = findViewById(R.id.tv_app_name_version);
tvAppDesc = findViewById(R.id.tv_app_desc);
llFunctionContainer = findViewById(R.id.ll_function_container);
// 功能按钮绑定
ibSebugStepOver = findViewById(R.id.ib_debug_step_over);
ibSigngetDialog = findViewById(R.id.ib_signgetdialog);
ibWinBoLLHostDialog = findViewById(R.id.ib_winbollhostdialog);
// 调试按钮统一只在调试模式显示
ibWinBoLLHostDialog.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
//ibSigngetDialog.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
ibSebugStepOver.setVisibility(GlobalApplication.isDebugging() ? View.VISIBLE : View.GONE);
// 绑定按钮点击事件
setBtnClickListener();
LogUtils.d(TAG, "initViewFromXml():布局加载+控件绑定+事件初始化完成");
}
/**
* 从APPInfo实体读取应用基础核心配置赋值到本地属性
*/
private void initAppBaseInfo() {
LogUtils.d(TAG, "initAppBaseInfo()开始读取APPInfo基础配置");
private void initAPPBaseInfo() {
LogUtils.d(TAG, "initAPPBaseInfo()开始读取APPInfo基础配置");
if (mAPPInfo == null) {
LogUtils.w(TAG, "initAppBaseInfo()跳过执行APPInfo为null");
LogUtils.w(TAG, "initAPPBaseInfo()跳过执行APPInfo为null");
return;
}
mszAppName = mAPPInfo.getAppName() == null ? "" : mAPPInfo.getAppName();
@@ -218,22 +224,22 @@ public class AboutView extends LinearLayout {
mszAppDescription = mAPPInfo.getAppDescription() == null ? "" : mAPPInfo.getAppDescription();
mnAppIcon = (mAPPInfo.getAppIcon() != 0) ? mAPPInfo.getAppIcon() : mnAppIcon;
mIsAddDebugTools = mAPPInfo.isAddDebugTools();
LogUtils.d(TAG, "initAppBaseInfo():基础配置读取完成,应用名=" + mszAppName + ",调试开关=" + mIsAddDebugTools);
LogUtils.d(TAG, "initAPPBaseInfo():基础配置读取完成,应用名=" + mszAppName + ",调试开关=" + mIsAddDebugTools);
}
/**
* 从包管理中获取当前应用版本号,初始化版本相关信息
*/
private void initAppVersionInfo() {
LogUtils.d(TAG, "initAppVersionInfo():开始初始化应用版本信息");
private void initAPPVersionInfo() {
LogUtils.d(TAG, "initAPPVersionInfo():开始初始化应用版本信息");
try {
mszAppVersionName = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "initAppVersionInfo()获取版本号失败默认赋值unknown", e);
LogUtils.e(TAG, "initAPPVersionInfo()获取版本号失败默认赋值unknown", e);
mszAppVersionName = "unknown";
}
mszCurrentAppPackageName = String.format("%s_%s.apk", mszAppVersionName, mszAppVersionName);
LogUtils.d(TAG, "initAppVersionInfo():版本信息初始化完成,版本号=" + mszAppVersionName + "当前APK名=" + mszCurrentAppPackageName);
LogUtils.d(TAG, "initAPPVersionInfo():版本信息初始化完成,版本号=" + mszAppVersionName + "当前APK名=" + mszCurrentAppPackageName);
}
/**
@@ -246,10 +252,10 @@ public class AboutView extends LinearLayout {
/**
* 初始化应用相关链接(主页+Git源码地址根据分支配置动态拼接Git地址
*/
private void initAppLinkInfo() {
LogUtils.d(TAG, "initAppLinkInfo():开始初始化应用链接信息");
private void initAPPLinkInfo() {
LogUtils.d(TAG, "initAPPLinkInfo():开始初始化应用链接信息");
if (mAPPInfo == null) {
LogUtils.w(TAG, "initAppLinkInfo()跳过执行APPInfo为null");
LogUtils.w(TAG, "initAPPLinkInfo()跳过执行APPInfo为null");
return;
}
mszHomePage = mAPPInfo.getAppHomePage() == null ? "" : mAPPInfo.getAppHomePage();
@@ -261,7 +267,7 @@ public class AboutView extends LinearLayout {
mAPPInfo.getAppGitOwner(), mszAppGitName,
mAPPInfo.getAppGitAPPBranch(), mAPPInfo.getAppGitAPPSubProjectFolder());
}
LogUtils.d(TAG, "initAppLinkInfo():链接信息初始化完成,应用主页=" + mszHomePage + "Git地址=" + mszGitea);
LogUtils.d(TAG, "initAPPLinkInfo():链接信息初始化完成,应用主页=" + mszHomePage + "Git地址=" + mszGitea);
}
/**
@@ -312,12 +318,22 @@ public class AboutView extends LinearLayout {
*/
private void setBtnClickListener() {
LogUtils.d(TAG, "setBtnClickListener():开始绑定功能按钮点击事件");
// 正版校验弹窗
// 取消调试状态按钮
ibSebugStepOver.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "ibSebugStepOver onClick取消调试状态按钮已点击");
GlobalApplication.setIsDebugging(false);
GlobalApplication.saveDebugStatus(GlobalApplication.getInstance());
ToastUtils.show("已取消调试状态,重启应用可生效。");
}
});
// 正版校验弹窗
ibSigngetDialog.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "ibSigngetDialog onClick唤起应用正版校验弹窗");
new AppValidationDialog(mContext, mszAppGitName, mszAppVersionName).show();
new APPValidationDialog(mContext, mszAppName, mszAppVersionName).show();
}
});
// 调试地址配置弹窗
@@ -337,7 +353,7 @@ public class AboutView extends LinearLayout {
*/
private void addFunctionView(View view) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
params.topMargin = dp2px(PADDING_SMALL);
params.topMargin = 0;
llFunctionContainer.addView(view, params);
}
@@ -397,7 +413,18 @@ public class AboutView extends LinearLayout {
setPadding(dp2px(PADDING_MID), dp2px(PADDING_SMALL), dp2px(PADDING_MID), dp2px(PADDING_SMALL));
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
setClickable(true);
setBackgroundResource(android.R.drawable.list_selector_background);
setBackground(create_item_background());
}
/**
* 创建带1像素边框的背景drawable
*/
private android.graphics.drawable.Drawable create_item_background() {
android.graphics.drawable.GradientDrawable drawable = new android.graphics.drawable.GradientDrawable();
drawable.setStroke(1, mItemContext.getResources().getColor(R.color.gray_200));
drawable.setCornerRadius(4);
drawable.setColor(mItemContext.getResources().getColor(android.R.color.white));
return drawable;
}
/**

View File

@@ -0,0 +1,61 @@
package cc.winboll.studio.libappbase.views;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.Toast;
import cc.winboll.studio.libappbase.GlobalApplication;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/04/06 19:32
* @Describe 具有调试模式切换功能的应用Logo控件连续点击10次弹出提示
*/
public class DebugSwitchImageView extends ImageView {
public static final String TAG = "DebugSwitchImageView";
// 连续点击计数
private int mClickCount = 0;
// 目标点击次数
private static final int TARGET_CLICK_COUNT = 10;
public DebugSwitchImageView(Context context) {
super(context);
init();
}
public DebugSwitchImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public DebugSwitchImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public DebugSwitchImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mClickCount++;
if (mClickCount == TARGET_CLICK_COUNT) {
// 达到10次弹出Toast
Toast.makeText(getContext(), "连续点击已达到10次现在开启应用调试功能。", Toast.LENGTH_SHORT).show();
GlobalApplication.setIsDebugging(true);
GlobalApplication.saveDebugStatus(GlobalApplication.getInstance());
// 重置计数,可再次触发
mClickCount = 0;
}
}
});
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M19,7H16.19C15.74,6.2 15.12,5.5 14.37,5L16,3.41L14.59,2L12.42,4.17C11.96,4.06 11.5,4 11,4S10.05,4.06 9.59,4.17L7.41,2L6,3.41L7.62,5C6.87,5.5 6.26,6.21 5.81,7H3V9H5.09C5.03,9.33 5,9.66 5,10V11H3V13H5V14C5,14.34 5.03,14.67 5.09,15H3V17H5.81C7.26,19.5 10.28,20.61 13,19.65V19C13,18.43 13.09,17.86 13.25,17.31C12.59,17.76 11.8,18 11,18C8.79,18 7,16.21 7,14V10C7,7.79 8.79,6 11,6S15,7.79 15,10V14C15,14.19 15,14.39 14.95,14.58C15.54,14.04 16.24,13.62 17,13.35V13H19V11H17V10C17,9.66 16.97,9.33 16.91,9H19V7M13,9V11H9V9H13M13,13V15H9V13H13M16,16H22V22H16V16Z"/>
</vector>

View File

@@ -10,15 +10,15 @@
android:orientation="vertical"
android:gravity="center_horizontal"
android:paddingLeft="16dp"
android:paddingTop="32dp"
android:paddingTop="16dp"
android:paddingRight="16dp"
android:paddingBottom="32dp">
android:paddingBottom="16dp">
<ImageView
<cc.winboll.studio.libappbase.views.DebugSwitchImageView
android:id="@+id/iv_app_icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="16dp"
android:layout_marginBottom="8dp"
android:scaleType="centerCrop"/>
<TextView
@@ -32,16 +32,16 @@
android:id="@+id/tv_app_desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:textSize="14sp"
android:textColor="@color/gray_500"/>
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="8dp"
android:layout_marginBottom="16dp"
android:layout_marginTop="4dp"
android:layout_marginBottom="8dp"
android:background="@color/gray_200"/>
<LinearLayout
@@ -55,9 +55,18 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="16dp"
android:layout_marginTop="8dp"
android:spacing="20dp">
<ImageButton
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_debug_step_over"
android:id="@+id/ib_debug_step_over"
android:scaleType="fitCenter"
android:adjustViewBounds="true"
android:background="@null"/>
<ImageButton
android:layout_width="48dp"
android:layout_height="48dp"

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Thu Jan 29 17:03:53 HKT 2026
stageCount=15
#Wed Apr 08 17:37:24 GMT 2026
stageCount=26
libraryProject=
baseVersion=15.11
publishVersion=15.11.14
buildCount=0
baseBetaVersion=15.11.15
publishVersion=15.11.25
buildCount=30
baseBetaVersion=15.11.26

View File

@@ -18,7 +18,7 @@
tools:ignore="QueryAllPackagesPermission" />
<!-- 可选:兼容低版本系统 -->
<uses-permission android:name="android.permission.GET_PACKAGE_SIZE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
@@ -291,6 +291,18 @@
<activity android:name="cc.winboll.studio.winboll.unittest.TermuxEnvTestActivity"/>
<activity
android:name=".termux.NfcTermuxBridgeActivity"
android:exported="true"> <!-- 必须设置为 true允许外部应用调用 -->
<!-- 接收 ACTION_BUILD 意图 -->
<intent-filter>
<action android:name="cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,14 @@
package cc.winboll.studio.winboll.models;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/03/15 08:46
*/
public class NfcTermuxCmd {
public String script; // 要执行的预制脚本名(如 auth.sh
public String[] args; // 脚本参数
public String workDir; // 工作目录
public boolean background; // 是否后台执行
public String resultDir; // 结果输出目录(可为 null
}

View File

@@ -0,0 +1,241 @@
/*
* 源码说明与描述:
* NFC 与 Termux 桥接活动,用于接收外部应用(包调用)传递的 JSON 指令并执行 Termux 脚本命令。
* 支持 ACTION_BUILD后台执行与 ACTION_BUILD_VIEW终端窗口唤起两种动作。
*
* 作者:豆包&ZhanGSKen<zhangsken@qq.com>
* 创建时间2025-03-15 14:00:00
* 最后编辑时间2026-03-16 10:00:00
*/
package cc.winboll.studio.winboll.termux;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.winboll.models.NfcTermuxCmd;
import com.google.gson.Gson;
public class NfcTermuxBridgeActivity extends Activity {
// ========================= 常量与静态属性 =========================
public static final String TAG = "NfcTermuxBridgeActivity";
// 外部应用调用时使用的 Action 常量
public static final String ACTION_BUILD = "cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity.ACTION_BUILD";
private static final Gson GSON = new Gson();
// ========================= 生命周期方法 =========================
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LogUtils.d(TAG, "onCreate() 调用savedInstanceState: " + (savedInstanceState != null ? "非空" : ""));
dispatchIntent(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
LogUtils.d(TAG, "onNewIntent() 调用intent: " + (intent != null ? intent.toString() : "null"));
if (intent != null) {
LogUtils.d(TAG, "onNewIntent() action: " + intent.getAction());
LogUtils.d(TAG, "onNewIntent() data: " + intent.getDataString());
LogUtils.d(TAG, "onNewIntent() extras: " + intent.getExtras());
LogUtils.d(TAG, "onNewIntent() flags: " + intent.getFlags());
LogUtils.d(TAG, "onNewIntent() component: " + intent.getComponent());
} else {
LogUtils.w(TAG, "onNewIntent() intent is null");
}
dispatchIntent(intent);
}
// ========================= 统一 Intent 分发(合并去重) =========================
/**
* 统一分发 Intent根据 Action 路由到不同业务逻辑
* @param intent 外部传入的 Intent
*/
private void dispatchIntent(Intent intent) {
LogUtils.d(TAG, "dispatchIntent() 分发 intent");
if (intent == null) {
LogUtils.w(TAG, "dispatchIntent() intent is null");
return;
}
String action = intent.getAction();
if (ACTION_BUILD.equals(action)) {
ToastUtils.show("ACTION_BUILD 命中");
onOpenTermuxProjectBuild(intent);
} else {
LogUtils.w(TAG, "dispatchIntent() 未知 Action: " + action);
finish();
}
}
// ========================= 核心业务方法 =========================
/**
* 处理 ACTION_BUILD 动作:后台执行 NFC 指令
*/
// private void handleNfcIntent(Intent intent) {
// LogUtils.d(TAG, "handleNfcIntent() 调用");
// if (intent == null) {
// LogUtils.w(TAG, "handleNfcIntent() intent 为空");
// return;
// }
//
// try {
// String json = intent.getStringExtra(Intent.EXTRA_TEXT);
// LogUtils.d(TAG, "handleNfcIntent() json: " + json);
//
// if (json == null || json.isEmpty()) {
// LogUtils.e(TAG, "handleNfcIntent() 指令为空");
// showToast("指令为空");
// finish();
// return;
// }
//
// NfcTermuxCmd cmd = GSON.fromJson(json, NfcTermuxCmd.class);
// LogUtils.d(TAG, "handleNfcIntent() cmd: " + cmd);
//
// if (cmd.script == null || cmd.script.isEmpty()) {
// LogUtils.e(TAG, "handleNfcIntent() script 为空");
// showToast("script 不能为空");
// finish();
// return;
// }
//
// //String scriptPath = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/" + cmd.script;
// String scriptPath = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/" + "BuildWinBoLLProject.sh";
// LogUtils.d(TAG, "handleNfcIntent() 脚本路径: " + scriptPath);
//
// boolean success = TermuxCommandExecutor.executeCommand(
// this, scriptPath, cmd.args, cmd.workDir, cmd.background, cmd.resultDir
// );
// LogUtils.d(TAG, "handleNfcIntent() 执行结果: " + success);
//
// if (success) {
// showToast("指令已发送: " + cmd.script);
// LogUtils.i(TAG, "执行成功: " + scriptPath);
// } else {
// showToast("指令发送失败");
// LogUtils.e(TAG, "执行失败");
// }
//
// } catch (Exception e) {
// LogUtils.e(TAG, "handleNfcIntent() 异常: " + e.getMessage(), e);
// showToast("解析失败");
// } finally {
// finish();
// }
// }
/**
* 处理 ACTION_BUILD_VIEW 动作:唤起 Termux 窗口执行命令
*/
public void onOpenTermuxProjectBuild(Intent intent) {
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 调用");
if (intent == null) {
LogUtils.w(TAG, "onOpenTermuxProjectBuildView() intent 为空");
return;
}
try {
String json = intent.getStringExtra(Intent.EXTRA_TEXT);
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() json: " + json);
if (json == null || json.isEmpty()) {
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() 指令为空");
showToast("指令为空");
finish();
return;
}
NfcTermuxCmd cmd = GSON.fromJson(json, NfcTermuxCmd.class);
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() cmd: " + cmd);
if (cmd.script == null || cmd.script.isEmpty()) {
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() script 为空");
showToast("script 不能为空");
finish();
return;
}
StringBuilder targetCmd = new StringBuilder();
String nfcScriptFolder = "/data/data/com.termux/files/home/TermuxWorkSpaces/BashShells/AutoNFC/";
targetCmd.append("cd " + nfcScriptFolder + " && ");
//targetCmd.append("stdbuf -o0 -e0 -i0 bash ").append(cmd.script).append(" ");
targetCmd.append("stdbuf -o0 -e0 -i0 bash ").append("BuildWinBoLLProject.sh").append(" ");
if (cmd.args != null) {
for (String arg : cmd.args) {
targetCmd.append(arg).append(" ");
}
}
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 命令: " + targetCmd);
boolean cmdSuccess = TermuxCommandExecutor.executeTerminalCommand(this, targetCmd.toString());
LogUtils.d(TAG, "onOpenTermuxProjectBuildView() 执行结果: " + cmdSuccess);
if (cmdSuccess) {
showToast("指令已发送: " + cmd.script);
} else {
showToast("指令发送失败");
}
} catch (Exception e) {
LogUtils.e(TAG, "onOpenTermuxProjectBuildView() 异常: " + e.getMessage(), e);
showToast("解析失败");
} finally {
finish();
}
}
// ========================= 公共静态测试方法 =========================
/**
* 内部测试方法:发送 ACTION_BUILD 指令
*/
public static void testCommand(Context context) {
LogUtils.d(TAG, "testCommand()");
try {
String testJson = "{\"script\":\"BuildWinBoLLProject.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
Intent intent = new Intent(context, NfcTermuxBridgeActivity.class);
intent.setAction(ACTION_BUILD); // 指定 Action
intent.putExtra(Intent.EXTRA_TEXT, testJson);
context.startActivity(intent);
} catch (Exception e) {
LogUtils.e(TAG, "testCommand() 失败: " + e.getMessage());
}
}
/**
* 内部测试方法:发送 ACTION_BUILD_VIEW 指令
*/
// public static void testViewCommand(Context context) {
// LogUtils.d(TAG, "testViewCommand()");
// try {
// String testJson = "{\"script\":\"BuildWinBoLLProjectView.sh\",\"args\":[\"DebugTemp\"],\"workDir\":null,\"background\":true,\"resultDir\":null}";
// Intent intent = new Intent(context, NfcTermuxBridgeActivity.class);
// intent.setAction(ACTION_BUILD_VIEW); // 指定 Action
// intent.putExtra(Intent.EXTRA_TEXT, testJson);
// context.startActivity(intent);
// } catch (Exception e) {
// LogUtils.e(TAG, "testViewCommand() 失败: " + e.getMessage());
// }
// }
// ========================= 工具方法 =========================
/**
* 统一显示 Toast确保在主线程调用
*/
private void showToast(final String message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(NfcTermuxBridgeActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@@ -1,5 +1,6 @@
package cc.winboll.studio.winboll.unittest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -7,11 +8,11 @@ import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.winboll.MainActivity;
import cc.winboll.studio.libappbase.ToastUtils;
import cc.winboll.studio.winboll.R;
import cc.winboll.studio.winboll.activities.BaseWinBoLLActivity;
import cc.winboll.studio.winboll.termux.NfcTermuxBridgeActivity;
import cc.winboll.studio.winboll.termux.TermuxCommandExecutor;
import java.io.BufferedReader;
import java.io.File;
@@ -19,7 +20,6 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import android.app.Activity;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
@@ -38,8 +38,8 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
// 常量属性(置顶排列)
public static final String TAG = "TermuxEnvTestActivity";
private static final String TERMUX_HOME_PATH = "/sdcard/WinBoLLStudio";
private static final String CMD_RESULT_FILE = TERMUX_HOME_PATH + "/.authcenter_cmd_result.tmp";
private static final String TERMUX_HOME_PATH = "/data/data/com.termux/files/home/TermuxWorkSpaces";
private static final String CMD_RESULT_FILE = TERMUX_HOME_PATH + "/CMD_RESULT_FILE.log";
// 成员属性(常量后排列)
private Toolbar mToolbar;
@@ -103,12 +103,12 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
LogUtils.d(TAG, "initToolbar() 导航栏返回按钮点击");
getActivity().finish();
}
});
@Override
public void onClick(View v) {
LogUtils.d(TAG, "initToolbar() 导航栏返回按钮点击");
getActivity().finish();
}
});
LogUtils.d(TAG, "initToolbar() 初始化完成");
}
@@ -158,7 +158,7 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
/**
* 测试执行Gradle命令实时输出版唤起Termux窗口
*/
public void onTestTermuxCMD(View view) {
public void onTestTermuxGradleBuildCMD(View view) {
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击执行Gradle命令实时输出");
tvMessage.append("\n【测试执行Gradle命令实时输出\n");
@@ -171,7 +171,7 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
// 2. 定义核心路径确保路径与Termux中一致
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
String projectPath = TERMUX_HOME_PATH + "/Sources/WinBoLL"; // 项目目录
String projectPath = TERMUX_HOME_PATH + "/Sources/DebugTemp"; // 项目目录
// 3. 构造命令核心用stdbuf禁用缓冲实现实时输出
String targetCmd = "";
@@ -216,7 +216,17 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
tvMessage.append("💡 若未实时输出请在Termux中执行pkg install coreutils安装stdbuf\n");
tvMessage.append("-------------------------\n");
}
public void onTestWinBoLLProjectBuild(View view) {
ToastUtils.show("onTestWinBoLLProjectBuild");
NfcTermuxBridgeActivity.testCommand(this);
}
// public void onTestWinBoLLProjectBuildView(View view) {
// ToastUtils.show("onTestWinBoLLProjectBuildView");
// NfcTermuxBridgeActivity.testViewCommand(this);
// }
public void onOpenTermuxBash(View view) {
LogUtils.d(TAG, "onTestTermuxCMD() 按钮点击执行Gradle命令实时输出");
tvMessage.append("\n【测试执行Gradle命令实时输出\n");
@@ -230,7 +240,7 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
// 2. 定义核心路径确保路径与Termux中一致
String gradleFullPath = "/data/data/com.termux/files/home/gradle/gradle-7.5.1/bin/gradle";
String projectPath = TERMUX_HOME_PATH + "/Sources/WinBoLL"; // 项目目录
String projectPath = TERMUX_HOME_PATH + "/"; // 项目目录
// 3. 构造命令核心用stdbuf禁用缓冲实现实时输出
String targetCmd = "";
@@ -275,7 +285,7 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
tvMessage.append("💡 若未实时输出请在Termux中执行pkg install coreutils安装stdbuf\n");
tvMessage.append("-------------------------\n");
}
/**
* 跨包读取Termux命令结果文件保留原功能兼容其他场景
*/
@@ -399,7 +409,7 @@ public class TermuxEnvTestActivity extends BaseWinBoLLActivity {
String filePath = file.getAbsolutePath();
String fileSize = file.isFile() ? " | 大小:" + formatFileSize(file.length()) : "";
String filePerm = " | 权限r:" + file.canRead() + "/w:" + file.canWrite();
result.append(fileType);
result.append(" ");
result.append(fileName);

View File

@@ -16,25 +16,35 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OpenTermuxBash"
android:text="Open Termux Bash"
android:onClick="onOpenTermuxBash"/>
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestTermuxCMD"
android:onClick="onTestTermuxCMD"/>
android:text="Test Termux Gradle Build CMD"
android:onClick="onTestTermuxGradleBuildCMD"/>
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestTermuxEnv"
android:text="Test Termux Env"
android:onClick="onTestTermuxEnv"/>
<Button
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test WinBoLL Project Build"
android:onClick="onTestWinBoLLProjectBuild"/>
</LinearLayout>
<ScrollView