Compare commits
86 Commits
androiddem
...
appbase
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6ce490436 | ||
|
|
6ddb599e6c | ||
|
|
f332abaa05 | ||
|
|
935dba200e | ||
|
|
02c9776062 | ||
|
|
dfc8de83d7 | ||
|
|
a34e3e1633 | ||
|
|
5dc87361cc | ||
|
|
269e7b30be | ||
|
|
ca953adec2 | ||
|
|
916e807f1b | ||
|
|
1f57ac5401 | ||
|
|
1ba19d8f77 | ||
|
|
435f7ced79 | ||
|
|
bd3270f8dd | ||
|
|
5489977a1a | ||
| 0bb909427c | |||
|
|
d4f054a86b | ||
| caef679d9a | |||
|
|
5194d7a188 | ||
| a0f88a423a | |||
|
|
81b5141608 | ||
| 3a1eaaf92d | |||
|
|
ddda54bd75 | ||
|
|
829268a9cc | ||
|
|
70a5405077 | ||
|
|
894edd5fa4 | ||
|
|
cf6209e2b4 | ||
|
|
80424c523d | ||
|
|
f5b2500f06 | ||
| ff8d09fb00 | |||
|
|
236339d256 | ||
|
|
edaf7007d6 | ||
|
|
12f4fd3f45 | ||
|
|
f7d9478f9e | ||
|
|
6ba57089cd | ||
|
|
94054c39e8 | ||
|
|
dee9586748 | ||
|
|
72ba518c6d | ||
| eccf9b9bde | |||
|
|
14abd9edd8 | ||
| fbd6f70b36 | |||
| 4ddc187b58 | |||
|
|
047de2bd1b | ||
|
|
0ede961391 | ||
|
|
33d4a1f0bb | ||
| c30588f174 | |||
| b5ccbd09f4 | |||
|
|
d8a6049abb | ||
| 8e15c82129 | |||
| 44fc3b1899 | |||
|
|
cdb9d8d41b | ||
|
|
51b6179df6 | ||
|
|
f32563b906 | ||
| 5cb6309134 | |||
| 27f84d9e18 | |||
|
|
3e8dde50ec | ||
|
|
fd0315d3cf | ||
| e9bb789daa | |||
| dbff19e7f4 | |||
|
|
44679d0c8a | ||
| c1af20009a | |||
|
|
cb3796bfea | ||
| 6656161903 | |||
|
|
edc63c750b | ||
| b170085482 | |||
| 6e8ba3394d | |||
| b91fe3c16b | |||
| e30b7acfdb | |||
| 6221555dfe | |||
|
|
c60eb29f4f | ||
|
|
3efcf40025 | ||
|
|
27e948b86d | ||
|
|
ae4742f3e2 | ||
|
|
d3312cbb29 | ||
|
|
58f3778dff | ||
|
|
f5e2961445 | ||
|
|
e4e9c31f02 | ||
|
|
a02a7efe43 | ||
|
|
2220efd009 | ||
|
|
0bc0fdf8c2 | ||
|
|
6b7b07a6fd | ||
|
|
8f3417818d | ||
|
|
ca280c7334 | ||
|
|
8172cf7b6f | ||
|
|
8014f4149c |
16
.gitignore
vendored
16
.gitignore
vendored
@@ -87,19 +87,15 @@ lint/tmp/
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Custom
|
||||
.androidide
|
||||
# 忽略 Lint 输出文件
|
||||
lint-results.xml
|
||||
lint-results.html
|
||||
winboll.properties
|
||||
local.properties
|
||||
|
||||
## 忽略 AndroidIDE 临时文件夹
|
||||
.androidide
|
||||
|
||||
## 忽略模块应用编译配置
|
||||
/settings.gradle
|
||||
/gradle.properties
|
||||
|
||||
## 忽略 srv 纠结问题
|
||||
/srv/
|
||||
|
||||
## 忽略 winboll-x 文件夹
|
||||
/winboll-x/
|
||||
/winboll.properties
|
||||
/local.properties
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "libjc/jcc/libs"]
|
||||
path = libjc/jcc/libs
|
||||
url = https://gitea.winboll.cc/Studio/APP_libjc_jcc_libs.git
|
||||
[submodule "keystore"]
|
||||
path = keystore
|
||||
url = https://gitea.winboll.cc/Studio/keystore.git
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
//
|
||||
|
||||
android {
|
||||
def winbollProps = new Properties()
|
||||
def winbollPropsFile = rootProject.file("${winbollFilePath}")
|
||||
assert(winbollPropsFile.exists())
|
||||
winbollProps.load(new FileInputStream(winbollPropsFile))
|
||||
|
||||
// 读取秘钥配置文件
|
||||
//
|
||||
def keyProps = new Properties()
|
||||
@@ -122,6 +127,7 @@ android {
|
||||
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 文件设置了类库模块项目文件名
|
||||
@@ -133,14 +139,15 @@ android {
|
||||
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 标签管理文件夹
|
||||
// 如果正在发布,就拷贝到 WinBoLL 标签管理文件夹,和处理 Git 仓库管理任务。
|
||||
//
|
||||
if((variant.flavorName == "stage"&&variant.buildType.name == "debug")
|
||||
|| (variant.flavorName == "stage"&&variant.buildType.name == "release")){
|
||||
if(variant.flavorName == "stage"&&variant.buildType.name == "release"){
|
||||
// 发布 APK 文件
|
||||
//
|
||||
// 截取版本号的版本字段为短版本名
|
||||
@@ -221,6 +228,7 @@ android {
|
||||
}
|
||||
|
||||
// 提交新的编译标志配置
|
||||
println 'exec bashCommitAppPublishBuildFlagInfoFilePath ...'
|
||||
def resultCommitBuildFlag = exec {
|
||||
commandLine 'bash', '--', "${RootProjectDir}/${bashCommitAppPublishBuildFlagInfoFilePath}", "${RootProjectDir}", "${versionName}", variant.buildType.name , rootProject.name
|
||||
}
|
||||
@@ -228,22 +236,78 @@ android {
|
||||
assert(resultCommitBuildFlag.getExitValue() == 0)
|
||||
}
|
||||
} // if(variant.buildType.name == "release"){
|
||||
|
||||
// 如果公共目录存在就拷贝到公共目录并重命名为app.apk
|
||||
//
|
||||
File outCommonDir = new File("/sdcard/AppProjects")
|
||||
String commandAPKName = "app.apk"
|
||||
if(outCommonDir.exists()) {
|
||||
copy{
|
||||
from file.outputFile
|
||||
into outCommonDir
|
||||
rename {
|
||||
String fileName -> "${commandAPKName}"
|
||||
|
||||
// 如果正在调试发布版,就只生成和输出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
|
||||
}
|
||||
println "Output APK (Common): " + outCommonDir.getAbsolutePath() + "/${commandAPKName}"
|
||||
// 复制一个备份
|
||||
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['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 {)
|
||||
|
||||
20
README.md
20
README.md
@@ -119,7 +119,7 @@
|
||||
## ★. 项目 Android SDK 编译环境设置(可选),local.properties-demo 要复制为 local.properties,并按需要设置 Android SDK 目录。
|
||||
## ★. 应用签名密钥 keystore 设置问题。一般调试编译只需用【Termux】cd 进 GenKeyStore 目录执行 $ bash gen_debug_keystore.sh 命令即可完成设置。
|
||||
## ☆. 应用 WiBoLL 签名密钥配置问题<非必须考虑>。设置时需要 clone 【keystore】模块源码并拷贝模块目录的 appkey.jks 与 appkey.keystore 到项目根目录即可。
|
||||
## ☆. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息。
|
||||
## ☆. 类库型模块编译环境设置(可选),winboll.properties-demo 要复制为 winboll.properties,并按需要设置 WinBoLL Maven 库登录用户信息, 和 APK 文件额外输出路径。
|
||||
|
||||
|
||||
# ☆类库型项目编译方法
|
||||
@@ -128,7 +128,8 @@
|
||||
设置属性 libraryProject=<类库项目模块文件夹名称>
|
||||
### 再编译测试项目
|
||||
$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称>
|
||||
#### 测试项目编译后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
|
||||
#### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
|
||||
#### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
||||
### 最后编译类库项目
|
||||
$ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块文件夹名称>
|
||||
#### 类库模块编译命令执行后,编译器会发布到 WinBoLL Nexus Maven 库:Maven 库地址可以参阅根项目目录配置 build.gradle 文件。
|
||||
@@ -136,4 +137,17 @@ $ bash .winboll/bashPublishLIBAddTag.sh <类库项目模块文件夹名称>
|
||||
# ☆应用型项目编译方法
|
||||
## 直接调用以下命令编译应用型项目
|
||||
$ bash .winboll/bashPublishAPKAddTag.sh <应用项目模块文件夹名称>
|
||||
#### 应用模块编译命令执行后,编译器会复制一份 APK 到以下路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
|
||||
#### 应用模块编译命令执行后,编译器会复制一份 APK 到
|
||||
#### 测试项目编译后,编译器会复制一份 APK 到 路径:"/sdcard/WinBoLLStudio/APKs/<项目根目录名称>/tag/" 文件夹。
|
||||
#### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
||||
|
||||
## ☆应用调试编译方法
|
||||
使用以下命令编译调试:
|
||||
|
||||
### Beta调试使用:
|
||||
$ bash gradlew assembleBetaDebug
|
||||
|
||||
### Stage调试使用:
|
||||
$ bash gradlew assembleStageDebug
|
||||
|
||||
### 若是 winboll.properties 文件的 [ExtraAPKOutputPath] 属性设置了路径。编译器也会复制一份 APK 到这个路径。
|
||||
|
||||
@@ -29,7 +29,7 @@ android {
|
||||
// versionName 更新后需要手动设置
|
||||
// 项目模块目录的 build.gradle 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.9"
|
||||
versionName "15.10"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Aug 31 23:40:17 HKT 2025
|
||||
stageCount=4
|
||||
#Mon Sep 29 13:04:18 HKT 2025
|
||||
stageCount=3
|
||||
libraryProject=libaes
|
||||
baseVersion=15.9
|
||||
publishVersion=15.9.3
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.2
|
||||
buildCount=0
|
||||
baseBetaVersion=15.9.4
|
||||
baseBetaVersion=15.10.3
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
|
||||
<activity android:name=".AboutActivity"/>
|
||||
|
||||
<activity android:name=".TestActivityManagerActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -12,18 +12,18 @@ import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.winboll.APPInfo;
|
||||
import cc.winboll.studio.libaes.winboll.AboutView;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.models.APPInfo;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libaes.views.AboutView;
|
||||
|
||||
public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||
public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "AboutActivity";
|
||||
|
||||
Context mContext;
|
||||
Toolbar mToolbar;
|
||||
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
@@ -33,7 +33,7 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -64,13 +64,13 @@ public class AboutActivity extends AppCompatActivity implements IWinBoLLActivity
|
||||
);
|
||||
layout.addView(aboutView, params);
|
||||
|
||||
GlobalApplication.getWinBoLLActivityManager().add(this);
|
||||
WinBoLLActivityManager.getInstance().add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
|
||||
WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||
}
|
||||
|
||||
public AboutView CreateAboutView() {
|
||||
|
||||
@@ -6,6 +6,7 @@ package cc.winboll.studio.aes;
|
||||
* @Describe AES应用类
|
||||
*/
|
||||
import android.view.Gravity;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import com.hjq.toast.style.WhiteToastStyle;
|
||||
@@ -18,6 +19,7 @@ public class App extends GlobalApplication {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
WinBoLLActivityManager.init(this);
|
||||
|
||||
// 初始化 Toast 框架
|
||||
ToastUtils.init(this);
|
||||
|
||||
@@ -15,9 +15,10 @@ 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.beans.DrawerMenuBean;
|
||||
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;
|
||||
@@ -25,10 +26,10 @@ 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.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import com.a4455jkjh.colorpicker.ColorPickerDialog;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import java.util.ArrayList;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
|
||||
public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActivity {
|
||||
|
||||
@@ -122,9 +123,10 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActi
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int nItemId = item.getItemId();
|
||||
// if (item.getItemId() == R.id.item_log) {
|
||||
// WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(getApplicationContext(), LogActivity.class);
|
||||
// } else
|
||||
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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
package cc.winboll.studio.appbase.activities;
|
||||
package cc.winboll.studio.aes;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/05/10 09:48
|
||||
* @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 cc.winboll.studio.appbase.MainActivity;
|
||||
import cc.winboll.studio.appbase.R;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
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;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
|
||||
public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
|
||||
public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "WinBoLLActivity";
|
||||
|
||||
@@ -36,13 +35,13 @@ public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == R.id.item_log) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
|
||||
/*if (item.getItemId() == R.id.item_log) {
|
||||
WinBoLLActivityManager.getInstance().startLogActivity(this);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.item_home) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(getApplicationContext(), MainActivity.class);
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
}
|
||||
}*/
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
@@ -50,12 +49,12 @@ public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
GlobalApplication.getWinBoLLActivityManager().add(this);
|
||||
WinBoLLActivityManager.getInstance().add(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
GlobalApplication.getWinBoLLActivityManager().registeRemove(this);
|
||||
WinBoLLActivityManager.getInstance().registeRemove(this);
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:id="@+id/ivapp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="窗口管理类测试窗口"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<?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"/>
|
||||
|
||||
@@ -19,18 +19,21 @@ def genVersionName(def versionName){
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion 32
|
||||
buildToolsVersion "32.0.0"
|
||||
// 1. compileSdkVersion:必须 ≥ targetSdkVersion,建议直接等于 targetSdkVersion(30)
|
||||
compileSdkVersion 30
|
||||
|
||||
// 2. buildToolsVersion:需匹配 compileSdkVersion,建议使用 30.x.x 最新稳定版(无需高于 compileSdkVersion)
|
||||
buildToolsVersion "30.0.3" // 这是 30 对应的最新稳定版,避免使用 beta 版
|
||||
|
||||
defaultConfig {
|
||||
applicationId "cc.winboll.studio.appbase"
|
||||
minSdkVersion 24
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
// versionName 更新后需要手动设置
|
||||
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.9"
|
||||
versionName "15.10"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Aug 31 23:37:38 HKT 2025
|
||||
stageCount=6
|
||||
#Tue Nov 11 13:06:53 GMT 2025
|
||||
stageCount=10
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.9
|
||||
publishVersion=15.9.5
|
||||
buildCount=0
|
||||
baseBetaVersion=15.9.6
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.9
|
||||
buildCount=8
|
||||
baseBetaVersion=15.10.10
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" >
|
||||
|
||||
<application>
|
||||
<application
|
||||
tools:replace="android:icon,android:roundIcon"
|
||||
android:icon="@drawable/ic_winboll_beta"
|
||||
android:roundIcon="@drawable/ic_winboll_beta">
|
||||
|
||||
<!-- Put flavor specific code here -->
|
||||
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.appbase">
|
||||
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:icon="@drawable/ic_miapp"
|
||||
android:icon="@drawable/ic_winboll"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/MyAPPBaseTheme"
|
||||
android:resizeableActivity="true"
|
||||
android:process=":App"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
android:process=":App">
|
||||
|
||||
<activity
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:exported="true"
|
||||
@@ -25,103 +24,14 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.NewActivity"
|
||||
android:label="NewActivity"
|
||||
android:exported="true"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
||||
|
||||
<activity
|
||||
android:name=".activities.New2Activity"
|
||||
android:label="New2Activity"
|
||||
android:exported="true"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/>
|
||||
|
||||
<service
|
||||
android:name=".MyTileService"
|
||||
android:exported="true"
|
||||
android:label="@string/tileservice_name"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".services.MainService"
|
||||
android:exported="true"/>
|
||||
|
||||
<service
|
||||
android:name="cc.winboll.studio.appbase.services.TestDemoBindService"
|
||||
android:exported="true"/>
|
||||
|
||||
<service
|
||||
android:name="cc.winboll.studio.appbase.services.TestDemoService"
|
||||
android:exported="true"/>
|
||||
|
||||
<service android:name=".services.AssistantService"/>
|
||||
|
||||
<receiver
|
||||
android:name="cc.winboll.studio.appbase.receivers.MainReceiver"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.appbase.receivers.MainReceiver"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.APPNewsWidget"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_WAKEUP_SERVICE"/>
|
||||
|
||||
<action android:name="cc.winboll.studio.appbase.widgets.APPNewsWidget.ACTION_RELOAD_REPORT"/>
|
||||
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_provider_info_sos"/>
|
||||
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".receivers.APPNewsWidgetClickListener"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_PRE"/>
|
||||
|
||||
<action android:name="cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener.ACTION_NEXT"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
|
||||
<activity android:name=".GlobalApplication$CrashActivity"/>
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="4.0"/>
|
||||
|
||||
@@ -5,23 +5,15 @@ package cc.winboll.studio.appbase;
|
||||
* @Date 2025/01/05 09:54:42
|
||||
* @Describe APPbase 应用类
|
||||
*/
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import android.content.IntentFilter;
|
||||
import cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver;
|
||||
import cc.winboll.studio.libappbase.sos.SOS;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
|
||||
public static final String TAG = "App";
|
||||
|
||||
SOSCenterServiceReceiver mSOSCenterServiceReceiver;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mSOSCenterServiceReceiver = new SOSCenterServiceReceiver();
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(SOS.ACTION_SOS);
|
||||
registerReceiver(mSOSCenterServiceReceiver, intentFilter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,23 @@
|
||||
package cc.winboll.studio.appbase;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
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.CheckBox;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.appbase.R;
|
||||
import cc.winboll.studio.appbase.activities.NewActivity;
|
||||
import cc.winboll.studio.appbase.activities.WinBoLLActivity;
|
||||
import cc.winboll.studio.appbase.services.MainService;
|
||||
import cc.winboll.studio.appbase.services.TestDemoBindService;
|
||||
import cc.winboll.studio.appbase.services.TestDemoService;
|
||||
import cc.winboll.studio.libappbase.CrashHandler;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.GlobalCrashActivity;
|
||||
import cc.winboll.studio.libappbase.LogActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import cc.winboll.studio.libappbase.sos.SOS;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.widgets.StatusWidget;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
public static final String TAG = "MainActivity";
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
@@ -49,9 +28,6 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setActionBar(mToolbar);
|
||||
|
||||
CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1);
|
||||
cbIsDebugMode.setChecked(GlobalApplication.isDebuging());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -62,122 +38,44 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if(item.getItemId() == R.id.item_yun) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.YunActivity.class);
|
||||
} else if(item.getItemId() == R.id.item_logon) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, cc.winboll.studio.libappbase.activities.LogonActivity.class);
|
||||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.item_home : {
|
||||
openWebsiteInBrowser(this);
|
||||
}
|
||||
}
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
Intent intentAPPWidget = new Intent(this, StatusWidget.class);
|
||||
intentAPPWidget.setAction(StatusWidget.ACTION_STATUS_UPDATE);
|
||||
sendBroadcast(intentAPPWidget);
|
||||
public void onCrashTest(View view) {
|
||||
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
|
||||
getString(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSwitchDebugMode(View view) {
|
||||
boolean isDebuging = ((CheckBox)view).isChecked();
|
||||
GlobalApplication.setIsDebuging(isDebuging);
|
||||
GlobalApplication.saveDebugStatus();
|
||||
public void onLogTest(View view) {
|
||||
LogActivity.startLogActivity(this);
|
||||
}
|
||||
|
||||
public void onToastUtilsTest(View view) {
|
||||
LogUtils.d(TAG, "onToastUtilsTest");
|
||||
ToastUtils.init(getApplicationContext());
|
||||
ToastUtils.show("Hello, WinBoLL!");
|
||||
ToastUtils.release();
|
||||
}
|
||||
|
||||
public void onPreviewGlobalCrashActivity(View view) {
|
||||
Intent intent = new Intent(this, GlobalCrashActivity.class);
|
||||
intent.putExtra(CrashHandler.EXTRA_CRASH_INFO, "Demo log...");
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void onStartCenter(View view) {
|
||||
MainService.startMainService(this);
|
||||
}
|
||||
|
||||
public void onStopCenter(View view) {
|
||||
MainService.stopMainService(this);
|
||||
}
|
||||
|
||||
public void onTestStopMainServiceWithoutSettingEnable(View view) {
|
||||
LogUtils.d(TAG, "onTestStopMainServiceWithoutSettingEnable");
|
||||
stopService(new Intent(this, MainService.class));
|
||||
}
|
||||
|
||||
public void onTestUseComponentStartService(View view) {
|
||||
LogUtils.d(TAG, "onTestUseComponentStartService");
|
||||
|
||||
// 目标服务的包名和类名
|
||||
String packageName = this.getPackageName();
|
||||
String serviceClassName = TestDemoService.class.getName();
|
||||
|
||||
// 构建Intent
|
||||
Intent intentService = new Intent();
|
||||
intentService.setComponent(new ComponentName(packageName, serviceClassName));
|
||||
|
||||
startService(intentService);
|
||||
}
|
||||
|
||||
public void onTestDemoServiceSOS(View view) {
|
||||
Intent intent = new Intent(this, TestDemoService.class);
|
||||
stopService(intent);
|
||||
if (App.isDebuging()) {
|
||||
SOS.sosToAppBaseBeta(this, TestDemoService.class.getName());
|
||||
} else {
|
||||
SOS.sosToAppBase(this, TestDemoService.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void onSartTestDemoService(View view) {
|
||||
Intent intent = new Intent(this, TestDemoService.class);
|
||||
intent.setAction(TestDemoService.ACTION_ENABLE);
|
||||
startService(intent);
|
||||
|
||||
}
|
||||
|
||||
public void onStopTestDemoService(View view) {
|
||||
Intent intent = new Intent(this, TestDemoService.class);
|
||||
intent.setAction(TestDemoService.ACTION_DISABLE);
|
||||
startService(intent);
|
||||
|
||||
Intent intentStop = new Intent(this, TestDemoService.class);
|
||||
stopService(intentStop);
|
||||
}
|
||||
|
||||
public void onStopTestDemoServiceNoSettings(View view) {
|
||||
Intent intent = new Intent(this, TestDemoService.class);
|
||||
stopService(intent);
|
||||
}
|
||||
|
||||
public void onSartTestDemoBindService(View view) {
|
||||
Intent intent = new Intent(this, TestDemoBindService.class);
|
||||
intent.setAction(TestDemoBindService.ACTION_ENABLE);
|
||||
startService(intent);
|
||||
|
||||
}
|
||||
|
||||
public void onStopTestDemoBindService(View view) {
|
||||
Intent intent = new Intent(this, TestDemoBindService.class);
|
||||
intent.setAction(TestDemoBindService.ACTION_DISABLE);
|
||||
startService(intent);
|
||||
|
||||
Intent intentStop = new Intent(this, TestDemoBindService.class);
|
||||
stopService(intentStop);
|
||||
}
|
||||
|
||||
public void onStopTestDemoBindServiceNoSettings(View view) {
|
||||
Intent intent = new Intent(this, TestDemoBindService.class);
|
||||
stopService(intent);
|
||||
}
|
||||
|
||||
public void onTestOpenNewActivity(View view) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 唤起默认浏览器打开指定网站
|
||||
* @param context 上下文(如 Activity.this)
|
||||
*/
|
||||
public void openWebsiteInBrowser(Context context) {
|
||||
// 目标网站地址
|
||||
String url = "https://www.winboll.cc";
|
||||
// 构建打开浏览器的意图
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
||||
// 设置标志:避免创建新的任务栈(可选,按需求调整)
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package cc.winboll.studio.appbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/13 19:30:10
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
import cc.winboll.studio.appbase.models.MainServiceBean;
|
||||
import cc.winboll.studio.appbase.services.MainService;
|
||||
|
||||
public class MyTileService extends TileService {
|
||||
public static final String TAG = "MyTileService";
|
||||
|
||||
volatile static MyTileService _MyTileService;
|
||||
|
||||
@Override
|
||||
public void onStartListening() {
|
||||
super.onStartListening();
|
||||
_MyTileService = this;
|
||||
Tile tile = getQsTile();
|
||||
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
if (bean != null && bean.isEnable()) {
|
||||
//MainService.startMainService(context);
|
||||
tile.setState(Tile.STATE_ACTIVE);
|
||||
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud));
|
||||
} else {
|
||||
//MainService.stopMainService(context);
|
||||
tile.setState(Tile.STATE_INACTIVE);
|
||||
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
|
||||
}
|
||||
tile.updateTile();
|
||||
// Tile tile = getQsTile();
|
||||
// tile.setState(Tile.STATE_INACTIVE);
|
||||
// tile.setLabel(getString(R.string.tileservice_name));
|
||||
// tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
|
||||
// tile.updateTile();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick() {
|
||||
super.onClick();
|
||||
Tile tile = getQsTile();
|
||||
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
if (bean == null) {
|
||||
bean = new MainServiceBean();
|
||||
}
|
||||
|
||||
if (tile.getState() == Tile.STATE_ACTIVE) {
|
||||
bean.setIsEnable(false);
|
||||
MainServiceBean.saveBean(this, bean);
|
||||
MainService.stopMainService(this);
|
||||
} else if (tile.getState() == Tile.STATE_INACTIVE) {
|
||||
bean.setIsEnable(true);
|
||||
MainServiceBean.saveBean(this, bean);
|
||||
MainService.startMainService(this);
|
||||
}
|
||||
updateServiceIconStatus(this);
|
||||
}
|
||||
|
||||
public static void updateServiceIconStatus(Context context) {
|
||||
if (_MyTileService == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Tile tile = _MyTileService.getQsTile();
|
||||
MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class);
|
||||
if (bean != null && bean.isEnable()) {
|
||||
tile.setState(Tile.STATE_ACTIVE);
|
||||
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud));
|
||||
} else {
|
||||
tile.setState(Tile.STATE_INACTIVE);
|
||||
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud_outline));
|
||||
}
|
||||
tile.updateTile();
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package cc.winboll.studio.appbase.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/25 11:46:40
|
||||
* @Describe 测试窗口2
|
||||
*/
|
||||
import android.app.Activity;
|
||||
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.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
|
||||
public class New2Activity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "New2Activity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
//LogView mLogView;
|
||||
|
||||
@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_new2);
|
||||
|
||||
// mLogView = findViewById(R.id.logview);
|
||||
// mLogView.start();
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//mLogView.start();
|
||||
}
|
||||
|
||||
public void onCloseThisActivity(View view) {
|
||||
GlobalApplication.getWinBoLLActivityManager().finish(this);
|
||||
}
|
||||
|
||||
public void onCloseAllActivity(View view) {
|
||||
GlobalApplication.getWinBoLLActivityManager().finishAll();
|
||||
}
|
||||
|
||||
public void onNewActivity(View view) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, NewActivity.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
package cc.winboll.studio.appbase.activities;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/25 05:04:22
|
||||
*/
|
||||
import android.app.Activity;
|
||||
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.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
|
||||
public class NewActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "NewActivity";
|
||||
|
||||
Toolbar mToolbar;
|
||||
//LogView mLogView;
|
||||
|
||||
@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_new);
|
||||
// mLogView = findViewById(R.id.logview);
|
||||
// mLogView.start();
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
setActionBar(mToolbar);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//mLogView.start();
|
||||
}
|
||||
|
||||
public void onCloseThisActivity(View view) {
|
||||
GlobalApplication.getWinBoLLActivityManager().finish(this);
|
||||
}
|
||||
|
||||
public void onCloseAllActivity(View view) {
|
||||
GlobalApplication.getWinBoLLActivityManager().finishAll();
|
||||
}
|
||||
|
||||
public void onNew2Activity(View view) {
|
||||
GlobalApplication.getWinBoLLActivityManager().startWinBoLLActivity(this, New2Activity.class);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package cc.winboll.studio.appbase.handlers;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/14 03:51:40
|
||||
*/
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import cc.winboll.studio.appbase.services.MainService;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MainServiceHandler extends Handler {
|
||||
public static final String TAG = "MainServiceHandler";
|
||||
|
||||
public static final int MSG_REMINDTHREAD = 0;
|
||||
|
||||
WeakReference<MainService> serviceWeakReference;
|
||||
public MainServiceHandler(MainService service) {
|
||||
serviceWeakReference = new WeakReference<MainService>(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_REMINDTHREAD: // 处理下载完成消息,更新UI
|
||||
{
|
||||
// 显示提醒消息
|
||||
//
|
||||
//LogUtils.d(TAG, "显示提醒消息");
|
||||
MainService mainService = serviceWeakReference.get();
|
||||
if (mainService != null) {
|
||||
mainService.appenMessage((String)msg.obj);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package cc.winboll.studio.appbase.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/13 07:06:13
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MainServiceBean extends BaseBean {
|
||||
|
||||
public static final String TAG = "MainServiceBean";
|
||||
|
||||
boolean isEnable;
|
||||
|
||||
public MainServiceBean() {
|
||||
this.isEnable = false;
|
||||
}
|
||||
|
||||
public void setIsEnable(boolean isEnable) {
|
||||
this.isEnable = isEnable;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
return isEnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return MainServiceBean.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isEnable").value(isEnable());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("isEnable")) {
|
||||
setIsEnable(jsonReader.nextBoolean());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package cc.winboll.studio.appbase.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/07 12:47:22
|
||||
* @Describe TestServiceBean
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TestDemoBindServiceBean extends BaseBean {
|
||||
|
||||
public static final String TAG = "TestServiceBean";
|
||||
|
||||
boolean isEnable;
|
||||
|
||||
public TestDemoBindServiceBean() {
|
||||
this.isEnable = false;
|
||||
}
|
||||
|
||||
public void setIsEnable(boolean isEnable) {
|
||||
this.isEnable = isEnable;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
return isEnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TestDemoBindServiceBean.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isEnable").value(isEnable());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("isEnable")) {
|
||||
setIsEnable(jsonReader.nextBoolean());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package cc.winboll.studio.appbase.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/07 12:49:21
|
||||
* @Describe TestDemoServiceBean
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TestDemoServiceBean extends BaseBean {
|
||||
|
||||
public static final String TAG = "TestDemoServiceBean";
|
||||
|
||||
boolean isEnable;
|
||||
|
||||
public TestDemoServiceBean() {
|
||||
this.isEnable = false;
|
||||
}
|
||||
|
||||
public void setIsEnable(boolean isEnable) {
|
||||
this.isEnable = isEnable;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
return isEnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TestDemoServiceBean.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isEnable").value(isEnable());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("isEnable")) {
|
||||
setIsEnable(jsonReader.nextBoolean());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package cc.winboll.studio.appbase.receivers;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/24 07:11:44
|
||||
*/
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import cc.winboll.studio.appbase.widgets.APPNewsWidget;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class APPNewsWidgetClickListener extends BroadcastReceiver {
|
||||
|
||||
public static final String TAG = "APPNewsWidgetClickListener";
|
||||
public static final String ACTION_PRE = APPNewsWidgetClickListener.class.getName() + ".ACTION_PRE";
|
||||
public static final String ACTION_NEXT = APPNewsWidgetClickListener.class.getName() + ".ACTION_NEXT";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action == null) {
|
||||
LogUtils.d(TAG, String.format("action %s", action));
|
||||
return;
|
||||
}
|
||||
if (action.equals(ACTION_PRE)) {
|
||||
LogUtils.d(TAG, "ACTION_PRE");
|
||||
APPNewsWidget.prePage(context);
|
||||
} else if (action.equals(ACTION_NEXT)) {
|
||||
LogUtils.d(TAG, "ACTION_NEXT");
|
||||
APPNewsWidget.nextPage(context);
|
||||
} else {
|
||||
LogUtils.d(TAG, String.format("action %s", action));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package cc.winboll.studio.appbase.receivers;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/13 06:58:04
|
||||
* @Describe 主要广播接收器
|
||||
*/
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import cc.winboll.studio.appbase.services.MainService;
|
||||
import cc.winboll.studio.appbase.widgets.APPNewsWidget;
|
||||
import cc.winboll.studio.libappbase.AppUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.models.APPModel;
|
||||
import cc.winboll.studio.libappbase.models.WinBoLLModel;
|
||||
import cc.winboll.studio.libappbase.models.WinBoLLNewsBean;
|
||||
import cc.winboll.studio.libappbase.sos.SOS;
|
||||
import cc.winboll.studio.libappbase.sos.SOSObject;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.winboll.WinBoLL;
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class MainReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String TAG = "MainReceiver";
|
||||
|
||||
public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
|
||||
|
||||
WeakReference<MainService> mwrService;
|
||||
|
||||
public MainReceiver(MainService service) {
|
||||
mwrService = new WeakReference<MainService>(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String szAction = intent.getAction();
|
||||
if (szAction.equals(ACTION_BOOT_COMPLETED)) {
|
||||
ToastUtils.show("ACTION_BOOT_COMPLETED");
|
||||
} else if (szAction.equals(IWinBoLLActivity.ACTION_BIND)) {
|
||||
LogUtils.d(TAG, "ACTION_BIND");
|
||||
LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName()));
|
||||
LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction()));
|
||||
String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
|
||||
LogUtils.d(TAG, String.format("szAPPModel %s", szWinBoLLModel));
|
||||
if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
|
||||
try {
|
||||
WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
|
||||
if (bean != null) {
|
||||
String szAppPackageName = bean.getAppPackageName();
|
||||
LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
|
||||
String szAppMainServiveName = bean.getAppMainServiveName();
|
||||
LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
|
||||
mwrService.get().bindWinBoLLModelConnection(bean);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
} else if (intent.getAction().equals(SOS.ACTION_SOS)) {
|
||||
LogUtils.d(TAG, "ACTION_SOS");
|
||||
String sos = intent.getStringExtra(SOS.EXTRA_OBJECT);
|
||||
LogUtils.d(TAG, String.format("SOS %s", sos));
|
||||
if (sos != null && !sos.equals("")) {
|
||||
SOSObject bean = SOS.parseSOSObject(sos);
|
||||
if (bean != null) {
|
||||
String szObjectPackageName = bean.getObjectPackageName();
|
||||
LogUtils.d(TAG, String.format("szObjectPackageName %s", szObjectPackageName));
|
||||
String szObjectServiveName = bean.getObjectServiveName();
|
||||
LogUtils.d(TAG, String.format("szObjectServiveName %s", szObjectServiveName));
|
||||
|
||||
Intent intentService = new Intent();
|
||||
intentService.setComponent(new ComponentName(szObjectPackageName, szObjectServiveName));
|
||||
context.startService(intentService);
|
||||
|
||||
String appName = AppUtils.getAppNameByPackageName(context, szObjectPackageName);
|
||||
LogUtils.d(TAG, String.format("appName %s", appName));
|
||||
WinBoLLNewsBean appWinBoLLNewsBean = new WinBoLLNewsBean(appName);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
|
||||
String currentTime = sdf.format(new Date());
|
||||
StringBuilder sbLine = new StringBuilder();
|
||||
sbLine.append("[");
|
||||
sbLine.append(currentTime);
|
||||
sbLine.append("] Power to ");
|
||||
sbLine.append(appName);
|
||||
appWinBoLLNewsBean.setMessage(sbLine.toString());
|
||||
|
||||
APPNewsWidget.addWinBoLLNewsBean(context, appWinBoLLNewsBean);
|
||||
|
||||
Intent intentWidget = new Intent(context, APPNewsWidget.class);
|
||||
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||
context.sendBroadcast(intentWidget);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
ToastUtils.show(szAction);
|
||||
}
|
||||
}
|
||||
|
||||
// 注册 Receiver
|
||||
//
|
||||
public void registerAction(MainService service) {
|
||||
IntentFilter filter=new IntentFilter();
|
||||
filter.addAction(ACTION_BOOT_COMPLETED);
|
||||
filter.addAction(SOS.ACTION_SOS);
|
||||
filter.addAction(WinBoLL.ACTION_BIND);
|
||||
//filter.addAction(Intent.ACTION_BATTERY_CHANGED);
|
||||
service.registerReceiver(this, filter);
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package cc.winboll.studio.appbase.services;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/14 03:38:31
|
||||
* @Describe 守护进程服务
|
||||
*/
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.appbase.models.MainServiceBean;
|
||||
import cc.winboll.studio.appbase.services.AssistantService;
|
||||
import cc.winboll.studio.appbase.services.MainService;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import android.os.Binder;
|
||||
|
||||
public class AssistantService extends Service {
|
||||
|
||||
public static final String TAG = "AssistantService";
|
||||
|
||||
MainServiceBean mMainServiceBean;
|
||||
MyServiceConnection mMyServiceConnection;
|
||||
MainService mMainService;
|
||||
boolean isBound = false;
|
||||
volatile boolean isThreadAlive = false;
|
||||
|
||||
public synchronized void setIsThreadAlive(boolean isThreadAlive) {
|
||||
LogUtils.d(TAG, "setIsThreadAlive(...)");
|
||||
LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive));
|
||||
this.isThreadAlive = isThreadAlive;
|
||||
}
|
||||
|
||||
public boolean isThreadAlive() {
|
||||
return isThreadAlive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new MyBinder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
LogUtils.d(TAG, "onCreate");
|
||||
super.onCreate();
|
||||
|
||||
//mMyBinder = new MyBinder();
|
||||
if (mMyServiceConnection == null) {
|
||||
mMyServiceConnection = new MyServiceConnection();
|
||||
}
|
||||
// 设置运行参数
|
||||
setIsThreadAlive(false);
|
||||
assistantService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "call onStartCommand(...)");
|
||||
assistantService();
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
//LogUtils.d(TAG, "onDestroy");
|
||||
setIsThreadAlive(false);
|
||||
// 解除绑定
|
||||
if (isBound) {
|
||||
unbindService(mMyServiceConnection);
|
||||
isBound = false;
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
// 运行服务内容
|
||||
//
|
||||
void assistantService() {
|
||||
LogUtils.d(TAG, "assistantService()");
|
||||
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable()));
|
||||
if (mMainServiceBean.isEnable()) {
|
||||
LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive()));
|
||||
if (isThreadAlive() == false) {
|
||||
// 设置运行状态
|
||||
setIsThreadAlive(true);
|
||||
// 唤醒和绑定主进程
|
||||
wakeupAndBindMain();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 唤醒和绑定主进程
|
||||
//
|
||||
void wakeupAndBindMain() {
|
||||
LogUtils.d(TAG, "wakeupAndBindMain()");
|
||||
// 绑定服务的Intent
|
||||
Intent intent = new Intent(this, MainService.class);
|
||||
startService(new Intent(this, MainService.class));
|
||||
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
|
||||
|
||||
// startService(new Intent(this, MainService.class));
|
||||
// bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
|
||||
}
|
||||
|
||||
// 主进程与守护进程连接时需要用到此类
|
||||
//
|
||||
class MyServiceConnection implements ServiceConnection {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
LogUtils.d(TAG, "onServiceConnected(...)");
|
||||
MainService.MyBinder binder = (MainService.MyBinder) service;
|
||||
mMainService = binder.getService();
|
||||
isBound = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
LogUtils.d(TAG, "onServiceDisconnected(...)");
|
||||
mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class);
|
||||
if (mMainServiceBean.isEnable()) {
|
||||
wakeupAndBindMain();
|
||||
}
|
||||
isBound = false;
|
||||
mMainService = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 用于返回服务实例的Binder
|
||||
public class MyBinder extends Binder {
|
||||
AssistantService getService() {
|
||||
LogUtils.d(TAG, "AssistantService MyBinder getService()");
|
||||
return AssistantService.this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
package cc.winboll.studio.appbase.services;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/13 06:56:41
|
||||
* @Describe 拨号主服务
|
||||
* 参考:
|
||||
* 进程保活-双进程守护的正确姿势
|
||||
* https://blog.csdn.net/sinat_35159441/article/details/75267380
|
||||
* Android Service之onStartCommand方法研究
|
||||
* https://blog.csdn.net/cyp331203/article/details/38920491
|
||||
*/
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.appbase.MyTileService;
|
||||
import cc.winboll.studio.appbase.handlers.MainServiceHandler;
|
||||
import cc.winboll.studio.appbase.models.MainServiceBean;
|
||||
import cc.winboll.studio.appbase.receivers.MainReceiver;
|
||||
import cc.winboll.studio.appbase.services.AssistantService;
|
||||
import cc.winboll.studio.appbase.threads.MainServiceThread;
|
||||
import cc.winboll.studio.appbase.widgets.APPNewsWidget;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.models.APPModel;
|
||||
import cc.winboll.studio.libappbase.models.WinBoLLModel;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class MainService extends Service {
|
||||
|
||||
public static final String TAG = "MainService";
|
||||
|
||||
public static final int MSG_UPDATE_STATUS = 0;
|
||||
|
||||
static MainService _mControlCenterService;
|
||||
|
||||
volatile boolean isServiceRunning;
|
||||
|
||||
MainServiceBean mMainServiceBean;
|
||||
MainServiceThread mMainServiceThread;
|
||||
MainServiceHandler mMainServiceHandler;
|
||||
MyServiceConnection mMyServiceConnection;
|
||||
AssistantService mAssistantService;
|
||||
boolean isBound = false;
|
||||
MainReceiver mMainReceiver;
|
||||
ArrayList<APPConnection> mAPPModelConnectionList;
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new MyBinder();
|
||||
}
|
||||
|
||||
public MainServiceThread getRemindThread() {
|
||||
return mMainServiceThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate()");
|
||||
mAPPModelConnectionList = new ArrayList<APPConnection>();
|
||||
|
||||
_mControlCenterService = MainService.this;
|
||||
isServiceRunning = false;
|
||||
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
|
||||
if (mMyServiceConnection == null) {
|
||||
mMyServiceConnection = new MyServiceConnection();
|
||||
}
|
||||
mMainServiceHandler = new MainServiceHandler(this);
|
||||
|
||||
// 运行服务内容
|
||||
mainService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "onStartCommand(...)");
|
||||
// 运行服务内容
|
||||
mainService();
|
||||
return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
// 运行服务内容
|
||||
//
|
||||
void mainService() {
|
||||
LogUtils.d(TAG, "mainService()");
|
||||
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
if (mMainServiceBean.isEnable() && isServiceRunning == false) {
|
||||
LogUtils.d(TAG, "mainService() start running");
|
||||
isServiceRunning = true;
|
||||
// 唤醒守护进程
|
||||
wakeupAndBindAssistant();
|
||||
|
||||
if (mMainReceiver == null) {
|
||||
// 注册广播接收器
|
||||
mMainReceiver = new MainReceiver(this);
|
||||
mMainReceiver.registerAction(this);
|
||||
}
|
||||
|
||||
// 启动小部件
|
||||
Intent intentTimeWidget = new Intent(this, APPNewsWidget.class);
|
||||
intentTimeWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||
this.sendBroadcast(intentTimeWidget);
|
||||
|
||||
startMainServiceThread();
|
||||
|
||||
MyTileService.updateServiceIconStatus(this);
|
||||
|
||||
LogUtils.i(TAG, "Main Service Is Start.");
|
||||
}
|
||||
}
|
||||
|
||||
// 唤醒和绑定守护进程
|
||||
//
|
||||
void wakeupAndBindAssistant() {
|
||||
LogUtils.d(TAG, "wakeupAndBindAssistant()");
|
||||
|
||||
Intent intent = new Intent(this, AssistantService.class);
|
||||
startService(intent);
|
||||
// 绑定服务的Intent
|
||||
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
|
||||
}
|
||||
|
||||
// 开启提醒铃声线程
|
||||
//
|
||||
public void startMainServiceThread() {
|
||||
LogUtils.d(TAG, "startMainServiceThread");
|
||||
if (mMainServiceThread == null) {
|
||||
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
|
||||
LogUtils.d(TAG, "new MainServiceThread");
|
||||
} else {
|
||||
if (mMainServiceThread.isExist() == true) {
|
||||
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
|
||||
LogUtils.d(TAG, "renew MainServiceThread");
|
||||
} else {
|
||||
// 提醒进程正在进行中就更新状态后退出
|
||||
LogUtils.d(TAG, "A mMainServiceThread running.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
mMainServiceThread.start();
|
||||
}
|
||||
|
||||
public void stopRemindThread() {
|
||||
if (mMainServiceThread != null) {
|
||||
mMainServiceThread.setIsExist(true);
|
||||
mMainServiceThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
//LogUtils.d(TAG, "onDestroy");
|
||||
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
if (mMainServiceBean.isEnable() == false) {
|
||||
// 设置运行状态
|
||||
isServiceRunning = false;// 解除绑定
|
||||
if (isBound) {
|
||||
unbindService(mMyServiceConnection);
|
||||
isBound = false;
|
||||
}
|
||||
// 停止守护进程
|
||||
Intent intent = new Intent(this, AssistantService.class);
|
||||
stopService(intent);
|
||||
// 停止Receiver
|
||||
if (mMainReceiver != null) {
|
||||
unregisterReceiver(mMainReceiver);
|
||||
mMainReceiver = null;
|
||||
}
|
||||
// 停止前台通知栏
|
||||
stopForeground(true);
|
||||
// 停止消息提醒进程
|
||||
stopRemindThread();
|
||||
|
||||
MyTileService.updateServiceIconStatus(this);
|
||||
|
||||
super.onDestroy();
|
||||
//LogUtils.d(TAG, "onDestroy done");
|
||||
}
|
||||
}
|
||||
|
||||
public void bindWinBoLLModelConnection(WinBoLLModel bean) {
|
||||
LogUtils.d(TAG, "bindAPPModelConnection(...)");
|
||||
// 清理旧的绑定链接
|
||||
for (int i = mAPPModelConnectionList.size() - 1; i > -1; i--) {
|
||||
APPConnection item = mAPPModelConnectionList.get(i);
|
||||
if (item.isBindToAPP(bean)) {
|
||||
LogUtils.d(TAG, "Bind Servive exist.");
|
||||
unbindService(item);
|
||||
mAPPModelConnectionList.remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定服务
|
||||
APPConnection appConnection = new APPConnection();
|
||||
Intent intentService = new Intent();
|
||||
intentService.setComponent(new ComponentName(bean.getAppPackageName(), bean.getAppMainServiveName()));
|
||||
bindService(intentService, appConnection, Context.BIND_IMPORTANT);
|
||||
mAPPModelConnectionList.add(appConnection);
|
||||
|
||||
Intent intentWidget = new Intent(this, APPNewsWidget.class);
|
||||
intentWidget.setAction(APPNewsWidget.ACTION_WAKEUP_SERVICE);
|
||||
WinBoLLModel appSOSBean = new WinBoLLModel(bean.getAppPackageName(), bean.getAppMainServiveName());
|
||||
intentWidget.putExtra("APPSOSBean", appSOSBean.toString());
|
||||
sendBroadcast(intentWidget);
|
||||
}
|
||||
|
||||
public class APPConnection implements ServiceConnection {
|
||||
|
||||
ComponentName mComponentName;
|
||||
|
||||
boolean isBindToAPP(WinBoLLModel bean) {
|
||||
return mComponentName != null
|
||||
&& mComponentName.getClassName().equals(bean.getAppMainServiveName())
|
||||
&& mComponentName.getPackageName().equals(bean.getAppPackageName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
LogUtils.d(TAG, "onServiceConnected(...)");
|
||||
mComponentName = name;
|
||||
LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
LogUtils.d(TAG, "onServiceDisconnected(...)");
|
||||
LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
|
||||
|
||||
// 尝试无参数启动一下服务
|
||||
String appPackage = mComponentName.getPackageName();
|
||||
LogUtils.d(TAG, String.format("appPackage %s", appPackage));
|
||||
String appMainServiceClassName = mComponentName.getClassName();
|
||||
LogUtils.d(TAG, String.format("appMainServiceClassName %s", appMainServiceClassName));
|
||||
|
||||
Intent intentService = new Intent();
|
||||
intentService.setComponent(new ComponentName(appPackage, appMainServiceClassName));
|
||||
startService(intentService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 主进程与守护进程连接时需要用到此类
|
||||
//
|
||||
private class MyServiceConnection implements ServiceConnection {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
LogUtils.d(TAG, "onServiceConnected(...)");
|
||||
AssistantService.MyBinder binder = (AssistantService.MyBinder) service;
|
||||
mAssistantService = binder.getService();
|
||||
isBound = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
LogUtils.d(TAG, "onServiceDisconnected(...)");
|
||||
|
||||
if (mMainServiceBean.isEnable()) {
|
||||
// 唤醒守护进程
|
||||
wakeupAndBindAssistant();
|
||||
}
|
||||
isBound = false;
|
||||
mAssistantService = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// 用于返回服务实例的Binder
|
||||
public class MyBinder extends Binder {
|
||||
MainService getService() {
|
||||
LogUtils.d(TAG, "MainService MyBinder getService()");
|
||||
return MainService.this;
|
||||
}
|
||||
}
|
||||
|
||||
// //
|
||||
// // 启动服务
|
||||
// //
|
||||
// public static void startControlCenterService(Context context) {
|
||||
// Intent intent = new Intent(context, MainService.class);
|
||||
// context.startForegroundService(intent);
|
||||
// }
|
||||
//
|
||||
// //
|
||||
// // 停止服务
|
||||
// //
|
||||
// public static void stopControlCenterService(Context context) {
|
||||
// Intent intent = new Intent(context, MainService.class);
|
||||
// context.stopService(intent);
|
||||
// }
|
||||
|
||||
public void appenMessage(String message) {
|
||||
LogUtils.d(TAG, String.format("Message : %s", message));
|
||||
}
|
||||
|
||||
public static void stopMainService(Context context) {
|
||||
LogUtils.d(TAG, "stopMainService");
|
||||
MainServiceBean bean = new MainServiceBean();
|
||||
bean.setIsEnable(false);
|
||||
MainServiceBean.saveBean(context, bean);
|
||||
context.stopService(new Intent(context, MainService.class));
|
||||
}
|
||||
|
||||
public static void startMainService(Context context) {
|
||||
LogUtils.d(TAG, "startMainService");
|
||||
MainServiceBean bean = new MainServiceBean();
|
||||
bean.setIsEnable(true);
|
||||
MainServiceBean.saveBean(context, bean);
|
||||
context.startService(new Intent(context, MainService.class));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
package cc.winboll.studio.appbase.services;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/07 12:45:49
|
||||
* @Describe 启动时申请绑定到APPBase主服务的服务示例
|
||||
*/
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.appbase.App;
|
||||
import cc.winboll.studio.appbase.models.TestDemoBindServiceBean;
|
||||
import cc.winboll.studio.appbase.services.TestDemoBindService;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.sos.SOS;
|
||||
import cc.winboll.studio.libappbase.winboll.WinBoLL;
|
||||
|
||||
public class TestDemoBindService extends Service {
|
||||
|
||||
public static final String TAG = "TestDemoBindService";
|
||||
|
||||
public static final String ACTION_ENABLE = TestDemoBindService.class.getName() + ".ACTION_ENABLE";
|
||||
public static final String ACTION_DISABLE = TestDemoBindService.class.getName() + ".ACTION_DISABLE";
|
||||
|
||||
volatile static TestThread _TestThread;
|
||||
|
||||
volatile static boolean _IsRunning;
|
||||
|
||||
public synchronized static void setIsRunning(boolean isRunning) {
|
||||
_IsRunning = isRunning;
|
||||
}
|
||||
|
||||
public static boolean isRunning() {
|
||||
return _IsRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new MyBinder();
|
||||
}
|
||||
|
||||
public class MyBinder extends Binder {
|
||||
public TestDemoBindService getService() {
|
||||
return TestDemoBindService.this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate()");
|
||||
|
||||
run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "onStartCommand(...)");
|
||||
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
|
||||
if (bean == null) {
|
||||
bean = new TestDemoBindServiceBean();
|
||||
}
|
||||
|
||||
if (intent.getAction() != null) {
|
||||
if (intent.getAction().equals(ACTION_ENABLE)) {
|
||||
bean.setIsEnable(true);
|
||||
LogUtils.d(TAG, "setIsEnable(true);");
|
||||
TestDemoBindServiceBean.saveBean(this, bean);
|
||||
} else if (intent.getAction().equals(ACTION_DISABLE)) {
|
||||
bean.setIsEnable(false);
|
||||
LogUtils.d(TAG, "setIsEnable(false);");
|
||||
TestDemoBindServiceBean.saveBean(this, bean);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||
//return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
void run() {
|
||||
LogUtils.d(TAG, "run()");
|
||||
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
|
||||
if (bean == null) {
|
||||
bean = new TestDemoBindServiceBean();
|
||||
TestDemoBindServiceBean.saveBean(this, bean);
|
||||
}
|
||||
if (bean.isEnable()) {
|
||||
LogUtils.d(TAG, "run() bean.isEnable()");
|
||||
TestThread.getInstance(this).start();
|
||||
LogUtils.d(TAG, "_TestThread.start()");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy()");
|
||||
TestDemoBindServiceBean bean = TestDemoBindServiceBean.loadBean(this, TestDemoBindServiceBean.class);
|
||||
if (bean == null) {
|
||||
bean = new TestDemoBindServiceBean();
|
||||
}
|
||||
|
||||
TestThread.getInstance(this).setIsExit(true);
|
||||
|
||||
// 预防 APPBase 应用重启绑定失效。
|
||||
// 所以退出时检查本服务是否配置启用,如果启用就发送一个 SOS 信号。
|
||||
// 这样 APPBase 就会用组件方式启动本服务。
|
||||
if (bean.isEnable()) {
|
||||
if (App.isDebuging()) {
|
||||
SOS.sosToAppBaseBeta(this, TestDemoBindService.class.getName());
|
||||
} else {
|
||||
SOS.sosToAppBase(this, TestDemoBindService.class.getName());
|
||||
}
|
||||
}
|
||||
|
||||
_IsRunning = false;
|
||||
}
|
||||
|
||||
static class TestThread extends Thread {
|
||||
|
||||
volatile static TestThread _TestThread;
|
||||
Context mContext;
|
||||
volatile boolean isStarted = false;
|
||||
volatile boolean isExit = false;
|
||||
|
||||
TestThread(Context context) {
|
||||
super();
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static synchronized TestThread getInstance(Context context) {
|
||||
if (_TestThread != null) {
|
||||
_TestThread.setIsExit(true);
|
||||
}
|
||||
_TestThread = new TestThread(context);
|
||||
|
||||
return _TestThread;
|
||||
}
|
||||
|
||||
public synchronized void setIsExit(boolean isExit) {
|
||||
this.isExit = isExit;
|
||||
}
|
||||
|
||||
public boolean isExit() {
|
||||
return isExit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isStarted == false) {
|
||||
isStarted = true;
|
||||
super.run();
|
||||
LogUtils.d(TAG, "run() start");
|
||||
if (App.isDebuging()) {
|
||||
WinBoLL.bindToAPPBaseBeta(mContext, TestDemoBindService.class.getName());
|
||||
} else {
|
||||
WinBoLL.bindToAPPBase(mContext, TestDemoBindService.class.getName());
|
||||
}
|
||||
|
||||
while (!isExit()) {
|
||||
LogUtils.d(TAG, "run()");
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "run() exit");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package cc.winboll.studio.appbase.services;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/07 12:39:24
|
||||
* @Describe 普通服务示例
|
||||
*/
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.appbase.models.TestDemoServiceBean;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class TestDemoService extends Service {
|
||||
|
||||
public static final String TAG = "TestDemoService";
|
||||
|
||||
public static final String ACTION_ENABLE = TestDemoService.class.getName() + ".ACTION_ENABLE";
|
||||
public static final String ACTION_DISABLE = TestDemoService.class.getName() + ".ACTION_DISABLE";
|
||||
|
||||
volatile static TestThread _TestThread;
|
||||
|
||||
volatile static boolean _IsRunning;
|
||||
|
||||
public synchronized static void setIsRunning(boolean isRunning) {
|
||||
_IsRunning = isRunning;
|
||||
}
|
||||
|
||||
public static boolean isRunning() {
|
||||
return _IsRunning;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return new MyBinder();
|
||||
}
|
||||
|
||||
public class MyBinder extends Binder {
|
||||
public TestDemoService getService() {
|
||||
return TestDemoService.this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate()");
|
||||
|
||||
|
||||
run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "onStartCommand(...)");
|
||||
TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
|
||||
if (bean == null) {
|
||||
bean = new TestDemoServiceBean();
|
||||
}
|
||||
|
||||
if (intent.getAction() != null) {
|
||||
if (intent.getAction().equals(ACTION_ENABLE)) {
|
||||
bean.setIsEnable(true);
|
||||
LogUtils.d(TAG, "setIsEnable(true);");
|
||||
TestDemoServiceBean.saveBean(this, bean);
|
||||
} else if (intent.getAction().equals(ACTION_DISABLE)) {
|
||||
bean.setIsEnable(false);
|
||||
LogUtils.d(TAG, "setIsEnable(false);");
|
||||
TestDemoServiceBean.saveBean(this, bean);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||
//return super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
void run() {
|
||||
LogUtils.d(TAG, "run()");
|
||||
TestDemoServiceBean bean = TestDemoServiceBean.loadBean(this, TestDemoServiceBean.class);
|
||||
if (bean == null) {
|
||||
bean = new TestDemoServiceBean();
|
||||
TestDemoServiceBean.saveBean(this, bean);
|
||||
}
|
||||
if (bean.isEnable()) {
|
||||
LogUtils.d(TAG, "run() bean.isEnable()");
|
||||
TestThread.getInstance(this).start();
|
||||
LogUtils.d(TAG, "_TestThread.start()");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy()");
|
||||
TestThread.getInstance(this).setIsExit(true);
|
||||
|
||||
_IsRunning = false;
|
||||
}
|
||||
|
||||
static class TestThread extends Thread {
|
||||
|
||||
volatile static TestThread _TestThread;
|
||||
Context mContext;
|
||||
volatile boolean isStarted = false;
|
||||
volatile boolean isExit = false;
|
||||
|
||||
TestThread(Context context) {
|
||||
super();
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
public static synchronized TestThread getInstance(Context context) {
|
||||
if (_TestThread != null) {
|
||||
_TestThread.setIsExit(true);
|
||||
}
|
||||
_TestThread = new TestThread(context);
|
||||
|
||||
return _TestThread;
|
||||
}
|
||||
|
||||
public synchronized void setIsExit(boolean isExit) {
|
||||
this.isExit = isExit;
|
||||
}
|
||||
|
||||
public boolean isExit() {
|
||||
return isExit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isStarted == false) {
|
||||
isStarted = true;
|
||||
super.run();
|
||||
LogUtils.d(TAG, "run() start");
|
||||
|
||||
while (!isExit()) {
|
||||
LogUtils.d(TAG, "run()");
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "run() exit");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package cc.winboll.studio.appbase.threads;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/14 03:46:44
|
||||
*/
|
||||
import android.content.Context;
|
||||
import cc.winboll.studio.appbase.handlers.MainServiceHandler;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MainServiceThread extends Thread {
|
||||
|
||||
public static final String TAG = "MainServiceThread";
|
||||
|
||||
Context mContext;
|
||||
|
||||
// 控制线程是否退出的标志
|
||||
volatile boolean isExist = false;
|
||||
|
||||
// 服务Handler, 用于线程发送消息使用
|
||||
WeakReference<MainServiceHandler> mwrMainServiceHandler;
|
||||
|
||||
public void setIsExist(boolean isExist) {
|
||||
this.isExist = isExist;
|
||||
}
|
||||
|
||||
public boolean isExist() {
|
||||
return isExist;
|
||||
}
|
||||
|
||||
public MainServiceThread(Context context, MainServiceHandler handler) {
|
||||
mContext = context;
|
||||
mwrMainServiceHandler = new WeakReference<MainServiceHandler>(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, "run()");
|
||||
|
||||
while (!isExist()) {
|
||||
//ToastUtils.show("run()");
|
||||
//LogUtils.d(TAG, "run()");
|
||||
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "run() exit.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package cc.winboll.studio.appbase.widgets;
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/15 14:41:25
|
||||
* @Describe TimeWidget
|
||||
*/
|
||||
import android.app.PendingIntent;
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProvider;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.RemoteViews;
|
||||
import cc.winboll.studio.appbase.R;
|
||||
import cc.winboll.studio.appbase.receivers.APPNewsWidgetClickListener;
|
||||
import cc.winboll.studio.libappbase.AppUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.models.APPModel;
|
||||
import cc.winboll.studio.libappbase.models.WinBoLLNewsBean;
|
||||
import cc.winboll.studio.libappbase.winboll.WinBoLL;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import cc.winboll.studio.libappbase.models.WinBoLLModel;
|
||||
|
||||
public class APPNewsWidget extends AppWidgetProvider {
|
||||
|
||||
public static final String TAG = "APPNewsWidget";
|
||||
|
||||
public static final String ACTION_WAKEUP_SERVICE = APPNewsWidget.class.getName() + ".ACTION_WAKEUP_SERVICE";
|
||||
public static final String ACTION_RELOAD_REPORT = APPNewsWidget.class.getName() + ".ACTION_RELOAD_REPORT";
|
||||
|
||||
|
||||
volatile static ArrayList<WinBoLLNewsBean> _WinBoLLNewsBeanList;
|
||||
final static int _MAX_PAGES = 10;
|
||||
final static int _OnePageLinesCount = 5;
|
||||
volatile static int _CurrentPageIndex = 0;
|
||||
|
||||
@Override
|
||||
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||
initWinBoLLNewsBeanList(context);
|
||||
for (int appWidgetId : appWidgetIds) {
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
initWinBoLLNewsBeanList(context);
|
||||
if (intent.getAction().equals(ACTION_RELOAD_REPORT)) {
|
||||
LogUtils.d(TAG, "ACTION_RELOAD_REPORT");
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
|
||||
for (int appWidgetId : appWidgetIds) {
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||
}
|
||||
}else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) {
|
||||
LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE");
|
||||
String szWinBoLLModel = intent.getStringExtra(WinBoLL.EXTRA_WINBOLLMODEL);
|
||||
LogUtils.d(TAG, String.format("szWinBoLLModel %s", szWinBoLLModel));
|
||||
if (szWinBoLLModel != null && !szWinBoLLModel.equals("")) {
|
||||
try {
|
||||
WinBoLLModel bean = WinBoLLModel.parseStringToBean(szWinBoLLModel, WinBoLLModel.class);
|
||||
if (bean != null) {
|
||||
String szAppPackageName = bean.getAppPackageName();
|
||||
LogUtils.d(TAG, String.format("szAppPackageName %s", szAppPackageName));
|
||||
String szAppMainServiveName = bean.getAppMainServiveName();
|
||||
LogUtils.d(TAG, String.format("szAppMainServiveName %s", szAppMainServiveName));
|
||||
|
||||
|
||||
String appName = AppUtils.getAppNameByPackageName(context, szAppPackageName);
|
||||
LogUtils.d(TAG, String.format("appName %s", appName));
|
||||
WinBoLLNewsBean winBollNewsBean = new WinBoLLNewsBean(appName);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
|
||||
String currentTime = sdf.format(new Date());
|
||||
StringBuilder sbLine = new StringBuilder();
|
||||
sbLine.append("[");
|
||||
sbLine.append(currentTime);
|
||||
sbLine.append("] Wake up ");
|
||||
sbLine.append(appName);
|
||||
winBollNewsBean.setMessage(sbLine.toString());
|
||||
|
||||
addWinBoLLNewsBean(context, winBollNewsBean);
|
||||
|
||||
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, APPNewsWidget.class));
|
||||
for (int appWidgetId : appWidgetIds) {
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 加入新报告信息
|
||||
//
|
||||
public synchronized static void addWinBoLLNewsBean(Context context, WinBoLLNewsBean bean) {
|
||||
initWinBoLLNewsBeanList(context);
|
||||
_WinBoLLNewsBeanList.add(0, bean);
|
||||
// 控制记录总数
|
||||
while (_WinBoLLNewsBeanList.size() > _MAX_PAGES * _OnePageLinesCount) {
|
||||
_WinBoLLNewsBeanList.remove(_WinBoLLNewsBeanList.size() - 1);
|
||||
}
|
||||
WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
|
||||
}
|
||||
|
||||
synchronized static void initWinBoLLNewsBeanList(Context context) {
|
||||
if (_WinBoLLNewsBeanList == null) {
|
||||
_WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>();
|
||||
WinBoLLNewsBean.loadBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
|
||||
}
|
||||
if (_WinBoLLNewsBeanList == null) {
|
||||
_WinBoLLNewsBeanList = new ArrayList<WinBoLLNewsBean>();
|
||||
WinBoLLNewsBean.saveBeanList(context, _WinBoLLNewsBeanList, WinBoLLNewsBean.class);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
|
||||
LogUtils.d(TAG, "updateAppWidget(...)");
|
||||
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_news);
|
||||
//设置按钮点击事件
|
||||
Intent intentPre = new Intent(context, APPNewsWidgetClickListener.class);
|
||||
intentPre.setAction(APPNewsWidgetClickListener.ACTION_PRE);
|
||||
PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre);
|
||||
Intent intentNext = new Intent(context, APPNewsWidgetClickListener.class);
|
||||
intentNext.setAction(APPNewsWidgetClickListener.ACTION_NEXT);
|
||||
PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext);
|
||||
|
||||
views.setTextViewText(R.id.tv_msg, getPageInfo());
|
||||
views.setTextViewText(R.id.tv_news, getMessage());
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||
}
|
||||
|
||||
public static String getMessage() {
|
||||
ArrayList<String> msgTemp = new ArrayList<String>();
|
||||
if (_WinBoLLNewsBeanList != null) {
|
||||
int start = _OnePageLinesCount * _CurrentPageIndex;
|
||||
start = _WinBoLLNewsBeanList.size() > start ? start : _WinBoLLNewsBeanList.size() - 1;
|
||||
for (int i = start, j = 0; i < _WinBoLLNewsBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) {
|
||||
msgTemp.add(_WinBoLLNewsBeanList.get(i).getMessage());
|
||||
}
|
||||
String message = String.join("\n", msgTemp);
|
||||
return message;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static void prePage(Context context) {
|
||||
if (_WinBoLLNewsBeanList != null) {
|
||||
if (_CurrentPageIndex > 0) {
|
||||
_CurrentPageIndex = _CurrentPageIndex - 1;
|
||||
}
|
||||
Intent intentWidget = new Intent(context, APPNewsWidget.class);
|
||||
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||
context.sendBroadcast(intentWidget);
|
||||
}
|
||||
}
|
||||
|
||||
public static void nextPage(Context context) {
|
||||
if (_WinBoLLNewsBeanList != null) {
|
||||
if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _WinBoLLNewsBeanList.size()) {
|
||||
_CurrentPageIndex = _CurrentPageIndex + 1;
|
||||
}
|
||||
Intent intentWidget = new Intent(context, APPNewsWidget.class);
|
||||
intentWidget.setAction(APPNewsWidget.ACTION_RELOAD_REPORT);
|
||||
context.sendBroadcast(intentWidget);
|
||||
}
|
||||
}
|
||||
|
||||
String getPageInfo() {
|
||||
if (_WinBoLLNewsBeanList == null) {
|
||||
return "0/0";
|
||||
}
|
||||
int leftCount = _WinBoLLNewsBeanList.size() % _OnePageLinesCount;
|
||||
int currentPageCount = _WinBoLLNewsBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1);
|
||||
return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount);
|
||||
}
|
||||
}
|
||||
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>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?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="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20Z"/>
|
||||
|
||||
</vector>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?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="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20M6.5,18H18.5Q19.55,18 20.27,17.27 21,16.55 21,15.5 21,14.45 20.27,13.73 19.55,13 18.5,13H17V11Q17,8.93 15.54,7.46 14.08,6 12,6 9.93,6 8.46,7.46 7,8.93 7,11H6.5Q5.05,11 4.03,12.03 3,13.05 3,14.5 3,15.95 4.03,17 5.05,18 6.5,18M12,12Z"/>
|
||||
|
||||
</vector>
|
||||
@@ -3,219 +3,59 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp">
|
||||
|
||||
<android.widget.Toolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<ScrollView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0">
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:spacing="12dp">
|
||||
|
||||
<LinearLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
android:text="应用崩溃测试"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="#81C7F5"
|
||||
android:paddingVertical="12dp"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:onClick="onCrashTest"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0"
|
||||
android:gravity="center_horizontal">
|
||||
<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="onLogTest"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="安卓R对应的是Android 11,其API级别是30。以下是Android 11中一些重要的API相关特性:
|
||||
<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="onToastUtilsTest"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
\n- 隐私保护方面:引入单次授权,让用户可以选择授予应用对位置信息、麦克风和摄像头的临时访问权限。还增加了数据访问审核功能,能让开发者深入了解应用在何处访问私密数据。
|
||||
|
||||
\n- 系统功能方面:提供了ControlsProviderService API,用于向连接的外部设备提供控件,这些控件显示于Android电源菜单中的设备控件下。媒体控件也得到更新,显示于快捷设置旁,来自多个应用的会话排列在一个可滑动的轮播界面中。
|
||||
|
||||
\n- 硬件支持方面:提供了一些API以支持瀑布屏,通过将窗口布局属性 layoutInDisplayCutoutMode 设为 LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS ,可允许窗口延伸到屏幕各个边缘上的刘海和瀑布区域。对于合页式屏幕配置的设备,提供了具有 TYPE_HINGE_ANGLE 的新传感器以及新的 SensorEvent ,用于监控合页角度。
|
||||
|
||||
\n- 安全方面:对生物识别身份验证机制进行了更新,引入了 BiometricManager.Authenticators 接口,定义了 BIOMETRIC_STRONG 、 BIOMETRIC_WEAK 、 DEVICE_CREDENTIAL 等身份验证类型。还在 BiometricPrompt 类中提供了对“每次使用时进行身份验证”密钥的更多支持。
|
||||
|
||||
\n- 性能和质量方面:支持无线调试,通过Android调试桥(adb)从工作站以无线方式部署和调试应用,避免了常见的USB连接问题。"/>
|
||||
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right|center_vertical"
|
||||
android:layout_width="wrap_content">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Debug Mode"
|
||||
android:layout_weight="1.0"
|
||||
android:onClick="onSwitchDebugMode"
|
||||
android:id="@+id/activitymainCheckBox1"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Test Application CrashReport"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onTestApplicationCrashReport"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="PreviewGlobalCrashActivity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onPreviewGlobalCrashActivity"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="StartCenter"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onStartCenter"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="StopCenter"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onStopCenter"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SartTestDemoService"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onSartTestDemoService"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="StopTestDemoService"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onStopTestDemoService"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="StopTestDemoServiceNoSettings"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onStopTestDemoServiceNoSettings"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SartTestDemoBindService"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onSartTestDemoBindService"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="StopTestDemoBindService"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onStopTestDemoBindService"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="StopTestDemoBindServiceNoSettings"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onStopTestDemoBindServiceNoSettings"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</HorizontalScrollView>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestStopMainServiceWithoutSettingEnable"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onTestStopMainServiceWithoutSettingEnable"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestUseComponentStartService"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onTestUseComponentStartService"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestDemoServiceSOS"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onTestDemoServiceSOS"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="TestOpenNewActivity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onTestOpenNewActivity"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?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.widget.Toolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="NewActivity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CloseThisActivity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onCloseThisActivity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CloseAllActivity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onCloseAllActivity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="New2Activity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onNew2Activity"/>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?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.widget.Toolbar
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="New2Activity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CloseThisActivity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onCloseThisActivity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CloseAllActivity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onCloseAllActivity"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="NewActivity"
|
||||
android:textAllCaps="false"
|
||||
android:onClick="onNewActivity"/>
|
||||
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="#FFFFFFFF">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="right|center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_weight="1.0"
|
||||
android:text="WinBoLLNews"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:text="⇦"
|
||||
android:id="@+id/widget_button_pre"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:text="⇨"
|
||||
android:id="@+id/widget_button_next"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/tv_msg"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:id="@+id/tv_news"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -3,18 +3,6 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/item_home"
|
||||
android:title="HOME"
|
||||
android:title="WinBoLL Home"
|
||||
android:icon="@drawable/ic_winboll"/>
|
||||
<item
|
||||
android:id="@+id/item_yun"
|
||||
android:title="YUN"
|
||||
android:icon="@drawable/ic_winboll"/>
|
||||
<item
|
||||
android:id="@+id/item_logon"
|
||||
android:title="Logon"
|
||||
android:icon="@drawable/ic_winboll"/>
|
||||
<item
|
||||
android:id="@+id/item_log"
|
||||
android:title="LOG"
|
||||
android:icon="@drawable/ic_winboll_log"/>
|
||||
</menu>
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<!-- 允许访问 winboll.cc 及其子域名(原配置) -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">winboll.cc</domain>
|
||||
</domain-config>
|
||||
|
||||
<!-- **新增:允许访问 IP 地址 10.8.0.250** -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">10.8.0.250</domain> <!-- 不包含子域名 -->
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="200dp"
|
||||
android:minHeight="100dp"
|
||||
android:updatePeriodMillis="1000"
|
||||
android:initialLayout="@layout/widget_news">
|
||||
</appwidget-provider>
|
||||
1
keystore
1
keystore
Submodule keystore deleted from e7f70226c1
@@ -21,8 +21,8 @@ android {
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
api 'cc.winboll.studio:libapputils:15.8.5'
|
||||
api 'cc.winboll.studio:libappbase:15.9.5'
|
||||
api 'cc.winboll.studio:libapputils:15.10.2'
|
||||
api 'cc.winboll.studio:libappbase:15.10.9'
|
||||
|
||||
// 吐司类库
|
||||
api 'com.github.getActivity:ToastUtils:10.5'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Aug 31 05:00:43 CST 2025
|
||||
stageCount=4
|
||||
#Mon Sep 29 13:04:07 HKT 2025
|
||||
stageCount=3
|
||||
libraryProject=libaes
|
||||
baseVersion=15.9
|
||||
publishVersion=15.9.3
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.2
|
||||
buildCount=0
|
||||
baseBetaVersion=15.9.4
|
||||
baseBetaVersion=15.10.3
|
||||
|
||||
@@ -23,17 +23,16 @@ import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import cc.winboll.studio.libaes.DrawerMenuDataAdapter;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.beans.AESThemeBean;
|
||||
import cc.winboll.studio.libaes.beans.DrawerMenuBean;
|
||||
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||
import cc.winboll.studio.libaes.models.DrawerMenuBean;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
import cc.winboll.studio.libaes.views.ADrawerMenuListView;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import com.baoyz.widget.PullRefreshLayout;
|
||||
import java.util.ArrayList;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
|
||||
public abstract class DrawerFragmentActivity extends AppCompatActivity implements IWinBoLLActivity,AdapterView.OnItemClickListener {
|
||||
public abstract class DrawerFragmentActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
|
||||
|
||||
public static final String TAG = "DrawerFragmentActivity";
|
||||
|
||||
@@ -176,8 +175,6 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
|
||||
for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
|
||||
getString(i);
|
||||
}
|
||||
// } else if (R.id.item_log == item.getItemId()) {
|
||||
// GlobalApplication.getWinBoLLActivityManager().startLogActivity(this);
|
||||
} else if (R.id.item_about == item.getItemId()) {
|
||||
LogUtils.d(TAG, "onAbout");
|
||||
} else if (android.R.id.home == item.getItemId()) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libappbase.dialogs;
|
||||
package cc.winboll.studio.libaes.dialogs;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -1,11 +1,12 @@
|
||||
package cc.winboll.studio.libappbase.winboll;
|
||||
import android.app.Activity;
|
||||
package cc.winboll.studio.libaes.interfaces;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/05/10 09:34
|
||||
* @Describe WinBoLL 窗口操作接口
|
||||
*/
|
||||
import android.app.Activity;
|
||||
|
||||
public abstract interface IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "IWinBoLLActivity";
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libaes.beans;
|
||||
package cc.winboll.studio.libaes.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libaes.winboll;
|
||||
package cc.winboll.studio.libaes.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libaes.beans;
|
||||
package cc.winboll.studio.libaes.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libaes.beans;
|
||||
package cc.winboll.studio.libaes.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen
|
||||
@@ -7,7 +7,7 @@ import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
|
||||
@@ -10,8 +10,8 @@ import android.os.Bundle;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
|
||||
public class TestASupportToolbarActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||
|
||||
|
||||
@@ -10,22 +10,11 @@ import android.os.Bundle;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.utils.AESThemeUtil;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
|
||||
public class TestAToolbarActivity extends Activity implements IWinBoLLActivity {
|
||||
public class TestAToolbarActivity extends Activity {
|
||||
|
||||
public static final String TAG = "TestAToolbarActivity";
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
AESThemeUtil.applyAppTheme(this);
|
||||
|
||||
@@ -14,9 +14,9 @@ import android.widget.Toast;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
|
||||
import cc.winboll.studio.libaes.beans.DrawerMenuBean;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libaes.models.DrawerMenuBean;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class TestDrawerFragmentActivity extends DrawerFragmentActivity implements IWinBoLLActivity {
|
||||
|
||||
@@ -12,7 +12,7 @@ import android.view.MenuItem;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.activitys.DrawerFragmentActivity;
|
||||
import cc.winboll.studio.libaes.beans.AESThemeBean;
|
||||
import cc.winboll.studio.libaes.models.AESThemeBean;
|
||||
|
||||
public class AESThemeUtil {
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libappbase.winboll;
|
||||
package cc.winboll.studio.libaes.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -8,8 +8,7 @@ import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class MyActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libappbase.winboll;
|
||||
package cc.winboll.studio.libaes.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -10,12 +10,13 @@ package cc.winboll.studio.libappbase.winboll;
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.TaskStackBuilder;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import cc.winboll.studio.libaes.interfaces.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.LogActivity;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
@@ -29,7 +30,7 @@ public class WinBoLLActivityManager {
|
||||
|
||||
public enum WinBoLLUI_TYPE { Aplication, Service }
|
||||
|
||||
Context mContext;
|
||||
GlobalApplication mGlobalApplication;
|
||||
volatile static WinBoLLActivityManager _mIWinBoLLActivityManager;
|
||||
Map<String, IWinBoLLActivity> mActivityListMap;
|
||||
|
||||
@@ -41,17 +42,21 @@ public class WinBoLLActivityManager {
|
||||
public static WinBoLLUI_TYPE getWinBoLLUI_TYPE() {
|
||||
return _WinBoLLUI_TYPE;
|
||||
}
|
||||
WinBoLLActivityManager() {
|
||||
mContext = GlobalApplication.getInstance();
|
||||
|
||||
WinBoLLActivityManager(GlobalApplication application) {
|
||||
mGlobalApplication = application;
|
||||
mActivityListMap = new HashMap<String, IWinBoLLActivity>();
|
||||
}
|
||||
|
||||
public static synchronized WinBoLLActivityManager getInstance() {
|
||||
if (_mIWinBoLLActivityManager == null) {
|
||||
_mIWinBoLLActivityManager = new WinBoLLActivityManager();
|
||||
}
|
||||
public static WinBoLLActivityManager getInstance() {
|
||||
return _mIWinBoLLActivityManager;
|
||||
}
|
||||
|
||||
public static synchronized <T extends GlobalApplication> void init(T application) {
|
||||
if (_mIWinBoLLActivityManager == null) {
|
||||
_mIWinBoLLActivityManager = new WinBoLLActivityManager(application);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把Activity添加到管理中
|
||||
@@ -187,11 +192,11 @@ public class WinBoLLActivityManager {
|
||||
ToastUtils.show("finishAll() activity");
|
||||
if (iWinBoLLActivity != null && iWinBoLLActivity.getActivity() != null && !iWinBoLLActivity.getActivity().isFinishing() && !iWinBoLLActivity.getActivity().isDestroyed()) {
|
||||
//ToastUtils.show("activity != null ...");
|
||||
if (GlobalApplication.getWinBoLLActivityManager().getWinBoLLUI_TYPE() == WinBoLLUI_TYPE.Service) {
|
||||
if (getWinBoLLUI_TYPE() == WinBoLLUI_TYPE.Service) {
|
||||
// 结束窗口和最近任务栏, 建议前台服务类应用使用,可以方便用户再次调用 UI 操作。
|
||||
iWinBoLLActivity.getActivity().finishAndRemoveTask();
|
||||
//ToastUtils.show("finishAll() activity.finishAndRemoveTask();");
|
||||
} else if (GlobalApplication.getWinBoLLActivityManager().getWinBoLLUI_TYPE() == WinBoLLUI_TYPE.Aplication) {
|
||||
} else if (getWinBoLLUI_TYPE() == WinBoLLUI_TYPE.Aplication) {
|
||||
// 结束窗口保留最近任务栏,建议前台服务类应用使用,可以保持应用的系统自觉性。
|
||||
iWinBoLLActivity.getActivity().finish();
|
||||
//ToastUtils.show("finishAll() activity.finish();");
|
||||
@@ -1,4 +1,4 @@
|
||||
package cc.winboll.studio.libaes.winboll;
|
||||
package cc.winboll.studio.libaes.views;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
@@ -17,10 +17,12 @@ import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.dialogs.YesNoAlertDialog;
|
||||
import cc.winboll.studio.libaes.models.APPInfo;
|
||||
import cc.winboll.studio.libaes.utils.AppVersionUtils;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog;
|
||||
import cc.winboll.studio.libapputils.utils.PrefUtils;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import java.io.IOException;
|
||||
@@ -32,7 +34,6 @@ import okhttp3.Credentials;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import mehdi.sakout.aboutpage.BuildConfig;
|
||||
|
||||
public class AboutView extends LinearLayout {
|
||||
|
||||
@@ -40,10 +41,10 @@ public class AboutView extends LinearLayout {
|
||||
|
||||
public static final int MSG_APPUPDATE_CHECKED = 0;
|
||||
|
||||
Context mContext;
|
||||
static Context _mContext;
|
||||
APPInfo mAPPInfo;
|
||||
|
||||
WinBoLLServiceStatusView mWinBoLLServiceStatusView;
|
||||
//WinBoLLServiceStatusView mWinBoLLServiceStatusView;
|
||||
OnRequestDevUserInfoAutofillListener mOnRequestDevUserInfoAutofillListener;
|
||||
String mszAppName = "";
|
||||
String mszAppAPKFolderName = "";
|
||||
@@ -64,7 +65,7 @@ public class AboutView extends LinearLayout {
|
||||
|
||||
public AboutView(Context context, APPInfo appInfo) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
_mContext = context;
|
||||
|
||||
setAPPInfo(appInfo);
|
||||
initView(context);
|
||||
@@ -72,7 +73,7 @@ public class AboutView extends LinearLayout {
|
||||
|
||||
public AboutView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
_mContext = context;
|
||||
|
||||
initView(context, attrs);
|
||||
}
|
||||
@@ -110,7 +111,7 @@ public class AboutView extends LinearLayout {
|
||||
mszWinBoLLServerHost = GlobalApplication.isDebuging() ? "https://yun-preivew.winboll.cc": "https://yun.winboll.cc";
|
||||
|
||||
try {
|
||||
mszAppVersionName = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName;
|
||||
mszAppVersionName = _mContext.getPackageManager().getPackageInfo(_mContext.getPackageName(), 0).versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
@@ -122,35 +123,8 @@ public class AboutView extends LinearLayout {
|
||||
} else {
|
||||
mszGitea = "https://gitea.winboll.cc/" + mAPPInfo.getAppGitOwner() + "/" + mszAppGitName + "/src/branch/" + mAPPInfo.getAppGitAPPBranch() + "/" + mAPPInfo.getAppGitAPPSubProjectFolder();
|
||||
}
|
||||
|
||||
|
||||
if (GlobalApplication.isDebuging()) {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
View addedView = inflater.inflate(R.layout.view_about_dev, this, false);
|
||||
LinearLayout llMain = addedView.findViewById(R.id.viewaboutdevLinearLayout1);
|
||||
metDevUserName = addedView.findViewById(R.id.viewaboutdevEditText1);
|
||||
metDevUserPassword = addedView.findViewById(R.id.viewaboutdevEditText2);
|
||||
metDevUserName.setText(PrefUtils.getString(mContext, "metDevUserName", ""));
|
||||
metDevUserPassword.setText(PrefUtils.getString(mContext, "metDevUserPassword", ""));
|
||||
//mDevelopHostConnectionStatusView = new DevelopHostConnectionStatusView(context);
|
||||
mWinBoLLServiceStatusView = addedView.findViewById(R.id.viewaboutdevWinBoLLServiceStatusView1);
|
||||
mWinBoLLServiceStatusView.setServerHost(mszWinBoLLServerHost);
|
||||
mWinBoLLServiceStatusView.setAuthInfo(metDevUserName.getText().toString(), metDevUserPassword.getText().toString());
|
||||
//llMain.addView(mDevelopHostConnectionStatusView);
|
||||
llMain.addView(createAboutPage());
|
||||
addView(addedView);
|
||||
} else {
|
||||
LayoutInflater inflater = LayoutInflater.from(mContext);
|
||||
View addedView = inflater.inflate(R.layout.view_about_www, this, false);
|
||||
LinearLayout llMain = addedView.findViewById(R.id.viewaboutwwwLinearLayout1);
|
||||
//mDevelopHostConnectionStatusView = new DevelopHostConnectionStatusView(context);
|
||||
mWinBoLLServiceStatusView = addedView.findViewById(R.id.viewaboutwwwWinBoLLServiceStatusView1);
|
||||
mWinBoLLServiceStatusView.setServerHost(mszWinBoLLServerHost);
|
||||
mWinBoLLServiceStatusView.setAuthInfo("", "");
|
||||
//llMain.addView(mDevelopHostConnectionStatusView);
|
||||
llMain.addView(createAboutPage());
|
||||
addView(addedView);
|
||||
}
|
||||
|
||||
addView(createAboutPage());
|
||||
|
||||
// 初始化标题栏
|
||||
//setSubtitle(getContext().getString(R.string.text_about));
|
||||
@@ -162,9 +136,9 @@ public class AboutView extends LinearLayout {
|
||||
String szReleaseAppVersionName = "";
|
||||
try {
|
||||
//LogUtils.d(TAG, String.format("mContext.getPackageName() %s", mContext.getPackageName()));
|
||||
String szSubBetaSuffix = subBetaSuffix(mContext.getPackageName());
|
||||
String szSubBetaSuffix = subBetaSuffix(_mContext.getPackageName());
|
||||
//LogUtils.d(TAG, String.format("szSubBetaSuffix : %s", szSubBetaSuffix));
|
||||
szReleaseAppVersionName = mContext.getPackageManager().getPackageInfo(szSubBetaSuffix, 0).versionName;
|
||||
szReleaseAppVersionName = _mContext.getPackageManager().getPackageInfo(szSubBetaSuffix, 0).versionName;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
@@ -205,7 +179,7 @@ public class AboutView extends LinearLayout {
|
||||
String szMsg = "Current app is :\n[ " + mszCurrentAppPackageName
|
||||
+ " ]\nThe last app is :\n[ " + mszNewestAppPackageName
|
||||
+ " ]\nIs download the last app?";
|
||||
YesNoAlertDialog.show(mContext, "Application Update Prompt", szMsg, mIsDownlaodUpdateListener);
|
||||
YesNoAlertDialog.show(_mContext, "Application Update Prompt", szMsg, mIsDownlaodUpdateListener);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -216,22 +190,22 @@ public class AboutView extends LinearLayout {
|
||||
protected View createAboutPage() {
|
||||
// 定义 GitWeb 按钮
|
||||
//
|
||||
Element elementGitWeb = new Element(mContext.getString(R.string.gitea_home), R.drawable.ic_winboll);
|
||||
Element elementGitWeb = new Element(_mContext.getString(R.string.gitea_home), R.drawable.ic_winboll);
|
||||
elementGitWeb.setOnClickListener(mGitWebOnClickListener);
|
||||
// 定义检查更新按钮
|
||||
//
|
||||
Element elementAppUpdate = new Element(mContext.getString(R.string.app_update), R.drawable.ic_winboll);
|
||||
Element elementAppUpdate = new Element(_mContext.getString(R.string.app_update), R.drawable.ic_winboll);
|
||||
elementAppUpdate.setOnClickListener(mAppUpdateOnClickListener);
|
||||
|
||||
String szAppInfo = "";
|
||||
try {
|
||||
szAppInfo = mszAppName + " "
|
||||
+ mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0).versionName
|
||||
+ _mContext.getPackageManager().getPackageInfo(_mContext.getPackageName(), 0).versionName
|
||||
+ "\n" + mszAppDescription;
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
AboutPage aboutPage = new AboutPage(mContext);
|
||||
AboutPage aboutPage = new AboutPage(_mContext);
|
||||
aboutPage.setDescription(szAppInfo)
|
||||
//.isRTL(false)
|
||||
//.setCustomFont(String) // or Typeface
|
||||
@@ -256,10 +230,10 @@ public class AboutView extends LinearLayout {
|
||||
//
|
||||
Element elementAppMode;
|
||||
if (GlobalApplication.isDebuging()) {
|
||||
elementAppMode = new Element(mContext.getString(R.string.app_normal), R.drawable.ic_winboll);
|
||||
elementAppMode = new Element(_mContext.getString(R.string.app_normal), R.drawable.ic_winboll);
|
||||
elementAppMode.setOnClickListener(mAppNormalOnClickListener);
|
||||
} else {
|
||||
elementAppMode = new Element(mContext.getString(R.string.app_debug), R.drawable.ic_winboll);
|
||||
elementAppMode = new Element(_mContext.getString(R.string.app_debug), R.drawable.ic_winboll);
|
||||
elementAppMode.setOnClickListener(mAppDebugOnClickListener);
|
||||
}
|
||||
aboutPage.addItem(elementAppMode);
|
||||
@@ -272,7 +246,7 @@ public class AboutView extends LinearLayout {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
//ToastUtils.show("mAppDebugOnClickListener");
|
||||
setApp2DebugMode(mContext);
|
||||
setApp2DebugMode(_mContext);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -280,7 +254,7 @@ public class AboutView extends LinearLayout {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
//ToastUtils.show("mAppNormalOnClickListener");
|
||||
setApp2NormalMode(mContext);
|
||||
setApp2NormalMode(_mContext);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -290,9 +264,9 @@ public class AboutView extends LinearLayout {
|
||||
//intent.setAction(cc.winboll.studio.libapputils.intent.action.DEBUGVIEW);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
GlobalApplication.setIsDebuging(true);
|
||||
GlobalApplication.saveDebugStatus();
|
||||
GlobalApplication.saveDebugStatus(_mContext);
|
||||
|
||||
GlobalApplication.getWinBoLLActivityManager().finishAll();
|
||||
WinBoLLActivityManager.getInstance().finishAll();
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -302,9 +276,9 @@ public class AboutView extends LinearLayout {
|
||||
if (intent != null) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
GlobalApplication.setIsDebuging(false);
|
||||
GlobalApplication.saveDebugStatus();
|
||||
GlobalApplication.saveDebugStatus(_mContext);
|
||||
|
||||
GlobalApplication.getWinBoLLActivityManager().finishAll();
|
||||
WinBoLLActivityManager.getInstance().finishAll();
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -313,7 +287,7 @@ public class AboutView extends LinearLayout {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(mszGitea));
|
||||
mContext.startActivity(browserIntent);
|
||||
_mContext.startActivity(browserIntent);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -329,8 +303,8 @@ public class AboutView extends LinearLayout {
|
||||
String credential = "";
|
||||
if (GlobalApplication.isDebuging()) {
|
||||
credential = Credentials.basic(metDevUserName.getText().toString(), metDevUserPassword.getText().toString());
|
||||
PrefUtils.saveString(mContext, "metDevUserName", metDevUserName.getText().toString());
|
||||
PrefUtils.saveString(mContext, "metDevUserPassword", metDevUserPassword.getText().toString());
|
||||
PrefUtils.saveString(_mContext, "metDevUserName", metDevUserName.getText().toString());
|
||||
PrefUtils.saveString(_mContext, "metDevUserPassword", metDevUserPassword.getText().toString());
|
||||
} else {
|
||||
String username = "WinBoLL";
|
||||
String password = "WinBoLLPowerByZhanGSKen";
|
||||
@@ -387,7 +361,7 @@ public class AboutView extends LinearLayout {
|
||||
public void onYes() {
|
||||
String szUrl = mszWinBoLLServerHost + "/studio/download.php?appname=" + mszAppAPKFolderName + "&apkname=" + mszNewestAppPackageName;
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(szUrl));
|
||||
mContext.startActivity(browserIntent);
|
||||
_mContext.startActivity(browserIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,35 +0,0 @@
|
||||
package cc.winboll.studio.libaes.winboll;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/28 19:11:27
|
||||
* @Describe WinBoLL UI 状态图标枚举
|
||||
*/
|
||||
import cc.winboll.studio.libaes.R;
|
||||
|
||||
public enum EWUIStatusIconDrawable {
|
||||
NORMAL(0),
|
||||
NEWS(1)
|
||||
;
|
||||
|
||||
static final String TAG = "WUIStatusIconDrawable";
|
||||
|
||||
static String[] _mlistCNName = { "正常", "新的消息" };
|
||||
|
||||
private int value = 0;
|
||||
private EWUIStatusIconDrawable(int value) { //必须是private的,否则编译错误
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static int getIconDrawableId(EWUIStatusIconDrawable drawableId) {
|
||||
int res;
|
||||
switch(drawableId){
|
||||
case NEWS :
|
||||
res = R.drawable.ic_winbollbeta;
|
||||
break;
|
||||
default :
|
||||
res = R.drawable.ic_winboll;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package cc.winboll.studio.libaes.winboll;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen
|
||||
* @Date 2025/05/03 19:28
|
||||
*/
|
||||
public class WinBoLLClientService extends Service {
|
||||
|
||||
public static final String TAG = "WinBoLLClientService";
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package cc.winboll.studio.libaes.winboll;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen
|
||||
* @Date 2025/05/03 19:14
|
||||
*/
|
||||
public class WinBoLLServiceStatusView extends LinearLayout {
|
||||
|
||||
public static final String TAG = "WinBoLLServiceStatusView";
|
||||
|
||||
public WinBoLLServiceStatusView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public WinBoLLServiceStatusView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public WinBoLLServiceStatusView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
|
||||
void setServerHost(String szWinBoLLServerHost) {
|
||||
|
||||
}
|
||||
|
||||
void setAuthInfo(String szDevUserName, String szDevUserPassword) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
<?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:id="@+id/viewaboutdevLinearLayout1">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="DevelopUserName :"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="0dp"
|
||||
android:ems="10"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/viewaboutdevEditText1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="180dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="DevelopUserPassword :"/>
|
||||
|
||||
<EditText
|
||||
android:layout_width="0dp"
|
||||
android:inputType="textPassword"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/viewaboutdevEditText2"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<cc.winboll.studio.libaes.winboll.WinBoLLServiceStatusView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/viewaboutdevWinBoLLServiceStatusView1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
<?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:id="@+id/viewaboutwwwLinearLayout1">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<cc.winboll.studio.libaes.winboll.WinBoLLServiceStatusView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/viewaboutwwwWinBoLLServiceStatusView1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,11 +5,14 @@ apply from: '../.winboll/winboll_lint_build.gradle'
|
||||
|
||||
android {
|
||||
|
||||
compileSdkVersion 32
|
||||
buildToolsVersion "32.0.0"
|
||||
// 1. compileSdkVersion:必须 ≥ targetSdkVersion,建议直接等于 targetSdkVersion(30)
|
||||
compileSdkVersion 30
|
||||
|
||||
// 2. buildToolsVersion:需匹配 compileSdkVersion,建议使用 30.x.x 最新稳定版(无需高于 compileSdkVersion)
|
||||
buildToolsVersion "30.0.3" // 这是 30 对应的最新稳定版,避免使用 beta 版
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
}
|
||||
buildTypes {
|
||||
@@ -22,9 +25,4 @@ android {
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// 网络连接类库
|
||||
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
|
||||
|
||||
api 'com.google.code.gson:gson:2.10.1'
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Aug 18 03:56:26 HKT 2025
|
||||
stageCount=6
|
||||
#Tue Nov 11 13:06:53 GMT 2025
|
||||
stageCount=10
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.9
|
||||
publishVersion=15.9.5
|
||||
buildCount=0
|
||||
baseBetaVersion=15.9.6
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.9
|
||||
buildCount=8
|
||||
baseBetaVersion=15.10.10
|
||||
|
||||
@@ -3,15 +3,6 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.libappbase">
|
||||
|
||||
<!-- 拥有完全的网络访问权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- 发送持久广播 -->
|
||||
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
|
||||
|
||||
<!-- 对正在运行的应用重新排序 -->
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS"/>
|
||||
|
||||
<application>
|
||||
|
||||
<activity
|
||||
@@ -27,7 +18,7 @@
|
||||
android:process=":GlobalCrashActivity"/>
|
||||
|
||||
<activity
|
||||
android:name=".winboll.LogActivity"
|
||||
android:name=".LogActivity"
|
||||
android:label="LogActivity"
|
||||
android:resizeableActivity="true"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
@@ -37,76 +28,6 @@
|
||||
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".SimpleOperateSignalCenterService"
|
||||
android:exported="true">
|
||||
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".services.TestService"
|
||||
android:exported="true"/>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.MyBroadcastReceiver"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.libappbase.action.SOS"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.StatusWidget"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
|
||||
<action android:name="cc.winboll.studio.libappbase.widgets.StatusWidget.ACTION_STATUS_UPDATE"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_provider_info_status"/>
|
||||
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name=".widgets.StatusWidgetClickListener"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
<service android:name="cc.winboll.studio.libappbase.sos.SOSCenter"/>
|
||||
|
||||
<receiver
|
||||
android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.libappbase.sos.SOSCenterServiceReceiver"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
<activity android:name="cc.winboll.studio.libappbase.activities.YunActivity"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.libappbase.activities.LogonActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:01
|
||||
* @Describe WinBoLL 应用全局数据模型类
|
||||
* 继承自 BaseBean,用于存储和管理应用的核心配置信息(如调试状态),
|
||||
* 支持 JSON 序列化/反序列化,便于数据持久化或跨组件传递
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class APPModel extends BaseBean {
|
||||
|
||||
/**
|
||||
* 日志打印标签,用于区分当前类的日志输出
|
||||
*/
|
||||
public static final String TAG = "APPModel";
|
||||
|
||||
/**
|
||||
* 应用调试状态标识
|
||||
* true:应用处于调试模式(可输出详细日志、启用调试功能等)
|
||||
* false:应用处于正式模式(关闭调试相关功能,优化性能)
|
||||
*/
|
||||
private boolean isDebugging = false; // 修正拼写:原 isDebuging -> isDebugging(符合命名规范)
|
||||
|
||||
/**
|
||||
* 无参构造方法
|
||||
* 初始化调试状态为默认值:false(正式模式)
|
||||
*/
|
||||
public APPModel() {
|
||||
this.isDebugging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 带参构造方法
|
||||
* 可通过参数指定应用的初始调试状态
|
||||
* @param isDebugging 初始调试状态(true:调试模式;false:正式模式)
|
||||
*/
|
||||
public APPModel(boolean isDebugging) {
|
||||
this.isDebugging = isDebugging;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置应用调试状态
|
||||
* @param isDebugging 目标调试状态(true:开启调试;false:关闭调试)
|
||||
*/
|
||||
public void setIsDebugging(boolean isDebugging) {
|
||||
this.isDebugging = isDebugging;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前应用调试状态
|
||||
* @return 调试状态(true:调试中;false:非调试)
|
||||
*/
|
||||
public boolean isDebugging() {
|
||||
return isDebugging;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,返回当前类的全限定名
|
||||
* 用于标识数据模型的类类型(可用于反射、序列化校验等场景)
|
||||
* @return 类的全限定名(如:cc.winboll.studio.libappbase.APPModel)
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return APPModel.class.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,将当前模型的字段序列化到 JSON 中
|
||||
* 用于将调试状态等核心数据转换为 JSON 格式(如持久化到文件、网络传输)
|
||||
* @param jsonWriter JSON 写入器对象,用于输出 JSON 数据
|
||||
* @throws IOException 当 JSON 写入失败时抛出(如流关闭、格式错误)
|
||||
*/
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
// 先调用父类方法,序列化父类中的字段(若 BaseBean 有可序列化字段)
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
// 序列化当前类的调试状态字段:key 为 "isDebuging"(保持与原代码一致,避免兼容性问题),value 为当前状态
|
||||
jsonWriter.name("isDebuging").value(isDebugging());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,从 JSON 中解析字段并初始化当前对象
|
||||
* 用于将 JSON 格式的配置数据解析为 APPModel 实例(如从文件读取、网络接收后解析)
|
||||
* @param jsonReader JSON 读取器对象,用于读取 JSON 数据
|
||||
* @param name 当前解析的 JSON 字段名
|
||||
* @return true:字段解析成功;false:字段不属于当前类,需由调用者处理
|
||||
* @throws IOException 当 JSON 读取失败时抛出(如流关闭、数据格式错误)
|
||||
*/
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
// 先调用父类方法,解析父类中的字段(若 BaseBean 有可解析字段)
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) {
|
||||
return true; // 父类已处理该字段,直接返回成功
|
||||
} else {
|
||||
// 解析当前类的字段
|
||||
if (name.equals("isDebuging")) {
|
||||
// 读取 JSON 中 "isDebuging" 字段的值,设置为当前对象的调试状态
|
||||
setIsDebugging(jsonReader.nextBoolean());
|
||||
} else {
|
||||
// 字段不属于当前类,返回 false 提示调用者跳过该字段
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 字段解析成功,返回 true
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写父类方法,从 JSON 读取器中完整解析一个 APPModel 实例
|
||||
* 负责处理 JSON 对象的开始/结束标记,循环解析所有字段
|
||||
* @param jsonReader JSON 读取器对象,用于读取 JSON 数据
|
||||
* @return 解析完成的当前 APPModel 实例(支持链式调用)
|
||||
* @throws IOException 当 JSON 读取失败时抛出(如流关闭、数据格式错误)
|
||||
*/
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
// 开始解析 JSON 对象(对应 JSON 中的 '{')
|
||||
jsonReader.beginObject();
|
||||
// 循环读取 JSON 中的所有字段(直到对象结束)
|
||||
while (jsonReader.hasNext()) {
|
||||
// 获取当前字段名
|
||||
String name = jsonReader.nextName();
|
||||
// 解析字段:若当前类无法处理该字段,则跳过(避免解析异常)
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束解析 JSON 对象(对应 JSON 中的 '}')
|
||||
jsonReader.endObject();
|
||||
// 返回解析完成的实例(当前对象)
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/15 20:05:03
|
||||
* @Describe AppUtils
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
public class AppUtils {
|
||||
|
||||
public static final String TAG = "AppUtils";
|
||||
|
||||
public static String getAppNameByPackageName(Context context, String packageName) {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
|
||||
return (String) packageManager.getApplicationLabel(applicationInfo);
|
||||
} catch (NameNotFoundException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/01/15 11:11:52
|
||||
* @Describe Json Bean 基础类。
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:03
|
||||
* @Describe WinBoLL JSON 数据模型基类(抽象类)
|
||||
* 定义 Json Bean 的核心规范:序列化/反序列化、文件持久化、列表处理等通用逻辑,
|
||||
* 子类(如 APPModel)需实现抽象方法,实现自身字段的 JSON 读写
|
||||
* @param <T> 泛型约束,限定子类必须继承自 BaseBean
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.util.JsonReader;
|
||||
@@ -16,272 +19,418 @@ import java.util.ArrayList;
|
||||
|
||||
public abstract class BaseBean<T extends BaseBean> {
|
||||
|
||||
public static final String TAG = "BaseBean";
|
||||
static final String BEAN_NAME = "BeanName";
|
||||
/** 日志标签,用于当前基类的日志输出标识 */
|
||||
public static final String TAG = "BaseBean";
|
||||
/** JSON 中存储 Bean 类名的字段键(用于校验 Bean 类型一致性) */
|
||||
static final String BEAN_NAME = "BeanName";
|
||||
|
||||
public BaseBean() {}
|
||||
/**
|
||||
* 无参构造方法(子类需默认实现,支持反射实例化)
|
||||
*/
|
||||
public BaseBean() {}
|
||||
|
||||
public abstract String getName();
|
||||
/**
|
||||
* 抽象方法:获取当前 Bean 的全限定类名
|
||||
* 子类需实现,用于标识 Bean 类型(序列化/校验时使用)
|
||||
* @return 类的全限定名(如:cc.winboll.studio.libappbase.APPModel)
|
||||
*/
|
||||
public abstract String getName();
|
||||
|
||||
public String getBeanJsonFilePath(Context context) {
|
||||
/**
|
||||
* 获取单个 Bean 的 JSON 持久化文件路径
|
||||
* 路径:外部存储/应用私有目录/BaseBean/[类名].json
|
||||
* @param context 上下文(用于获取应用存储目录)
|
||||
* @return 单个 Bean 的文件绝对路径
|
||||
*/
|
||||
public String getBeanJsonFilePath(Context context) {
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + ".json";
|
||||
}
|
||||
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + ".json";
|
||||
}
|
||||
/**
|
||||
* 获取 Bean 列表的 JSON 持久化文件路径
|
||||
* 路径:外部存储/应用私有目录/BaseBean/[类名]_List.json
|
||||
* @param context 上下文(用于获取应用存储目录)
|
||||
* @return Bean 列表的文件绝对路径
|
||||
*/
|
||||
public String getBeanListJsonFilePath(Context context) {
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json";
|
||||
}
|
||||
|
||||
public String getBeanListJsonFilePath(Context context) {
|
||||
/**
|
||||
* 将 Bean 类名写入 JSON(序列化基础字段)
|
||||
* 子类可重写扩展,添加自身字段的 JSON 写入逻辑
|
||||
* @param jsonWriter JSON 写入器(用于输出 JSON 数据)
|
||||
* @throws IOException JSON 写入失败时抛出(如流异常)
|
||||
*/
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
// 写入 Bean 类名字段(用于反序列化时校验类型)
|
||||
jsonWriter.name(BEAN_NAME).value(getName());
|
||||
}
|
||||
|
||||
return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json";
|
||||
}
|
||||
/**
|
||||
* 从 JSON 读取字段并初始化 Bean(反序列化基础逻辑)
|
||||
* 子类需重写,实现自身字段的解析逻辑
|
||||
* @param jsonReader JSON 读取器(用于读取 JSON 数据)
|
||||
* @param name 当前解析的 JSON 字段名
|
||||
* @return true:字段解析成功(当前类处理);false:字段未处理(需跳过)
|
||||
* @throws IOException JSON 读取失败时抛出(如流异常)
|
||||
*/
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
return false; // 基类未处理任何字段,返回 false
|
||||
}
|
||||
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
jsonWriter.name(BEAN_NAME).value(getName());
|
||||
}
|
||||
/**
|
||||
* 抽象方法:从 JSON 读取器解析并返回 Bean 实例
|
||||
* 子类需实现,处理自身字段的完整解析逻辑
|
||||
* @param jsonReader JSON 读取器(用于读取 JSON 数据)
|
||||
* @return 解析完成的 Bean 实例
|
||||
* @throws IOException JSON 读取失败时抛出(如流异常)
|
||||
*/
|
||||
abstract public T readBeanFromJsonReader(JsonReader jsonReader) throws IOException;
|
||||
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 校验 JSON 文件中的 Bean 列表与目标类是否一致
|
||||
* 对比文件中每个 Bean 的类名与目标类名,返回不一致信息
|
||||
* @param szFilePath JSON 文件路径(存储 Bean 列表的文件)
|
||||
* @param clazz 目标 Bean 类(用于校验类型)
|
||||
* @return 空串:校验一致;非空串:不一致信息(总数/差异数)或异常信息
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> String checkIsTheSameBeanListAndFile(String szFilePath, Class<T> clazz) {
|
||||
StringBuilder sbResult = new StringBuilder();
|
||||
String szErrorInfo = "Check Is The Same Bean List And File Error : ";
|
||||
|
||||
abstract public T readBeanFromJsonReader(JsonReader jsonReader) throws IOException;
|
||||
try {
|
||||
int sameCount = 0; // 类名匹配的 Bean 数量
|
||||
int totalCount = 0; // 文件中 Bean 总数量
|
||||
|
||||
public static <T extends BaseBean> String checkIsTheSameBeanListAndFile(String szFilePath, Class<T> clazz) {
|
||||
StringBuilder sbResult = new StringBuilder();
|
||||
String szErrorInfo = "Check Is The Same Bean List And File Error : ";
|
||||
// 反射创建目标 Bean 实例(用于获取类名)
|
||||
T beanTemp = clazz.newInstance();
|
||||
String targetBeanName = beanTemp.getName();
|
||||
// 读取文件中的 JSON 字符串
|
||||
String listJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
StringReader stringReader = new StringReader(listJson);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
|
||||
try {
|
||||
int nSameCount = 0;
|
||||
int nBeanListCout = 0;
|
||||
jsonReader.beginArray(); // 开始解析 JSON 数组(Bean 列表)
|
||||
while (jsonReader.hasNext()) {
|
||||
totalCount++;
|
||||
jsonReader.beginObject(); // 开始解析单个 Bean 对象
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
// 只校验 BEAN_NAME 字段,其他字段跳过
|
||||
if (name.equals(BEAN_NAME)) {
|
||||
// 对比当前 Bean 类名与目标类名
|
||||
if (targetBeanName.equals(jsonReader.nextString())) {
|
||||
sameCount++;
|
||||
}
|
||||
} else {
|
||||
jsonReader.skipValue(); // 跳过非目标字段
|
||||
}
|
||||
}
|
||||
jsonReader.endObject(); // 结束单个 Bean 对象解析
|
||||
}
|
||||
jsonReader.endArray(); // 结束 JSON 数组解析
|
||||
|
||||
T beanTemp = clazz.newInstance();
|
||||
String szBeanSimpleName = beanTemp.getName();
|
||||
String szListJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
StringReader stringReader = new StringReader(szListJson);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
jsonReader.beginArray();
|
||||
while (jsonReader.hasNext()) {
|
||||
nBeanListCout++;
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (name.equals(BEAN_NAME)) {
|
||||
if (szBeanSimpleName.equals(jsonReader.nextString())) {
|
||||
nSameCount++;
|
||||
}
|
||||
} else {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
jsonReader.endObject();
|
||||
}
|
||||
jsonReader.endArray();
|
||||
// 生成校验结果
|
||||
if (sameCount == totalCount) {
|
||||
return ""; // 全部匹配,返回空串
|
||||
} else {
|
||||
// 部分不匹配,返回统计信息
|
||||
sbResult.append("Total : ").append(totalCount)
|
||||
.append(" Diff : ").append(totalCount - sameCount);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
// 反射实例化失败(如无无参构造)
|
||||
sbResult.append(szErrorInfo).append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
// 反射访问权限异常
|
||||
sbResult.append(szErrorInfo).append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IOException e) {
|
||||
// 文件读取或 JSON 解析异常
|
||||
sbResult.append(szErrorInfo).append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return sbResult.toString();
|
||||
}
|
||||
|
||||
// 返回检查结果
|
||||
if (nSameCount == nBeanListCout) {
|
||||
// 检查一致直接返回空串
|
||||
return "";
|
||||
} else {
|
||||
// 检查不一致返回对比信息
|
||||
sbResult.append("Total : ");
|
||||
sbResult.append(nBeanListCout);
|
||||
sbResult.append(" Diff : ");
|
||||
sbResult.append(nBeanListCout - nSameCount);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
sbResult.append(szErrorInfo);
|
||||
sbResult.append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
sbResult.append(szErrorInfo);
|
||||
sbResult.append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IOException e) {
|
||||
sbResult.append(szErrorInfo);
|
||||
sbResult.append(e);
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return sbResult.toString();
|
||||
}
|
||||
/**
|
||||
* 将 JSON 字符串解析为目标 Bean 实例
|
||||
* 通过反射创建 Bean 实例,调用子类解析逻辑完成初始化
|
||||
* @param szBean JSON 字符串(单个 Bean 的 JSON 数据)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return 解析成功的 Bean 实例;失败返回 null
|
||||
* @throws IOException JSON 解析失败时抛出
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> T parseStringToBean(String szBean, Class<T> clazz) throws IOException {
|
||||
StringReader stringReader = new StringReader(szBean);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
|
||||
public static <T extends BaseBean> T parseStringToBean(String szBean, Class<T> clazz) throws IOException {
|
||||
// 创建 JsonWriter 对象
|
||||
StringReader stringReader = new StringReader(szBean);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return (T)beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
// 反射创建 Bean 实例
|
||||
T beanTemp = clazz.newInstance();
|
||||
// 调用子类解析方法,返回解析后的实例
|
||||
return (T) beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
// 反射异常日志记录
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean parseStringToBeanList(String szBeanList, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
if(beanList == null) {
|
||||
beanList = new ArrayList<T>();
|
||||
} else {
|
||||
beanList.clear();
|
||||
}
|
||||
StringReader stringReader = new StringReader(szBeanList);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
jsonReader.beginArray();
|
||||
while (jsonReader.hasNext()) {
|
||||
T beanTemp = clazz.newInstance();
|
||||
T bean = (T)beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
if (bean != null) {
|
||||
beanList.add(bean);
|
||||
//LogUtils.d(TAG, "beanList.add(bean)");
|
||||
}
|
||||
}
|
||||
jsonReader.endArray();
|
||||
return true;
|
||||
//LogUtils.d(TAG, "beanList.size() is " + Integer.toString(beanList.size()));
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 将 JSON 字符串解析为 Bean 列表
|
||||
* 清空目标列表,将解析后的 Bean 逐个添加到列表中
|
||||
* @param szBeanList JSON 字符串(Bean 列表的 JSON 数组)
|
||||
* @param beanList 目标列表(存储解析后的 Bean)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return true:解析成功;false:解析失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean parseStringToBeanList(String szBeanList, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
// 初始化目标列表(为空则创建,非空则清空)
|
||||
if (beanList == null) {
|
||||
beanList = new ArrayList<T>();
|
||||
} else {
|
||||
beanList.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// 创建 JsonWriter 对象
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" ");
|
||||
try {// 开始 JSON 对象
|
||||
jsonWriter.beginObject();
|
||||
// 写入键值对
|
||||
writeThisToJsonWriter(jsonWriter);
|
||||
// 结束 JSON 对象
|
||||
jsonWriter.endObject();
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
// 获取 JSON 字符串
|
||||
return "";
|
||||
}
|
||||
StringReader stringReader = new StringReader(szBeanList);
|
||||
JsonReader jsonReader = new JsonReader(stringReader);
|
||||
|
||||
public static <T extends BaseBean> String toStringByBeanList(ArrayList<T> beanList) {
|
||||
try {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" ");
|
||||
jsonWriter.beginArray();
|
||||
for (int i = 0; i < beanList.size(); i++) {
|
||||
// 开始 JSON 对象
|
||||
jsonWriter.beginObject();
|
||||
// 写入键值对
|
||||
beanList.get(i).writeThisToJsonWriter(jsonWriter);
|
||||
// 结束 JSON 对象
|
||||
jsonWriter.endObject();
|
||||
}
|
||||
jsonWriter.endArray();
|
||||
jsonWriter.close();
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
jsonReader.beginArray(); // 开始解析 JSON 数组
|
||||
while (jsonReader.hasNext()) {
|
||||
// 反射创建 Bean 实例,解析并添加到列表
|
||||
T beanTemp = clazz.newInstance();
|
||||
T bean = (T) beanTemp.readBeanFromJsonReader(jsonReader);
|
||||
if (bean != null) {
|
||||
beanList.add(bean);
|
||||
}
|
||||
}
|
||||
jsonReader.endArray(); // 结束 JSON 数组解析
|
||||
return true;
|
||||
} catch (InstantiationException | IllegalAccessException | IOException e) {
|
||||
// 异常日志记录
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写 toString(),将 Bean 序列化为格式化的 JSON 字符串
|
||||
* 调用自身序列化逻辑,生成带缩进的 JSON(便于调试)
|
||||
* @return Bean 的 JSON 字符串;失败返回空串
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" "); // 设置 JSON 缩进(格式化输出)
|
||||
|
||||
public static <T extends BaseBean> T loadBean(Context context, Class<T> clazz) {
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanFromFile(beanTemp.getBeanJsonFilePath(context), clazz);
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
jsonWriter.beginObject(); // 开始 JSON 对象
|
||||
writeThisToJsonWriter(jsonWriter); // 写入 Bean 字段(子类扩展)
|
||||
jsonWriter.endObject(); // 结束 JSON 对象
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> T loadBeanFromFile(String szFilePath, Class<T> clazz) {
|
||||
try {
|
||||
try {
|
||||
File fTemp = new File(szFilePath);
|
||||
if (fTemp.exists()) {
|
||||
T beanTemp = clazz.newInstance();String szJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
return beanTemp.parseStringToBean(szJson, clazz);
|
||||
}
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
/**
|
||||
* 将 Bean 列表序列化为格式化的 JSON 字符串
|
||||
* 遍历列表,逐个序列化每个 Bean,生成 JSON 数组
|
||||
* @param beanList 待序列化的 Bean 列表
|
||||
* @return 列表的 JSON 字符串;失败返回空串
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> String toStringByBeanList(ArrayList<T> beanList) {
|
||||
try {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
JsonWriter jsonWriter = new JsonWriter(stringWriter);
|
||||
jsonWriter.setIndent(" "); // 格式化缩进
|
||||
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
jsonWriter.beginArray(); // 开始 JSON 数组
|
||||
for (int i = 0; i < beanList.size(); i++) {
|
||||
jsonWriter.beginObject(); // 单个 Bean 开始
|
||||
beanList.get(i).writeThisToJsonWriter(jsonWriter); // 调用 Bean 自身序列化
|
||||
jsonWriter.endObject(); // 单个 Bean 结束
|
||||
}
|
||||
jsonWriter.endArray(); // 结束 JSON 数组
|
||||
jsonWriter.close();
|
||||
return stringWriter.toString();
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBean(Context context, T bean) {
|
||||
return saveBeanToFile(bean.getBeanJsonFilePath(context), bean);
|
||||
}
|
||||
/**
|
||||
* 从默认路径(getBeanJsonFilePath)加载 Bean 实例
|
||||
* 读取应用私有目录下的 JSON 文件,解析为目标 Bean
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return 加载成功的 Bean 实例;失败返回 null
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> T loadBean(Context context, Class<T> clazz) {
|
||||
try {
|
||||
// 反射创建 Bean 实例,获取默认文件路径
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanFromFile(beanTemp.getBeanJsonFilePath(context), clazz);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBeanToFile(String szFilePath, T bean) {
|
||||
try {
|
||||
String szJson = bean.toString();
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, szJson);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 从指定文件路径加载 Bean 实例
|
||||
* 检查文件是否存在,存在则读取 JSON 并解析为目标 Bean
|
||||
* @param szFilePath 目标文件路径(存储 Bean 的 JSON 文件)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return 加载成功的 Bean 实例;失败返回 null
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> T loadBeanFromFile(String szFilePath, Class<T> clazz) {
|
||||
try {
|
||||
File file = new File(szFilePath);
|
||||
if (file.exists()) { // 检查文件是否存在
|
||||
T beanTemp = clazz.newInstance();
|
||||
// 读取文件 JSON 字符串,解析为 Bean
|
||||
String json = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
return beanTemp.parseStringToBean(json, clazz);
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException | IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean loadBeanList(Context context, ArrayList<T> beanListDst, Class<T> clazz) {
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanListFromFile(beanTemp.getBeanListJsonFilePath(context), beanListDst, clazz);
|
||||
} catch (InstantiationException e) {} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 将 Bean 保存到默认路径(getBeanJsonFilePath)的文件中
|
||||
* 序列化 Bean 为 JSON,写入应用私有目录下的文件
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param bean 待保存的 Bean 实例
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBean(Context context, T bean) {
|
||||
return saveBeanToFile(bean.getBeanJsonFilePath(context), bean);
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean loadBeanListFromFile(String szFilePath, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
File fTemp = new File(szFilePath);
|
||||
if (fTemp.exists()) {
|
||||
String szListJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
return parseStringToBeanList(szListJson, beanList, clazz);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 将 Bean 保存到指定文件路径
|
||||
* 序列化 Bean 为 JSON 字符串,写入目标文件(覆盖原有内容)
|
||||
* @param szFilePath 目标文件路径(保存 JSON 的文件)
|
||||
* @param bean 待保存的 Bean 实例
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBeanToFile(String szFilePath, T bean) {
|
||||
try {
|
||||
// 序列化 Bean 为 JSON 字符串
|
||||
String json = bean.toString();
|
||||
// 写入文件(UTF-8 编码)
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, json);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBeanList(Context context, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
T beanTemp = clazz.newInstance();
|
||||
return saveBeanListToFile(beanTemp.getBeanListJsonFilePath(context), beanList);
|
||||
} catch (InstantiationException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
} catch (IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 从默认路径(getBeanListJsonFilePath)加载 Bean 列表
|
||||
* 读取应用私有目录下的列表 JSON 文件,解析并填充到目标列表
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param beanListDst 目标列表(存储加载后的 Bean)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return true:加载成功;false:加载失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean loadBeanList(Context context, ArrayList<T> beanListDst, Class<T> clazz) {
|
||||
try {
|
||||
// 反射创建 Bean 实例,获取默认列表文件路径
|
||||
T beanTemp = clazz.newInstance();
|
||||
return loadBeanListFromFile(beanTemp.getBeanListJsonFilePath(context), beanListDst, clazz);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static <T extends BaseBean> boolean saveBeanListToFile(String szFilePath, ArrayList<T> beanList) {
|
||||
try {
|
||||
String szJson = toStringByBeanList(beanList);
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, szJson);
|
||||
//LogUtils.d(TAG, "FileUtil.writeFile beanList.size() is " + Integer.toString(beanList.size()));
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 从指定文件路径加载 Bean 列表
|
||||
* 检查文件是否存在,存在则读取 JSON 数组,解析并填充到目标列表
|
||||
* @param szFilePath 目标文件路径(存储列表 JSON 的文件)
|
||||
* @param beanList 目标列表(存储加载后的 Bean)
|
||||
* @param clazz 目标 Bean 类(用于反射实例化)
|
||||
* @return true:加载成功;false:加载失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean loadBeanListFromFile(String szFilePath, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
File file = new File(szFilePath);
|
||||
if (file.exists()) { // 检查文件是否存在
|
||||
// 读取文件中的 JSON 字符串(Bean 列表数组)
|
||||
String listJson = UTF8FileUtils.readStringFromFile(szFilePath);
|
||||
// 解析 JSON 字符串为 Bean 列表,填充到目标列表
|
||||
return parseStringToBeanList(listJson, beanList, clazz);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 日志记录文件读取或解析异常
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Bean 列表保存到默认路径(getBeanListJsonFilePath)的文件中
|
||||
* 序列化列表为 JSON 数组,写入应用私有目录下的文件
|
||||
* @param context 上下文(用于获取文件路径)
|
||||
* @param beanList 待保存的 Bean 列表
|
||||
* @param clazz 目标 Bean 类(用于反射获取保存路径)
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBeanList(Context context, ArrayList<T> beanList, Class<T> clazz) {
|
||||
try {
|
||||
// 反射创建 Bean 实例,获取默认列表保存路径
|
||||
T beanTemp = clazz.newInstance();
|
||||
return saveBeanListToFile(beanTemp.getBeanListJsonFilePath(context), beanList);
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
// 日志记录反射实例化异常
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Bean 列表保存到指定文件路径
|
||||
* 序列化列表为 JSON 数组字符串,写入目标文件(覆盖原有内容)
|
||||
* @param szFilePath 目标文件路径(保存列表 JSON 的文件)
|
||||
* @param beanList 待保存的 Bean 列表
|
||||
* @return true:保存成功;false:保存失败
|
||||
* @param <T> 泛型约束,限定为 BaseBean 子类
|
||||
*/
|
||||
public static <T extends BaseBean> boolean saveBeanListToFile(String szFilePath, ArrayList<T> beanList) {
|
||||
try {
|
||||
// 序列化 Bean 列表为 JSON 字符串(数组格式)
|
||||
String json = toStringByBeanList(beanList);
|
||||
// 将 JSON 字符串写入文件(UTF-8 编码)
|
||||
UTF8FileUtils.writeStringToFile(szFilePath, json);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
// 日志记录文件写入或序列化异常
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2024/08/12 13:22:12
|
||||
* @Describe 异常处理类
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:14
|
||||
* @Describe * 应用全局崩溃处理类(单例逻辑)
|
||||
* 核心功能:捕获应用未捕获异常,记录崩溃日志到文件,启动崩溃报告页面,
|
||||
* 并通过「崩溃保险丝」机制防止重复崩溃,保障基础功能可用
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
@@ -12,29 +14,22 @@ import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.SpannableString;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
@@ -50,356 +45,506 @@ import java.util.Locale;
|
||||
|
||||
public final class CrashHandler {
|
||||
|
||||
public static final String TAG = "CrashHandler";
|
||||
/** 日志标签,用于当前类的日志输出标识 */
|
||||
public static final String TAG = "CrashHandler";
|
||||
|
||||
public static final String TITTLE = "CrashReport";
|
||||
/** 崩溃报告页面标题 */
|
||||
public static final String TITTLE = "CrashReport";
|
||||
|
||||
public static final String EXTRA_CRASH_INFO = "crashInfo";
|
||||
/** Intent 传递崩溃信息的键(用于向崩溃页面传递日志) */
|
||||
public static final String EXTRA_CRASH_INFO = "crashInfo";
|
||||
|
||||
final static String PREFS = CrashHandler.class.getName() + "PREFS";
|
||||
final static String PREFS_CRASHHANDLER_ISCRASHHAPPEN = "PREFS_CRASHHANDLER_ISCRASHHAPPEN";
|
||||
/** SharedPreferences 存储键(用于记录崩溃状态) */
|
||||
final static String PREFS = CrashHandler.class.getName() + "PREFS";
|
||||
/** SharedPreferences 中存储「是否发生崩溃」的键 */
|
||||
final static String PREFS_CRASHHANDLER_ISCRASHHAPPEN = "PREFS_CRASHHANDLER_ISCRASHHAPPEN";
|
||||
|
||||
public static String _CrashCountFilePath;
|
||||
/** 崩溃保险丝状态文件路径(存储当前熔断等级) */
|
||||
public static String _CrashCountFilePath;
|
||||
|
||||
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||
/** 系统默认的未捕获异常处理器(用于降级处理,避免 CrashHandler 自身崩溃) */
|
||||
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
|
||||
|
||||
public static void init(Application app) {
|
||||
_CrashCountFilePath = app.getExternalFilesDir("CrashHandler") + "/IsCrashHandlerCrashHappen.dat";
|
||||
LogUtils.d(TAG, String.format("_CrashCountFilePath %s", _CrashCountFilePath));
|
||||
init(app, null);
|
||||
}
|
||||
/**
|
||||
* 初始化崩溃处理器(默认存储路径)
|
||||
* 调用重载方法,崩溃日志默认存储在应用外部私有目录的 crash 文件夹下
|
||||
* @param app 全局 Application 实例(用于获取存储目录、包信息等)
|
||||
*/
|
||||
public static void init(Application app) {
|
||||
// 初始化崩溃保险丝状态文件路径(外部存储/CrashHandler/IsCrashHandlerCrashHappen.dat)
|
||||
_CrashCountFilePath = app.getExternalFilesDir("CrashHandler") + "/IsCrashHandlerCrashHappen.dat";
|
||||
LogUtils.d(TAG, String.format("_CrashCountFilePath %s", _CrashCountFilePath));
|
||||
// 调用带目录参数的初始化方法,传入 null 使用默认路径
|
||||
init(app, null);
|
||||
}
|
||||
|
||||
public static void init(final Application app, final String crashDir) {
|
||||
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(){
|
||||
/**
|
||||
* 初始化崩溃处理器(指定日志存储目录)
|
||||
* 替换系统默认的未捕获异常处理器,自定义崩溃处理逻辑
|
||||
* @param app 全局 Application 实例
|
||||
* @param crashDir 崩溃日志存储目录(null 则使用默认路径)
|
||||
*/
|
||||
public static void init(final Application app, final String crashDir) {
|
||||
// 设置自定义未捕获异常处理器
|
||||
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
try {
|
||||
// 尝试处理崩溃(捕获内部异常,避免 CrashHandler 自身崩溃)
|
||||
tryUncaughtException(thread, throwable);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
// 处理失败时,交给系统默认处理器兜底
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
|
||||
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uncaughtException(Thread thread, Throwable throwable) {
|
||||
try {
|
||||
tryUncaughtException(thread, throwable);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
|
||||
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 实际处理崩溃的核心方法
|
||||
* 1. 熔断保险丝(记录崩溃次数);2. 收集崩溃信息;3. 写入日志文件;4. 启动崩溃报告页面
|
||||
* @param thread 发生崩溃的线程
|
||||
* @param throwable 崩溃异常对象(包含堆栈信息)
|
||||
*/
|
||||
private void tryUncaughtException(Thread thread, Throwable throwable) {
|
||||
// 触发崩溃保险丝(每次崩溃熔断一次,降低防护等级)
|
||||
AppCrashSafetyWire.getInstance().burnSafetyWire();
|
||||
|
||||
private void tryUncaughtException(Thread thread, Throwable throwable) {
|
||||
// 每到这里就燃烧一次保险丝
|
||||
AppCrashSafetyWire.getInstance().burnSafetyWire();
|
||||
// 格式化崩溃发生时间(用于日志文件名和内容)
|
||||
final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss", Locale.getDefault()).format(new Date());
|
||||
// 创建崩溃日志文件(默认路径:外部存储/crash/[时间].txt)
|
||||
File crashFile = new File(
|
||||
TextUtils.isEmpty(crashDir) ? new File(app.getExternalFilesDir(null), "crash") : new File(crashDir),
|
||||
"crash_" + time + ".txt"
|
||||
);
|
||||
|
||||
final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss", Locale.getDefault()).format(new Date());
|
||||
File crashFile = new File(TextUtils.isEmpty(crashDir) ? new File(app.getExternalFilesDir(null), "crash")
|
||||
: new File(crashDir), "crash_" + time + ".txt");
|
||||
// 获取应用版本信息(版本名、版本号)
|
||||
String versionName = "unknown";
|
||||
long versionCode = 0;
|
||||
try {
|
||||
PackageInfo packageInfo = app.getPackageManager().getPackageInfo(app.getPackageName(), 0);
|
||||
versionName = packageInfo.versionName;
|
||||
// 适配 Android 9.0+(API 28)的版本号获取方式
|
||||
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() : packageInfo.versionCode;
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
|
||||
String versionName = "unknown";
|
||||
long versionCode = 0;
|
||||
try {
|
||||
PackageInfo packageInfo = app.getPackageManager().getPackageInfo(app.getPackageName(), 0);
|
||||
versionName = packageInfo.versionName;
|
||||
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode()
|
||||
: packageInfo.versionCode;
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
// 将异常堆栈信息转换为字符串
|
||||
String fullStackTrace;
|
||||
{
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
throwable.printStackTrace(pw); // 将异常堆栈写入 PrintWriter
|
||||
fullStackTrace = sw.toString();
|
||||
pw.close();
|
||||
}
|
||||
|
||||
String fullStackTrace; {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
throwable.printStackTrace(pw);
|
||||
fullStackTrace = sw.toString();
|
||||
pw.close();
|
||||
}
|
||||
// 拼接崩溃信息(设备信息 + 应用信息 + 堆栈信息)
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("************* Crash Head ****************\n");
|
||||
sb.append("Time Of Crash : ").append(time).append("\n");
|
||||
sb.append("Device Manufacturer : ").append(Build.MANUFACTURER).append("\n"); // 设备厂商
|
||||
sb.append("Device Model : ").append(Build.MODEL).append("\n"); // 设备型号
|
||||
sb.append("Android Version : ").append(Build.VERSION.RELEASE).append("\n"); // Android 版本
|
||||
sb.append("Android SDK : ").append(Build.VERSION.SDK_INT).append("\n"); // SDK 版本
|
||||
sb.append("App VersionName : ").append(versionName).append("\n"); // 应用版本名
|
||||
sb.append("App VersionCode : ").append(versionCode).append("\n"); // 应用版本号
|
||||
sb.append("************* Crash Head ****************\n");
|
||||
sb.append("\n").append(fullStackTrace); // 拼接异常堆栈
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("************* Crash Head ****************\n");
|
||||
sb.append("Time Of Crash : ").append(time).append("\n");
|
||||
sb.append("Device Manufacturer : ").append(Build.MANUFACTURER).append("\n");
|
||||
sb.append("Device Model : ").append(Build.MODEL).append("\n");
|
||||
sb.append("Android Version : ").append(Build.VERSION.RELEASE).append("\n");
|
||||
sb.append("Android SDK : ").append(Build.VERSION.SDK_INT).append("\n");
|
||||
sb.append("App VersionName : ").append(versionName).append("\n");
|
||||
sb.append("App VersionCode : ").append(versionCode).append("\n");
|
||||
sb.append("************* Crash Head ****************\n");
|
||||
sb.append("\n").append(fullStackTrace);
|
||||
final String errorLog = sb.toString();
|
||||
|
||||
String errorLog = sb.toString();
|
||||
// 将崩溃日志写入文件(忽略写入失败)
|
||||
try {
|
||||
writeFile(crashFile, errorLog);
|
||||
} catch (IOException ignored) {}
|
||||
|
||||
try {
|
||||
writeFile(crashFile, errorLog);
|
||||
} catch (IOException ignored) {}
|
||||
// 启动崩溃报告页面(标签用于代码块折叠)
|
||||
gotoCrashActiviy: {
|
||||
Intent intent = new Intent();
|
||||
LogUtils.d(TAG, "gotoCrashActiviy: ");
|
||||
|
||||
gotoCrashActiviy: {
|
||||
Intent intent = new Intent();
|
||||
LogUtils.d(TAG, "gotoCrashActiviy: ");
|
||||
if (AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()) {
|
||||
LogUtils.d(TAG, "gotoCrashActiviy: isAppCrashSafetyWireOK");
|
||||
intent.setClass(app, GlobalCrashActivity.class);
|
||||
intent.putExtra(EXTRA_CRASH_INFO, errorLog);
|
||||
// 如果发生了 CrashHandler 内部崩溃, 就调用基础的应用崩溃显示类
|
||||
// intent.setClass(app, GlobalCrashActiviy.class);
|
||||
// intent.putExtra(GlobalCrashActiviy.EXTRA_CRASH_INFO, errorLog);
|
||||
} else {
|
||||
LogUtils.d(TAG, "gotoCrashActiviy: else");
|
||||
// 正常状态调用进阶的应用崩溃显示页
|
||||
intent.setClass(app, CrashActivity.class);
|
||||
intent.putExtra(EXTRA_CRASH_INFO, errorLog);
|
||||
}
|
||||
// 根据保险丝状态选择启动的崩溃页面
|
||||
if (AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()) {
|
||||
LogUtils.d(TAG, "gotoCrashActiviy: isAppCrashSafetyWireOK");
|
||||
// 保险丝正常:启动自定义样式的崩溃报告页面(GlobalCrashActivity)
|
||||
intent.setClass(app, GlobalCrashActivity.class);
|
||||
intent.putExtra(EXTRA_CRASH_INFO, errorLog); // 传递崩溃日志
|
||||
} else {
|
||||
LogUtils.d(TAG, "gotoCrashActiviy: else");
|
||||
// 保险丝熔断:启动基础版崩溃页面(CrashActivity,避免复杂页面再次崩溃)
|
||||
intent.setClass(app, CrashActivity.class);
|
||||
intent.putExtra(EXTRA_CRASH_INFO, errorLog);
|
||||
}
|
||||
|
||||
// 设置意图标志:清除原有任务栈,创建新任务(避免回到崩溃前页面)
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
);
|
||||
|
||||
try {
|
||||
// 启动崩溃页面,终止当前进程(确保完全重启)
|
||||
app.startActivity(intent);
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
// 未找到崩溃页面(如未在 Manifest 注册),交给系统默认处理器
|
||||
e.printStackTrace();
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
|
||||
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 其他异常,兜底处理
|
||||
e.printStackTrace();
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) {
|
||||
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// intent.setClass(app, CrashActiviy.class);
|
||||
// intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog);
|
||||
/**
|
||||
* 将字符串内容写入文件(创建父目录、覆盖写入)
|
||||
* @param file 目标文件(包含路径)
|
||||
* @param content 待写入的内容(崩溃日志)
|
||||
* @throws IOException 文件创建或写入失败时抛出
|
||||
*/
|
||||
private void writeFile(File file, String content) throws IOException {
|
||||
File parentFile = file.getParentFile();
|
||||
// 父目录不存在则创建
|
||||
if (parentFile != null && !parentFile.exists()) {
|
||||
parentFile.mkdirs();
|
||||
}
|
||||
file.createNewFile(); // 创建文件
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(content.getBytes()); // 写入内容(默认 UTF-8 编码)
|
||||
try {
|
||||
fos.close(); // 关闭流
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用崩溃保险丝内部类(单例)
|
||||
* 核心作用:限制短时间内重复崩溃,通过「熔断等级」控制崩溃页面启动策略
|
||||
* 等级范围:MINI(1)~ MAX(2),每次崩溃等级-1,熔断后启动基础版崩溃页面
|
||||
*/
|
||||
public static final class AppCrashSafetyWire {
|
||||
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
);
|
||||
/** 单例实例(volatile 保证多线程可见性) */
|
||||
private static volatile AppCrashSafetyWire _AppCrashSafetyWire;
|
||||
|
||||
try {
|
||||
app.startActivity(intent);
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
|
||||
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
|
||||
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
|
||||
}
|
||||
}
|
||||
/** 当前熔断等级(1:最低防护;2:最高防护;≤0:熔断) */
|
||||
private volatile Integer currentSafetyLevel;
|
||||
/** 最低熔断等级(1,再崩溃则熔断) */
|
||||
private static final int _MINI = 1;
|
||||
/** 最高熔断等级(2,初始状态) */
|
||||
private static final int _MAX = 2;
|
||||
|
||||
}
|
||||
/**
|
||||
* 私有构造方法(单例模式,禁止外部实例化)
|
||||
* 初始化时加载本地存储的熔断等级
|
||||
*/
|
||||
private AppCrashSafetyWire() {
|
||||
LogUtils.d(TAG, "AppCrashSafetyWire()");
|
||||
currentSafetyLevel = loadCurrentSafetyLevel();
|
||||
}
|
||||
|
||||
private void writeFile(File file, String content) throws IOException {
|
||||
File parentFile = file.getParentFile();
|
||||
if (parentFile != null && !parentFile.exists()) {
|
||||
parentFile.mkdirs();
|
||||
}
|
||||
file.createNewFile();
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(content.getBytes());
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {}
|
||||
}
|
||||
/**
|
||||
* 获取单例实例(双重检查锁定,线程安全)
|
||||
* @return AppCrashSafetyWire 单例
|
||||
*/
|
||||
public static synchronized AppCrashSafetyWire getInstance() {
|
||||
if (_AppCrashSafetyWire == null) {
|
||||
_AppCrashSafetyWire = new AppCrashSafetyWire();
|
||||
}
|
||||
return _AppCrashSafetyWire;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 设置当前熔断等级(内存中)
|
||||
* @param currentSafetyLevel 目标等级(1~2)
|
||||
*/
|
||||
public void setCurrentSafetyLevel(int currentSafetyLevel) {
|
||||
this.currentSafetyLevel = currentSafetyLevel;
|
||||
}
|
||||
|
||||
//
|
||||
// 应用崩溃保险丝
|
||||
//
|
||||
public static final class AppCrashSafetyWire {
|
||||
/**
|
||||
* 获取当前熔断等级(内存中)
|
||||
* @return 当前等级(1~2 或 null)
|
||||
*/
|
||||
public int getCurrentSafetyLevel() {
|
||||
return currentSafetyLevel;
|
||||
}
|
||||
|
||||
volatile static AppCrashSafetyWire _AppCrashSafetyWire;
|
||||
/**
|
||||
* 保存熔断等级到本地文件(持久化,重启应用生效)
|
||||
* @param currentSafetyLevel 待保存的等级
|
||||
*/
|
||||
public void saveCurrentSafetyLevel(int currentSafetyLevel) {
|
||||
LogUtils.d(TAG, "saveCurrentSafetyLevel()");
|
||||
this.currentSafetyLevel = currentSafetyLevel;
|
||||
try {
|
||||
// 序列化等级到文件(ObjectOutputStream 写入 int)
|
||||
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(_CrashCountFilePath));
|
||||
oos.writeInt(currentSafetyLevel);
|
||||
oos.flush();
|
||||
oos.close();
|
||||
LogUtils.d(TAG, String.format("saveCurrentSafetyLevel writeInt currentSafetyLevel %d", currentSafetyLevel));
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
volatile Integer currentSafetyLevel; // 熔断值,为 0 表示熔断了。
|
||||
private static final int _MINI = 1;
|
||||
private static final int _MAX = 2;
|
||||
/**
|
||||
* 从本地文件加载熔断等级(应用启动时初始化)
|
||||
* @return 加载的等级(文件不存在则初始化为 MAX(2))
|
||||
*/
|
||||
public int loadCurrentSafetyLevel() {
|
||||
LogUtils.d(TAG, "loadCurrentSafetyLevel()");
|
||||
try {
|
||||
File f = new File(_CrashCountFilePath);
|
||||
if (f.exists()) {
|
||||
// 反序列化从文件读取等级
|
||||
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(_CrashCountFilePath));
|
||||
currentSafetyLevel = ois.readInt();
|
||||
LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() readInt currentSafetyLevel %d", currentSafetyLevel));
|
||||
} else {
|
||||
// 文件不存在,初始化等级为最高(2)并保存
|
||||
currentSafetyLevel = _MAX;
|
||||
LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() currentSafetyLevel init to _MAX->%d", _MAX));
|
||||
saveCurrentSafetyLevel(currentSafetyLevel);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return currentSafetyLevel;
|
||||
}
|
||||
|
||||
AppCrashSafetyWire() {
|
||||
LogUtils.d(TAG, "AppCrashSafetyWire()");
|
||||
currentSafetyLevel = loadCurrentSafetyLevel();
|
||||
}
|
||||
/**
|
||||
* 熔断保险丝(每次崩溃调用,降低防护等级)
|
||||
* @return 熔断后是否仍在防护范围内(true:是;false:已熔断)
|
||||
*/
|
||||
boolean burnSafetyWire() {
|
||||
LogUtils.d(TAG, "burnSafetyWire()");
|
||||
// 加载当前等级
|
||||
int safeLevel = loadCurrentSafetyLevel();
|
||||
// 若在防护范围内(1~2),等级-1 并保存
|
||||
if (isSafetyWireWorking(safeLevel)) {
|
||||
LogUtils.d(TAG, "burnSafetyWire() use");
|
||||
saveCurrentSafetyLevel(safeLevel - 1);
|
||||
// 返回熔断后的状态
|
||||
return isSafetyWireWorking(safeLevel - 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static synchronized AppCrashSafetyWire getInstance() {
|
||||
if (_AppCrashSafetyWire == null) {
|
||||
_AppCrashSafetyWire = new AppCrashSafetyWire();
|
||||
}
|
||||
return _AppCrashSafetyWire;
|
||||
}
|
||||
/**
|
||||
* 检查熔断等级是否在有效范围内(1~2)
|
||||
* @param safetyLevel 待检查的等级
|
||||
* @return true:在范围内(防护有效);false:超出范围(已熔断)
|
||||
*/
|
||||
boolean isSafetyWireWorking(int safetyLevel) {
|
||||
LogUtils.d(TAG, "isSafetyWireOK()");
|
||||
LogUtils.d(TAG, String.format("SafetyLevel %d", safetyLevel));
|
||||
|
||||
public void setCurrentSafetyLevel(int currentSafetyLevel) {
|
||||
this.currentSafetyLevel = currentSafetyLevel;
|
||||
}
|
||||
if (safetyLevel >= _MINI && safetyLevel <= _MAX) {
|
||||
LogUtils.d(TAG, String.format("In Safety Level"));
|
||||
return true;
|
||||
}
|
||||
LogUtils.d(TAG, String.format("Out of Safety Level"));
|
||||
return false;
|
||||
}
|
||||
|
||||
public int getCurrentSafetyLevel() {
|
||||
return currentSafetyLevel;
|
||||
}
|
||||
/**
|
||||
* 立即恢复熔断等级到最高(2)
|
||||
* 用于重启应用后重置防护状态
|
||||
*/
|
||||
void resumeToMaximumImmediately() {
|
||||
LogUtils.d(TAG, "resumeToMaximumImmediately() call saveCurrentSafetyLevel(_MAX)");
|
||||
AppCrashSafetyWire.getInstance().saveCurrentSafetyLevel(_MAX);
|
||||
}
|
||||
|
||||
public void saveCurrentSafetyLevel(int currentSafetyLevel) {
|
||||
LogUtils.d(TAG, "saveCurrentSafetyLevel()");
|
||||
this.currentSafetyLevel = currentSafetyLevel;
|
||||
try {
|
||||
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(_CrashCountFilePath));
|
||||
oos.writeInt(currentSafetyLevel);
|
||||
oos.flush();
|
||||
oos.close();
|
||||
LogUtils.d(TAG, String.format("saveCurrentSafetyLevel writeInt currentSafetyLevel %d", currentSafetyLevel));
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 关闭防护(设置等级为最低(1))
|
||||
* 下次崩溃直接熔断
|
||||
*/
|
||||
void off() {
|
||||
LogUtils.d(TAG, "off()");
|
||||
saveCurrentSafetyLevel(_MINI);
|
||||
}
|
||||
|
||||
public int loadCurrentSafetyLevel() {
|
||||
LogUtils.d(TAG, "loadCurrentSafetyLevel()");
|
||||
try {
|
||||
File f = new File(_CrashCountFilePath);
|
||||
if (f.exists()) {
|
||||
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(_CrashCountFilePath));
|
||||
currentSafetyLevel = ois.readInt();
|
||||
LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() readInt currentSafetyLevel %d", currentSafetyLevel));
|
||||
} else {
|
||||
currentSafetyLevel = _MAX;
|
||||
LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() currentSafetyLevel init to _MAX->%d", _MAX));
|
||||
saveCurrentSafetyLevel(currentSafetyLevel);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return currentSafetyLevel;
|
||||
}
|
||||
/**
|
||||
* 检查当前保险丝是否有效(防护未熔断)
|
||||
* @return true:有效(等级 1~2);false:已熔断
|
||||
*/
|
||||
boolean isAppCrashSafetyWireOK() {
|
||||
LogUtils.d(TAG, "isAppCrashSafetyWireOK()");
|
||||
currentSafetyLevel = loadCurrentSafetyLevel();
|
||||
return isSafetyWireWorking(currentSafetyLevel);
|
||||
}
|
||||
|
||||
boolean burnSafetyWire() {
|
||||
LogUtils.d(TAG, "burnSafetyWire()");
|
||||
// 崩溃计数进入崩溃保险值
|
||||
int safeLevel = loadCurrentSafetyLevel();
|
||||
if (isSafetyWireWorking(safeLevel)) {
|
||||
// 如果保险丝未熔断, 就增加一次熔断值
|
||||
LogUtils.d(TAG, "burnSafetyWire() use");
|
||||
saveCurrentSafetyLevel(safeLevel - 1);
|
||||
return isSafetyWireWorking(safeLevel - 1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* 延迟恢复保险丝到最高等级(500ms 后)
|
||||
* 核心作用:崩溃页面启动后,若下次即将熔断,提前恢复防护等级,避免持续崩溃
|
||||
* @param context 上下文(用于获取主线程 Handler)
|
||||
*/
|
||||
void postResumeCrashSafetyWireHandler(final Context context) {
|
||||
// 主线程延迟 500ms 执行(避免页面启动时阻塞)
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, "Handler run()");
|
||||
// 检查:若当前等级-1 后超出防护范围(即将熔断),则恢复到最高等级
|
||||
if (!AppCrashSafetyWire.getInstance().isSafetyWireWorking(currentSafetyLevel - 1)) {
|
||||
AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||||
LogUtils.d(TAG, "postResumeCrashSafetyWireHandler: 恢复保险丝到最高等级");
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
boolean isSafetyWireWorking(int safetyLevel) {
|
||||
LogUtils.d(TAG, "isSafetyWireOK()");
|
||||
//safetyLevel = _MINI;
|
||||
//safetyLevel = _MINI - 1;
|
||||
//safetyLevel = _MINI + 1;
|
||||
//safetyLevel = _MAX;
|
||||
//safetyLevel = _MAX + 1;
|
||||
LogUtils.d(TAG, String.format("SafetyLevel %d", safetyLevel));
|
||||
/**
|
||||
* 基础版崩溃报告页面(保险丝熔断时启动)
|
||||
* 极简实现:仅展示崩溃日志,提供复制、重启功能,避免复杂布局导致二次崩溃
|
||||
*/
|
||||
public static final class CrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
|
||||
/** 菜单标识:复制崩溃日志 */
|
||||
private static final int MENUITEM_COPY = 0;
|
||||
/** 菜单标识:重启应用 */
|
||||
private static final int MENUITEM_RESTART = 1;
|
||||
|
||||
if (safetyLevel >= _MINI && safetyLevel <= _MAX) {
|
||||
// 如果在保险值之内
|
||||
LogUtils.d(TAG, String.format("In Safety Level"));
|
||||
return true;
|
||||
}
|
||||
LogUtils.d(TAG, String.format("Out of Safety Level"));
|
||||
return false;
|
||||
}
|
||||
/** 崩溃日志文本(从 CrashHandler 传递过来) */
|
||||
private String mLog;
|
||||
|
||||
void resumeToMaximumImmediately() {
|
||||
LogUtils.d(TAG, "resumeToMaximumImmediately() call saveCurrentSafetyLevel(_MAX)");
|
||||
AppCrashSafetyWire.getInstance().saveCurrentSafetyLevel(_MAX);
|
||||
}
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// 初始化崩溃保险丝延迟恢复机制
|
||||
AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext());
|
||||
|
||||
void off() {
|
||||
LogUtils.d(TAG, "off()");
|
||||
saveCurrentSafetyLevel(_MINI);
|
||||
}
|
||||
// 获取传递的崩溃日志
|
||||
mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO);
|
||||
// 设置系统默认主题(避免自定义主题冲突)
|
||||
setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
|
||||
|
||||
boolean isAppCrashSafetyWireOK() {
|
||||
LogUtils.d(TAG, "isAppCrashSafetyWireOK()");
|
||||
currentSafetyLevel = loadCurrentSafetyLevel();
|
||||
return isSafetyWireWorking(currentSafetyLevel);
|
||||
}
|
||||
// 动态创建布局(避免 XML 布局加载异常)
|
||||
setContentView: {
|
||||
// 垂直滚动视图(处理日志过长)
|
||||
ScrollView contentView = new ScrollView(this);
|
||||
contentView.setFillViewport(true);
|
||||
|
||||
// 调用函数以启用持续崩溃保险,从而调用 CrashHandler 内部崩溃处理窗口
|
||||
void postResumeCrashSafetyWireHandler(final Context context) {
|
||||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.d(TAG, "Handler run()");
|
||||
if (!AppCrashSafetyWire.getInstance().isSafetyWireWorking(currentSafetyLevel - 1)) {
|
||||
// 如果下一次应用崩溃时,保险丝熔断,则先恢复保险丝满能状态
|
||||
// 进程持续运行时,恢复保险丝熔断值
|
||||
//Resume to maximum
|
||||
AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||||
LogUtils.d(TAG, "postResumeCrashSafetyWireHandler");
|
||||
}
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
// 水平滚动视图(处理日志行过长)
|
||||
HorizontalScrollView hw = new HorizontalScrollView(this);
|
||||
hw.setBackgroundColor(Color.GRAY); // 背景色设为灰色
|
||||
|
||||
public static final class CrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
|
||||
private static final int MENUITEM_COPY = 0;
|
||||
private static final int MENUITEM_RESTART = 1;
|
||||
// 日志显示文本框
|
||||
TextView message = new TextView(this);
|
||||
{
|
||||
int padding = dp2px(16); // 内边距 16dp(适配不同屏幕)
|
||||
message.setPadding(padding, padding, padding, padding);
|
||||
message.setText(mLog); // 设置崩溃日志
|
||||
message.setTextColor(Color.BLACK); // 文字黑色
|
||||
message.setTextIsSelectable(true); // 支持文本选择(便于手动复制)
|
||||
}
|
||||
|
||||
private String mLog;
|
||||
// 组装布局:TextView -> HorizontalScrollView -> ScrollView
|
||||
hw.addView(message);
|
||||
contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
// 设置当前 Activity 布局
|
||||
setContentView(contentView);
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext());
|
||||
// 配置 ActionBar 标题和副标题
|
||||
getActionBar().setTitle(TITTLE);
|
||||
getActionBar().setSubtitle(GlobalApplication.class.getSimpleName() + " Error");
|
||||
}
|
||||
}
|
||||
|
||||
mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO);
|
||||
setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
|
||||
setContentView: {
|
||||
ScrollView contentView = new ScrollView(this);
|
||||
contentView.setFillViewport(true);
|
||||
/**
|
||||
* 重写返回键逻辑:点击返回键直接重启应用
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
restart();
|
||||
}
|
||||
|
||||
HorizontalScrollView hw = new HorizontalScrollView(this);
|
||||
hw.setBackgroundColor(Color.GRAY);
|
||||
TextView message = new TextView(this); {
|
||||
int padding = dp2px(16);
|
||||
message.setPadding(padding, padding, padding, padding);
|
||||
message.setText(mLog);
|
||||
message.setTextColor(Color.BLACK);
|
||||
message.setTextIsSelectable(true);
|
||||
}
|
||||
hw.addView(message);
|
||||
/**
|
||||
* 重启当前应用(与 GlobalCrashActivity 逻辑一致)
|
||||
* 清除任务栈,启动主 Activity,终止当前进程
|
||||
*/
|
||||
private void restart() {
|
||||
PackageManager pm = getPackageManager();
|
||||
// 获取应用启动意图(默认启动主 Activity)
|
||||
Intent intent = pm.getLaunchIntentForPackage(getPackageName());
|
||||
if (intent != null) {
|
||||
// 设置意图标志:清除原有任务栈,创建新任务
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
);
|
||||
startActivity(intent);
|
||||
}
|
||||
// 关闭当前页面,终止进程,确保完全重启
|
||||
finish();
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
||||
setContentView(contentView);
|
||||
getActionBar().setTitle(TITTLE);
|
||||
getActionBar().setSubtitle(GlobalApplication.class.getSimpleName() + " Error");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* dp 转 px(适配不同屏幕密度)
|
||||
* @param dpValue dp 值
|
||||
* @return 转换后的 px 值
|
||||
*/
|
||||
private int dp2px(final float dpValue) {
|
||||
final float scale = Resources.getSystem().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f); // 四舍五入确保精度
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
restart();
|
||||
}
|
||||
/**
|
||||
* 菜单点击事件回调(处理复制、重启)
|
||||
* @param item 被点击的菜单项
|
||||
* @return false:不消费事件(保持默认行为)
|
||||
*/
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case MENUITEM_COPY:
|
||||
// 复制日志到剪贴板
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case MENUITEM_RESTART:
|
||||
// 恢复保险丝到最高等级,然后重启应用
|
||||
AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||||
restart();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
PackageManager pm = getPackageManager();
|
||||
Intent intent = pm.getLaunchIntentForPackage(getPackageName());
|
||||
if (intent != null) {
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
);
|
||||
startActivity(intent);
|
||||
}
|
||||
finish();
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
private int dp2px(final float dpValue) {
|
||||
final float scale = Resources.getSystem().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case MENUITEM_COPY:
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
case MENUITEM_RESTART:
|
||||
AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||||
restart();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, MENUITEM_COPY, 0, "Copy").setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.add(0, MENUITEM_RESTART, 0, "Restart").setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 创建 ActionBar 菜单(添加复制、重启项)
|
||||
* @param menu 菜单容器
|
||||
* @return true:显示菜单
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
// 添加「复制」菜单:有空间时显示在 ActionBar,否则放入溢出菜单
|
||||
menu.add(0, MENUITEM_COPY, 0, "Copy")
|
||||
.setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
// 添加「重启」菜单:同上
|
||||
menu.add(0, MENUITEM_RESTART, 0, "Restart")
|
||||
.setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,128 +1,134 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/01/05 10:10:23
|
||||
* @Describe 全局应用类
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 19:56
|
||||
* @Describe 全局 Application 类,用于初始化应用核心组件、管理全局状态(如调试模式)
|
||||
* 需在 AndroidManifest.xml 中配置 android:name=".GlobalApplication" 使其生效
|
||||
*/
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.models.APPModel;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.MyActivityLifecycleCallbacks;
|
||||
import cc.winboll.studio.libappbase.winboll.WinBoLLActivityManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
|
||||
public class GlobalApplication extends Application {
|
||||
|
||||
/** 日志标签 */
|
||||
public static final String TAG = "GlobalApplication";
|
||||
|
||||
volatile static GlobalApplication _GlobalApplication;
|
||||
// 是否处于调试状态
|
||||
volatile static boolean isDebuging = false;
|
||||
MyActivityLifecycleCallbacks mMyActivityLifecycleCallbacks;
|
||||
/**
|
||||
* 应用调试模式标记(volatile 保证多线程可见性)
|
||||
* true:调试模式(开启日志、调试功能);false:正式模式(关闭调试相关功能)
|
||||
*/
|
||||
private static volatile boolean isDebugging = false;
|
||||
|
||||
public static void setIsDebuging(boolean isDebuging) {
|
||||
GlobalApplication.isDebuging = isDebuging;
|
||||
}
|
||||
|
||||
public static void saveDebugStatus() {
|
||||
if (_GlobalApplication != null) {
|
||||
APPModel.saveBeanToFile(getAPPModelFilePath(), new APPModel(GlobalApplication.isDebuging));
|
||||
}
|
||||
/**
|
||||
* 设置应用调试模式
|
||||
* @param debugging 调试模式状态(true/false)
|
||||
*/
|
||||
public static void setIsDebugging(boolean debugging) {
|
||||
isDebugging = debugging;
|
||||
}
|
||||
|
||||
public static GlobalApplication getInstance() {
|
||||
return _GlobalApplication;
|
||||
/**
|
||||
* 保存调试模式状态到本地文件(持久化存储,重启应用后生效)
|
||||
* @param application 全局 Application 实例(通过 getInstance() 获取更规范,此处保留原有参数)
|
||||
*/
|
||||
public static void saveDebugStatus(GlobalApplication application) {
|
||||
// 将调试状态封装为 APPModel 并保存到文件
|
||||
APPModel.saveBeanToFile(
|
||||
getAppModelFilePath(application),
|
||||
new APPModel(isDebugging)
|
||||
);
|
||||
}
|
||||
|
||||
static String getAPPModelFilePath() {
|
||||
return _GlobalApplication.getDataDir().getPath() + "/APPModel.json";
|
||||
/**
|
||||
* 获取 APPModel 配置文件的存储路径
|
||||
* 路径:应用私有数据目录 / APPModel.json(仅当前应用可访问,安全)
|
||||
* @param application 全局 Application 实例
|
||||
* @return 配置文件绝对路径
|
||||
*/
|
||||
private static String getAppModelFilePath(GlobalApplication application) {
|
||||
return application.getDataDir().getPath() + "/APPModel.json";
|
||||
}
|
||||
|
||||
public static boolean isDebuging() {
|
||||
return isDebuging;
|
||||
}
|
||||
|
||||
public static WinBoLLActivityManager getWinBoLLActivityManager() {
|
||||
return WinBoLLActivityManager.getInstance();
|
||||
/**
|
||||
* 获取当前应用调试模式状态
|
||||
* @return true:调试模式;false:正式模式
|
||||
*/
|
||||
public static boolean isDebugging() {
|
||||
return isDebugging;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用启动时初始化(仅执行一次)
|
||||
* 初始化核心框架、恢复调试状态、配置全局异常处理等
|
||||
*/
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
// 保存初始实例
|
||||
_GlobalApplication = this;
|
||||
|
||||
setIsDebuging(true);
|
||||
// 添加日志模块
|
||||
|
||||
// 初始化基础组件(日志、崩溃处理、Toast)
|
||||
initCoreComponents();
|
||||
// 恢复/初始化调试模式状态(从本地文件读取,无文件则默认关闭调试)
|
||||
restoreDebugStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化应用核心组件(日志、崩溃处理、Toast 框架)
|
||||
*/
|
||||
private void initCoreComponents() {
|
||||
// 初始化日志工具(传入 Application 上下文)
|
||||
LogUtils.init(this);
|
||||
//LogUtils.setLogLevel(LogUtils.LOG_LEVEL.Debug);
|
||||
//LogUtils.setTAGListEnable(GlobalApplication.TAG, true);
|
||||
//LogUtils.setALlTAGListEnable(true);
|
||||
//LogUtils.d(TAG, "LogUtils init");
|
||||
// 设置应用异常处理窗口
|
||||
// 初始化全局异常处理器(捕获应用崩溃信息,用于调试或上报)
|
||||
CrashHandler.init(this);
|
||||
// 初始化 Toast 框架
|
||||
// 初始化 Toast 工具(统一 Toast 样式、避免内存泄漏等)
|
||||
ToastUtils.init(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复调试模式状态(从本地配置文件读取)
|
||||
* 1. 读取本地 APPModel.json 文件
|
||||
* 2. 读取成功:使用保存的调试状态;读取失败(文件不存在):默认关闭调试并创建配置文件
|
||||
*/
|
||||
private void restoreDebugStatus() {
|
||||
// 从文件加载 APPModel 实例(存储调试状态的模型类)
|
||||
APPModel appModel = APPModel.loadBeanFromFile(
|
||||
getAppModelFilePath(this),
|
||||
APPModel.class
|
||||
);
|
||||
|
||||
// 应用保存的调试标志
|
||||
APPModel appModel = APPModel.loadBeanFromFile(getAPPModelFilePath(), APPModel.class);
|
||||
if (appModel == null) {
|
||||
setIsDebuging(false);
|
||||
saveDebugStatus();
|
||||
// 配置文件不存在,默认关闭调试模式并创建文件
|
||||
setIsDebugging(false);
|
||||
saveDebugStatus(this);
|
||||
} else {
|
||||
setIsDebuging(appModel.isDebuging());
|
||||
// 配置文件存在,使用保存的调试状态
|
||||
setIsDebugging(appModel.isDebugging());
|
||||
}
|
||||
|
||||
getWinBoLLActivityManager().setWinBoLLUI_TYPE(WinBoLLActivityManager.WinBoLLUI_TYPE.Service);
|
||||
// 注册窗口回调监听
|
||||
mMyActivityLifecycleCallbacks = new MyActivityLifecycleCallbacks();
|
||||
registerActivityLifecycleCallbacks(mMyActivityLifecycleCallbacks);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
// 注销回调(非必须,但建议释放资源)
|
||||
unregisterActivityLifecycleCallbacks(mMyActivityLifecycleCallbacks);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取应用名称(从 AndroidManifest.xml 的 android:label 读取)
|
||||
* @param context 上下文(建议传入 Application 上下文,避免内存泄漏)
|
||||
* @return 应用名称(读取失败返回 null)
|
||||
*/
|
||||
public static String getAppName(Context context) {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
try {
|
||||
// 获取应用信息(包含应用名称、图标等)
|
||||
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(
|
||||
context.getPackageName(), 0);
|
||||
context.getPackageName(), // 当前应用包名
|
||||
0 // 额外标志(0 表示默认获取基本信息)
|
||||
);
|
||||
// 从应用信息中获取应用名称(支持多语言)
|
||||
return (String) packageManager.getApplicationLabel(applicationInfo);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
} catch (NameNotFoundException e) {
|
||||
// 包名不存在(理论上不会发生,捕获异常避免崩溃)
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
//
|
||||
// @Override
|
||||
// public void helpISOSService(Intent intent) {
|
||||
// String szServiceName = intent.getStringExtra(EXTRA_SERVICE);
|
||||
// String szPackageName = intent.getStringExtra(EXTRA_PACKAGE);
|
||||
// if (szServiceName != null && !szServiceName.equals("")
|
||||
// && szPackageName != null && !szPackageName.equals("")) {
|
||||
// LogUtils.d(TAG, "szPackageName " + szPackageName);
|
||||
// LogUtils.d(TAG, "szServiceName " + szServiceName);
|
||||
//
|
||||
// // 目标服务的包名和类名
|
||||
// //String packageName = this.getPackageName();
|
||||
// //String serviceClassName = SimpleOperateSignalCenterService.class.getName();
|
||||
//
|
||||
// // 构建Intent
|
||||
// Intent intentService = new Intent();
|
||||
// intentService.setComponent(new ComponentName(szPackageName, szServiceName));
|
||||
// intentService.putExtra(ISOSService.EXTRA_ENABLE, true);
|
||||
// startService(intentService);
|
||||
// LogUtils.d(TAG, "startService(intentService)");
|
||||
// }
|
||||
// LogUtils.d(TAG, "helpISOSService");
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/11 00:14:05
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 19:58
|
||||
* @Describe 应用异常报告观察活动窗口类
|
||||
* 核心功能:应用发生未捕获崩溃时,由 CrashHandler 启动此页面,展示崩溃日志详情,
|
||||
* 并提供「复制日志」「重启应用」操作入口,便于开发者定位问题和用户恢复应用
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
@@ -18,77 +21,166 @@ import cc.winboll.studio.libappbase.R;
|
||||
|
||||
public final class GlobalCrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
|
||||
|
||||
private static final int MENUITEM_COPY = 0;
|
||||
private static final int MENUITEM_RESTART = 1;
|
||||
|
||||
GlobalCrashReportView mGlobalCrashReportView;
|
||||
String mLog;
|
||||
|
||||
|
||||
/** 日志标签(用于调试日志输出,唯一标识当前 Activity) */
|
||||
public static final String TAG = "GlobalCrashActivity";
|
||||
|
||||
/** 菜单标识:复制崩溃日志(用于区分菜单项点击事件) */
|
||||
private static final int MENU_ITEM_COPY = 0;
|
||||
/** 菜单标识:重启应用(用于区分菜单项点击事件) */
|
||||
private static final int MENU_ITEM_RESTART = 1;
|
||||
|
||||
/** 崩溃报告展示自定义视图 */
|
||||
// 负责渲染崩溃日志文本、提供 Toolbar 容器,封装了日志展示和菜单样式控制逻辑
|
||||
private GlobalCrashReportView mCrashReportView;
|
||||
|
||||
/** 崩溃日志文本内容 */
|
||||
// 从 CrashHandler 通过 Intent 传递过来,包含异常堆栈、设备信息等完整崩溃数据
|
||||
private String mCrashLog;
|
||||
|
||||
/**
|
||||
* Activity 创建时初始化(生命周期核心方法,仅执行一次)
|
||||
* @param savedInstanceState 保存的实例状态(崩溃页面无需恢复状态,此处仅作兼容)
|
||||
*/
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
CrashHandler.AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext());
|
||||
|
||||
mLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_INFO);
|
||||
//setTheme(android.R.style.Theme_Holo_Light_NoActionBar);
|
||||
//setTheme(R.style.APPBaseTheme);
|
||||
// 初始化崩溃安全防护机制
|
||||
// 作用:防止应用重启后短时间内再次崩溃,由 CrashHandler 内部实现防护逻辑
|
||||
CrashHandler.AppCrashSafetyWire.getInstance()
|
||||
.postResumeCrashSafetyWireHandler(getApplicationContext());
|
||||
|
||||
// 从 Intent 中获取崩溃日志数据(EXTRA_CRASH_INFO 为 CrashHandler 定义的常量键)
|
||||
mCrashLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_INFO);
|
||||
|
||||
// 设置当前 Activity 的布局文件(展示崩溃报告的 UI 结构)
|
||||
setContentView(R.layout.activity_globalcrash);
|
||||
mGlobalCrashReportView = findViewById(R.id.activityglobalcrashGlobalCrashReportView1);
|
||||
mGlobalCrashReportView.setReport(mLog);
|
||||
setActionBar(mGlobalCrashReportView.getToolbar());
|
||||
|
||||
getActionBar().setTitle(CrashHandler.TITTLE);
|
||||
getActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext()));
|
||||
|
||||
// 初始化崩溃报告展示视图(通过布局 ID 找到自定义 View 实例)
|
||||
mCrashReportView = findViewById(R.id.activityglobalcrashGlobalCrashReportView1);
|
||||
// 将崩溃日志设置到视图中,由自定义 View 负责排版和显示
|
||||
mCrashReportView.setReport(mCrashLog);
|
||||
|
||||
// 设置页面的 ActionBar(复用自定义 View 中的 Toolbar 作为系统 ActionBar)
|
||||
setActionBar(mCrashReportView.getToolbar());
|
||||
|
||||
// 配置 ActionBar 标题和副标题(非空判断避免空指针异常)
|
||||
if (getActionBar() != null) {
|
||||
// 设置标题:使用 CrashHandler 中定义的统一标题(如 "应用崩溃报告")
|
||||
getActionBar().setTitle(CrashHandler.TITTLE);
|
||||
// 设置副标题:显示当前应用名称(从全局 Application 工具方法获取)
|
||||
getActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写返回键点击事件
|
||||
* 逻辑:点击手机返回键时,直接重启应用(而非返回上一页,因崩溃后上一页状态可能异常)
|
||||
*/
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
restart();
|
||||
restartApp();
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
PackageManager pm = getPackageManager();
|
||||
Intent intent = pm.getLaunchIntentForPackage(getPackageName());
|
||||
if (intent != null) {
|
||||
intent.addFlags(
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
);
|
||||
startActivity(intent);
|
||||
/**
|
||||
* 重启当前应用(核心工具方法)
|
||||
* 实现逻辑:
|
||||
* 1. 获取应用的启动意图(默认启动 AndroidManifest 中配置的主 Activity)
|
||||
* 2. 设置意图标志,清除原有任务栈,避免残留异常页面
|
||||
* 3. 启动主 Activity 并终止当前进程,确保应用完全重启
|
||||
*/
|
||||
private void restartApp() {
|
||||
// 获取 PackageManager 实例(用于获取应用相关信息和意图)
|
||||
PackageManager packageManager = getPackageManager();
|
||||
// 获取应用的启动意图(参数为当前应用包名,返回主 Activity 的意图)
|
||||
Intent launchIntent = packageManager.getLaunchIntentForPackage(getPackageName());
|
||||
|
||||
if (launchIntent != null) {
|
||||
// 设置意图标志:
|
||||
// FLAG_ACTIVITY_NEW_TASK:创建新的任务栈启动 Activity
|
||||
// FLAG_ACTIVITY_CLEAR_TOP:清除目标 Activity 之上的所有 Activity
|
||||
// FLAG_ACTIVITY_CLEAR_TASK:清除当前任务栈中的所有 Activity
|
||||
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
// 启动应用主 Activity
|
||||
startActivity(launchIntent);
|
||||
}
|
||||
|
||||
// 关闭当前崩溃报告页面
|
||||
finish();
|
||||
// 终止当前应用进程(确保释放所有资源,避免内存泄漏)
|
||||
android.os.Process.killProcess(android.os.Process.myPid());
|
||||
// 强制退出虚拟机(彻底终止应用,防止残留线程继续运行)
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单项点击事件回调(实现 MenuItem.OnMenuItemClickListener 接口)
|
||||
* @param item 被点击的菜单项实例
|
||||
* @return boolean:true 表示事件已消费,不再向下传递;false 表示未消费
|
||||
*/
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
// 根据菜单项 ID 判断点击的是哪个功能
|
||||
switch (item.getItemId()) {
|
||||
case MENUITEM_COPY:
|
||||
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
|
||||
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
|
||||
case MENU_ITEM_COPY:
|
||||
// 点击「复制」菜单,执行复制崩溃日志到剪贴板
|
||||
copyCrashLogToClipboard();
|
||||
break;
|
||||
case MENUITEM_RESTART:
|
||||
case MENU_ITEM_RESTART:
|
||||
// 点击「重启」菜单:先恢复崩溃防护机制到最大等级,再重启应用
|
||||
CrashHandler.AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
|
||||
restart();
|
||||
restartApp();
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建页面顶部菜单(ActionBar 菜单)
|
||||
* @param menu 菜单容器,用于添加菜单项
|
||||
* @return boolean:true 表示显示菜单;false 表示不显示
|
||||
*/
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
menu.add(0, MENUITEM_COPY, 0, "Copy").setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
menu.add(0, MENUITEM_RESTART, 0, "Restart").setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
|
||||
// 更新菜单文字风格
|
||||
mGlobalCrashReportView.updateMenuStyle();
|
||||
// 添加「复制」菜单项:
|
||||
// 参数说明:菜单组 ID(0 表示默认组)、菜单项 ID(MENU_ITEM_COPY)、排序号(0)、菜单文本("Copy")
|
||||
// setOnMenuItemClickListener(this):绑定点击事件到当前 Activity
|
||||
// setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM):有空间时显示在 ActionBar 上,否则放入溢出菜单
|
||||
menu.add(0, MENU_ITEM_COPY, 0, "Copy")
|
||||
.setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
|
||||
// 添加「重启」菜单项(参数含义同上)
|
||||
menu.add(0, MENU_ITEM_RESTART, 0, "Restart")
|
||||
.setOnMenuItemClickListener(this)
|
||||
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
|
||||
|
||||
// 调用自定义视图的方法,更新菜单文字样式(如颜色、字体大小等,由自定义 View 内部实现)
|
||||
mCrashReportView.updateMenuStyle();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将崩溃日志复制到系统剪贴板(工具方法)
|
||||
* 功能:用户点击复制菜单后,将完整崩溃日志存入剪贴板,方便粘贴到聊天工具或文档中
|
||||
*/
|
||||
private void copyCrashLogToClipboard() {
|
||||
// 获取系统剪贴板服务(需通过 getSystemService 方法获取)
|
||||
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
|
||||
// 创建剪贴板数据:
|
||||
// 参数 1:标签(用于标识剪贴板内容来源,此处用应用包名)
|
||||
// 参数 2:实际复制的文本内容(崩溃日志)
|
||||
ClipData clipData = ClipData.newPlainText(getPackageName(), mCrashLog);
|
||||
|
||||
// 将数据设置到剪贴板(完成复制操作)
|
||||
clipboardManager.setPrimaryClip(clipData);
|
||||
|
||||
// 显示复制成功的 Toast 提示(告知用户操作结果)
|
||||
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/11 20:18:30
|
||||
* @Describe 应用崩溃报告视图
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:21
|
||||
* @Describe 全局崩溃报告视图控件
|
||||
* 用于展示应用崩溃信息,包含顶部工具栏和崩溃日志文本区域,支持自定义配色
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
@@ -20,119 +21,289 @@ import cc.winboll.studio.libappbase.R;
|
||||
|
||||
public class GlobalCrashReportView extends LinearLayout {
|
||||
|
||||
public static final String TAG = "GlobalCrashReportView";
|
||||
// 日志标签
|
||||
public static final String TAG = "GlobalCrashReportView";
|
||||
|
||||
Context mContext;
|
||||
Toolbar mToolbar;
|
||||
int colorTittle;
|
||||
int colorTittleBackground;
|
||||
int colorText;
|
||||
int colorTextBackground;
|
||||
TextView mtvReport;
|
||||
// 上下文对象
|
||||
private Context mContext;
|
||||
// 顶部工具栏(标题栏)
|
||||
private Toolbar mToolbar;
|
||||
// 标题文字颜色
|
||||
private int mTitleColor;
|
||||
// 标题栏背景颜色
|
||||
private int mTitleBackgroundColor;
|
||||
// 日志文本颜色
|
||||
private int mTextColor;
|
||||
// 日志区域背景颜色
|
||||
private int mTextBackgroundColor;
|
||||
// 崩溃日志显示文本控件
|
||||
private TextView mTvReport;
|
||||
|
||||
public GlobalCrashReportView(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
//initView();
|
||||
}
|
||||
/**
|
||||
* 构造方法:仅上下文
|
||||
* @param context 上下文
|
||||
*/
|
||||
public GlobalCrashReportView(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
// 初始化默认配置(无自定义属性)
|
||||
initDefaultConfig();
|
||||
}
|
||||
|
||||
public GlobalCrashReportView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
initView(attrs);
|
||||
}
|
||||
/**
|
||||
* 构造方法:上下文 + 自定义属性
|
||||
* @param context 上下文
|
||||
* @param attrs 自定义属性集合
|
||||
*/
|
||||
public GlobalCrashReportView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
mContext = context;
|
||||
// 初始化视图(解析自定义属性)
|
||||
initView(attrs);
|
||||
}
|
||||
|
||||
public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
//initView();
|
||||
}
|
||||
/**
|
||||
* 构造方法:上下文 + 自定义属性 + 样式属性
|
||||
* @param context 上下文
|
||||
* @param attrs 自定义属性集合
|
||||
* @param defStyleAttr 样式属性
|
||||
*/
|
||||
public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mContext = context;
|
||||
// 初始化视图(解析自定义属性)
|
||||
initView(attrs);
|
||||
}
|
||||
|
||||
public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mContext = context;
|
||||
//initView();
|
||||
}
|
||||
/**
|
||||
* 构造方法:上下文 + 自定义属性 + 样式属性 + 样式资源
|
||||
* @param context 上下文
|
||||
* @param attrs 自定义属性集合
|
||||
* @param defStyleAttr 样式属性
|
||||
* @param defStyleRes 样式资源
|
||||
*/
|
||||
public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
mContext = context;
|
||||
// 初始化视图(解析自定义属性)
|
||||
initView(attrs);
|
||||
}
|
||||
|
||||
public void setColorTittle(int colorTittle) {
|
||||
this.colorTittle = colorTittle;
|
||||
}
|
||||
/**
|
||||
* 设置标题文字颜色
|
||||
* @param titleColor 颜色值(如 Color.WHITE 或 #FFFFFF)
|
||||
*/
|
||||
public void setTitleColor(int titleColor) {
|
||||
this.mTitleColor = titleColor;
|
||||
// 实时更新工具栏标题颜色
|
||||
if (mToolbar != null) {
|
||||
mToolbar.setTitleTextColor(titleColor);
|
||||
mToolbar.setSubtitleTextColor(titleColor);
|
||||
}
|
||||
}
|
||||
|
||||
public int getColorTittle() {
|
||||
return colorTittle;
|
||||
}
|
||||
/**
|
||||
* 获取标题文字颜色
|
||||
* @return 标题文字颜色值
|
||||
*/
|
||||
public int getTitleColor() {
|
||||
return mTitleColor;
|
||||
}
|
||||
|
||||
public void setColorTittleBackground(int colorTittleBackground) {
|
||||
this.colorTittleBackground = colorTittleBackground;
|
||||
}
|
||||
/**
|
||||
* 设置标题栏背景颜色
|
||||
* @param titleBackgroundColor 颜色值(如 Color.BLACK 或 #000000)
|
||||
*/
|
||||
public void setTitleBackgroundColor(int titleBackgroundColor) {
|
||||
this.mTitleBackgroundColor = titleBackgroundColor;
|
||||
// 实时更新工具栏背景颜色
|
||||
if (mToolbar != null) {
|
||||
mToolbar.setBackgroundColor(titleBackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
public int getColorTittleBackground() {
|
||||
return colorTittleBackground;
|
||||
}
|
||||
/**
|
||||
* 获取标题栏背景颜色
|
||||
* @return 标题栏背景颜色值
|
||||
*/
|
||||
public int getTitleBackgroundColor() {
|
||||
return mTitleBackgroundColor;
|
||||
}
|
||||
|
||||
public void setColorText(int colorText) {
|
||||
this.colorText = colorText;
|
||||
}
|
||||
/**
|
||||
* 设置日志文本颜色
|
||||
* @param textColor 颜色值(如 Color.BLACK 或 #000000)
|
||||
*/
|
||||
public void setTextColor(int textColor) {
|
||||
this.mTextColor = textColor;
|
||||
// 实时更新日志文本颜色
|
||||
if (mTvReport != null) {
|
||||
mTvReport.setTextColor(textColor);
|
||||
}
|
||||
}
|
||||
|
||||
public int getColorText() {
|
||||
return colorText;
|
||||
}
|
||||
/**
|
||||
* 获取日志文本颜色
|
||||
* @return 日志文本颜色值
|
||||
*/
|
||||
public int getTextColor() {
|
||||
return mTextColor;
|
||||
}
|
||||
|
||||
public void setColorTextBackground(int colorTextBackground) {
|
||||
this.colorTextBackground = colorTextBackground;
|
||||
}
|
||||
/**
|
||||
* 设置日志区域背景颜色
|
||||
* @param textBackgroundColor 颜色值(如 Color.WHITE 或 #FFFFFF)
|
||||
*/
|
||||
public void setTextBackgroundColor(int textBackgroundColor) {
|
||||
this.mTextBackgroundColor = textBackgroundColor;
|
||||
// 实时更新日志区域和主布局背景颜色
|
||||
if (mTvReport != null) {
|
||||
mTvReport.setBackgroundColor(textBackgroundColor);
|
||||
}
|
||||
setBackgroundColor(textBackgroundColor);
|
||||
}
|
||||
|
||||
public int getColorTextBackground() {
|
||||
return colorTextBackground;
|
||||
}
|
||||
/**
|
||||
* 获取日志区域背景颜色
|
||||
* @return 日志区域背景颜色值
|
||||
*/
|
||||
public int getTextBackgroundColor() {
|
||||
return mTextBackgroundColor;
|
||||
}
|
||||
|
||||
void initView(AttributeSet attrs) {
|
||||
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.GlobalCrashActivity, R.attr.themeGlobalCrashActivity, 0);
|
||||
this.colorTittle = a.getColor(R.styleable.GlobalCrashActivity_colorTittle, Color.WHITE);
|
||||
this.colorTittleBackground = a.getColor(R.styleable.GlobalCrashActivity_colorTittleBackgound, Color.BLACK);
|
||||
this.colorText = a.getColor(R.styleable.GlobalCrashActivity_colorText, Color.BLACK);
|
||||
this.colorTextBackground = a.getColor(R.styleable.GlobalCrashActivity_colorTextBackgound, Color.WHITE);
|
||||
// 返回一个绑定资源结束的信号给资源
|
||||
a.recycle();
|
||||
/**
|
||||
* 初始化默认配置(无自定义属性时使用)
|
||||
*/
|
||||
private void initDefaultConfig() {
|
||||
// 设置默认配色
|
||||
mTitleColor = Color.WHITE;
|
||||
mTitleBackgroundColor = Color.BLACK;
|
||||
mTextColor = Color.BLACK;
|
||||
mTextBackgroundColor = Color.WHITE;
|
||||
// 加载布局
|
||||
inflateView();
|
||||
// 初始化控件样式
|
||||
initWidgetStyle();
|
||||
}
|
||||
|
||||
/*this.colorTittle = Color.WHITE;
|
||||
this.colorTittleBackground = Color.BLACK;
|
||||
this.colorText = Color.BLACK;
|
||||
this.colorTextBackground = Color.WHITE;
|
||||
*/
|
||||
|
||||
inflate(mContext, R.layout.view_globalcrashreport, this);
|
||||
/**
|
||||
* 初始化视图(解析自定义属性 + 加载布局 + 设置样式)
|
||||
* @param attrs 自定义属性集合
|
||||
*/
|
||||
private void initView(AttributeSet attrs) {
|
||||
// 解析自定义属性(关联 attrs.xml 中的 GlobalCrashActivity 样式)
|
||||
TypedArray typedArray = mContext.obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.GlobalCrashActivity,
|
||||
R.attr.themeGlobalCrashActivity,
|
||||
0
|
||||
);
|
||||
|
||||
LinearLayout llMain = findViewById(R.id.viewglobalcrashreportLinearLayout1);
|
||||
llMain.setBackgroundColor(this.colorTextBackground);
|
||||
mToolbar = findViewById(R.id.viewglobalcrashreportToolbar1);
|
||||
mToolbar.setBackgroundColor(this.colorTittleBackground);
|
||||
mToolbar.setTitleTextColor(this.colorTittle);
|
||||
mToolbar.setSubtitleTextColor(this.colorTittle);
|
||||
mtvReport = findViewById(R.id.viewglobalcrashreportTextView1);
|
||||
mtvReport.setTextColor(this.colorText);
|
||||
mtvReport.setBackgroundColor(this.colorTextBackground);
|
||||
}
|
||||
// 读取自定义属性值(无设置时使用默认值)
|
||||
mTitleColor = typedArray.getColor(
|
||||
R.styleable.GlobalCrashActivity_colorTittle,
|
||||
Color.WHITE
|
||||
);
|
||||
mTitleBackgroundColor = typedArray.getColor(
|
||||
R.styleable.GlobalCrashActivity_colorTittleBackgound, // 注:原拼写错误(Backgound→Background),保持与 attrs.xml 一致
|
||||
Color.BLACK
|
||||
);
|
||||
mTextColor = typedArray.getColor(
|
||||
R.styleable.GlobalCrashActivity_colorText,
|
||||
Color.BLACK
|
||||
);
|
||||
mTextBackgroundColor = typedArray.getColor(
|
||||
R.styleable.GlobalCrashActivity_colorTextBackgound, // 注:原拼写错误,保持与 attrs.xml 一致
|
||||
Color.WHITE
|
||||
);
|
||||
|
||||
public void setReport(String report) {
|
||||
mtvReport.setText(report);
|
||||
}
|
||||
// 回收 TypedArray,避免内存泄漏
|
||||
typedArray.recycle();
|
||||
|
||||
public Toolbar getToolbar() {
|
||||
return mToolbar;
|
||||
}
|
||||
// 加载布局文件
|
||||
inflateView();
|
||||
// 初始化控件样式
|
||||
initWidgetStyle();
|
||||
}
|
||||
|
||||
//
|
||||
// 更新菜单文字风格
|
||||
//
|
||||
public void updateMenuStyle() {
|
||||
// 设置菜单文本颜色
|
||||
Menu menu = mToolbar.getMenu();
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
MenuItem item = menu.getItem(i);
|
||||
SpannableString spanString = new SpannableString(item.getTitle().toString());
|
||||
spanString.setSpan(new ForegroundColorSpan(this.colorTittle), 0, spanString.length(), 0);
|
||||
item.setTitle(spanString);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 加载布局文件
|
||||
*/
|
||||
private void inflateView() {
|
||||
// 加载自定义布局(R.layout.view_globalcrashreport)
|
||||
inflate(mContext, R.layout.view_globalcrashreport, this);
|
||||
// 绑定控件
|
||||
mToolbar = findViewById(R.id.viewglobalcrashreportToolbar1);
|
||||
mTvReport = findViewById(R.id.viewglobalcrashreportTextView1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件样式(设置配色和基础属性)
|
||||
*/
|
||||
private void initWidgetStyle() {
|
||||
// 设置主布局背景颜色
|
||||
setBackgroundColor(mTextBackgroundColor);
|
||||
|
||||
// 配置工具栏样式
|
||||
if (mToolbar != null) {
|
||||
mToolbar.setBackgroundColor(mTitleBackgroundColor);
|
||||
mToolbar.setTitleTextColor(mTitleColor);
|
||||
mToolbar.setSubtitleTextColor(mTitleColor);
|
||||
}
|
||||
|
||||
// 配置日志文本控件样式
|
||||
if (mTvReport != null) {
|
||||
mTvReport.setTextColor(mTextColor);
|
||||
mTvReport.setBackgroundColor(mTextBackgroundColor);
|
||||
// 可选:设置日志文本换行方式(默认已换行,此处增强可读性)
|
||||
mTvReport.setSingleLine(false);
|
||||
mTvReport.setHorizontallyScrolling(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置崩溃报告内容到文本控件
|
||||
* @param report 崩溃日志字符串(通常包含异常信息、调用栈等)
|
||||
*/
|
||||
public void setReport(String report) {
|
||||
if (mTvReport != null) {
|
||||
mTvReport.setText(report);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取顶部工具栏对象(用于外部设置标题、添加菜单等)
|
||||
* @return Toolbar 实例
|
||||
*/
|
||||
public Toolbar getToolbar() {
|
||||
return mToolbar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新工具栏菜单文字颜色(与标题颜色保持一致)
|
||||
* 需在菜单加载完成后调用(如 Toolbar 加载菜单后)
|
||||
*/
|
||||
public void updateMenuStyle() {
|
||||
if (mToolbar == null) return;
|
||||
|
||||
// 获取工具栏菜单
|
||||
Menu menu = mToolbar.getMenu();
|
||||
if (menu == null || menu.size() == 0) return;
|
||||
|
||||
// 遍历所有菜单项,设置文字颜色
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
MenuItem menuItem = menu.getItem(i);
|
||||
String title = menuItem.getTitle().toString();
|
||||
// 使用 SpannableString 设置文字颜色
|
||||
SpannableString spanString = new SpannableString(title);
|
||||
spanString.setSpan(
|
||||
new ForegroundColorSpan(mTitleColor),
|
||||
0,
|
||||
spanString.length(),
|
||||
0 // Spannable.SPAN_INCLUSIVE_EXCLUSIVE(默认值,包含起始位置,不包含结束位置)
|
||||
);
|
||||
menuItem.setTitle(spanString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:26
|
||||
* @Describe 水平滚动 ListView 控件
|
||||
* 继承自 ListView,重写布局和测量逻辑,实现子项水平排列和滚动,替代默认垂直布局
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Scroller;
|
||||
|
||||
public class HorizontalListView extends ListView {
|
||||
/** 日志标签,用于当前控件的日志输出标识 */
|
||||
public static final String TAG = "HorizontalListView";
|
||||
|
||||
/** 子项垂直偏移量(用于调整子项在垂直方向的位置,默认 0) */
|
||||
private int mVerticalOffset = 0;
|
||||
/** 平滑滚动控制器(用于实现水平方向的平滑滚动动画) */
|
||||
private Scroller mScroller;
|
||||
/** 所有子项总宽度(包含内边距),用于计算滚动范围 */
|
||||
private int mTotalWidth;
|
||||
|
||||
/**
|
||||
* 构造方法:仅上下文
|
||||
* @param context 上下文(Activity/Fragment)
|
||||
*/
|
||||
public HorizontalListView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法:上下文 + 自定义属性
|
||||
* @param context 上下文
|
||||
* @param attrs 自定义属性集合(如布局文件中设置的属性)
|
||||
*/
|
||||
public HorizontalListView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造方法:上下文 + 自定义属性 + 样式属性
|
||||
* @param context 上下文
|
||||
* @param attrs 自定义属性集合
|
||||
* @param defStyle 样式属性(如系统默认样式)
|
||||
*/
|
||||
public HorizontalListView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化控件配置
|
||||
* 初始化滚动控制器,设置滚动条显示状态
|
||||
*/
|
||||
private void init() {
|
||||
// 初始化平滑滚动器(上下文为当前控件所在上下文)
|
||||
mScroller = new Scroller(getContext());
|
||||
// 启用水平滚动条(默认显示)
|
||||
setHorizontalScrollBarEnabled(true);
|
||||
// 禁用垂直滚动条(水平列表无需垂直滚动)
|
||||
setVerticalScrollBarEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置子项垂直偏移量
|
||||
* 用于整体调整所有子项在垂直方向的位置(如居中、偏移)
|
||||
* @param verticalOffset 垂直偏移像素值(正数向下偏移,负数向上偏移)
|
||||
*/
|
||||
public void setVerticalOffset(int verticalOffset) {
|
||||
this.mVerticalOffset = verticalOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写布局方法:实现子项水平排列
|
||||
* 遍历所有子项,按水平方向依次布局(左对齐,叠加排列)
|
||||
* @param changed 布局是否发生变化(true:首次布局或尺寸变化;false:重绘)
|
||||
* @param l 控件左边界坐标
|
||||
* @param t 控件上边界坐标
|
||||
* @param r 控件右边界坐标
|
||||
* @param b 控件下边界坐标
|
||||
*/
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
super.onLayout(changed, l, t, r, b); // 执行父类布局逻辑(确保基础配置生效)
|
||||
|
||||
int childCount = getChildCount(); // 获取当前可见子项数量
|
||||
int left = getPaddingLeft(); // 子项起始左坐标(包含控件左内边距)
|
||||
// 控件可用高度(总高度 - 上下内边距)
|
||||
int viewHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
|
||||
mTotalWidth = left; // 初始化总宽度为左内边距
|
||||
|
||||
// 遍历子项,水平排列
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
View child = getChildAt(i); // 获取当前子项
|
||||
int childWidth = child.getMeasuredWidth(); // 子项测量宽度
|
||||
int childHeight = child.getMeasuredHeight(); // 子项测量高度
|
||||
|
||||
// 布局子项:水平方向从 left 开始,垂直方向偏移 mVerticalOffset
|
||||
child.layout(
|
||||
left, // 子项左边界
|
||||
mVerticalOffset, // 子项上边界(带垂直偏移)
|
||||
left + childWidth, // 子项右边界(左 + 宽度)
|
||||
mVerticalOffset + childHeight // 子项下边界(偏移 + 高度)
|
||||
);
|
||||
|
||||
left += childWidth; // 更新下一个子项的起始左坐标
|
||||
}
|
||||
|
||||
// 计算总宽度(所有子项宽度 + 左右内边距)
|
||||
mTotalWidth = left + getPaddingRight();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写测量方法:设置控件测量规则
|
||||
* 水平方向:允许无限宽度(适应所有子项总宽度);垂直方向:自适应内容高度
|
||||
* @param widthMeasureSpec 父控件传递的宽度测量规格
|
||||
* @param heightMeasureSpec 父控件传递的高度测量规格
|
||||
*/
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
// 重写宽度测量规则:最大值(Integer.MAX_VALUE >> 2 避免溢出),自适应内容
|
||||
int newWidthMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
|
||||
// 重写高度测量规则:同上,自适应子项高度
|
||||
int newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
|
||||
|
||||
// 执行父类测量逻辑(使用重写后的测量规格)
|
||||
super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写滚动计算方法:实现平滑滚动
|
||||
* 配合 Scroller 实现水平方向的平滑滚动动画(需在滚动时调用 invalidate() 触发)
|
||||
*/
|
||||
@Override
|
||||
public void computeScroll() {
|
||||
// 判断滚动是否正在进行(Scroller 计算当前滚动位置)
|
||||
if (mScroller.computeScrollOffset()) {
|
||||
// 滚动到当前计算的位置(x 轴水平滚动,y 轴固定 0)
|
||||
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
|
||||
// 触发重绘,持续更新滚动状态
|
||||
postInvalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 平滑滚动到指定坐标
|
||||
* 基于 Scroller 实现水平方向的平滑滚动(300ms 动画时长)
|
||||
* @param x 目标 x 轴坐标(水平滚动位置)
|
||||
* @param y 目标 y 轴坐标(固定 0,无需垂直滚动)
|
||||
*/
|
||||
public void smoothScrollTo(int x, int y) {
|
||||
// 计算滚动距离(目标坐标 - 当前滚动坐标)
|
||||
int dx = x - getScrollX();
|
||||
int dy = y - getScrollY();
|
||||
|
||||
// 启动平滑滚动:起始坐标(当前滚动位置)、滚动距离、动画时长(300ms)
|
||||
mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 300);
|
||||
// 触发重绘,启动滚动动画
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算水平滚动总范围(用于滚动条显示比例)
|
||||
* @return 滚动总宽度(所有子项总宽度 + 内边距)
|
||||
*/
|
||||
@Override
|
||||
public int computeHorizontalScrollRange() {
|
||||
return mTotalWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前水平滚动偏移量(用于滚动条位置)
|
||||
* @return 当前 x 轴滚动坐标
|
||||
*/
|
||||
@Override
|
||||
public int computeHorizontalScrollOffset() {
|
||||
return getScrollX();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算水平滚动可视范围(用于滚动条大小)
|
||||
* @return 控件可见宽度(当前显示区域宽度)
|
||||
*/
|
||||
@Override
|
||||
public int computeHorizontalScrollExtent() {
|
||||
return getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* 滚动到指定位置的子项(水平方向)
|
||||
* 定位目标子项,计算滚动坐标,执行平滑滚动
|
||||
* @param position 子项索引(从 0 开始,仅当前可见子项有效)
|
||||
*/
|
||||
public void scrollToItem(int position) {
|
||||
// 校验索引有效性(避免数组越界)
|
||||
if (position < 0 || position >= getChildCount()) {
|
||||
LogUtils.d(TAG, "无效的子项索引: " + position);
|
||||
return;
|
||||
}
|
||||
|
||||
View targetView = getChildAt(position); // 获取目标子项
|
||||
int targetLeft = targetView.getLeft(); // 目标子项左边界坐标
|
||||
// 计算目标滚动坐标(子项左边界 - 控件左内边距,确保子项左对齐显示)
|
||||
int scrollX = targetLeft - getPaddingLeft();
|
||||
|
||||
// 修正滚动范围(避免超出总宽度或小于 0)
|
||||
int maxScrollX = mTotalWidth - getWidth(); // 最大滚动坐标(总宽度 - 控件宽度)
|
||||
scrollX = Math.max(0, Math.min(scrollX, maxScrollX));
|
||||
|
||||
// 强制重新布局和绘制(确保子项位置正确)
|
||||
requestLayout();
|
||||
invalidateViews();
|
||||
// 平滑滚动到目标坐标
|
||||
smoothScrollTo(scrollX, 0);
|
||||
|
||||
// 打印滚动日志(调试用)
|
||||
LogUtils.d(TAG, String.format(
|
||||
"滚动到子项索引: %d, 目标滚动X: %d, 总滚动范围: %d",
|
||||
position, scrollX, computeHorizontalScrollRange()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置滚动到起始位置(最左侧)
|
||||
* 强制重新布局后,平滑滚动到 x=0 坐标
|
||||
*/
|
||||
public void resetScrollToStart() {
|
||||
// 强制重新布局和绘制(确保滚动位置准确)
|
||||
requestLayout();
|
||||
invalidateViews();
|
||||
// 平滑滚动到最左侧(x=0,y=0)
|
||||
smoothScrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:29
|
||||
* @Describe 应用日志展示 Activity
|
||||
* 用于单独启动窗口展示应用运行日志,依赖 LogView 控件实现日志加载与显示
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
|
||||
public class LogActivity extends Activity {
|
||||
|
||||
/** 日志标签,用于当前 Activity 的日志输出标识 */
|
||||
public static final String TAG = "LogActivity";
|
||||
|
||||
/** 日志展示控件(用于加载和显示应用日志) */
|
||||
private LogView mLogView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// 设置布局文件(包含 LogView 控件)
|
||||
setContentView(R.layout.activity_log);
|
||||
|
||||
// 绑定布局中的 LogView 控件
|
||||
mLogView = findViewById(R.id.logview);
|
||||
// 启动 LogView 日志加载(如实时刷新日志内容)
|
||||
mLogView.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// 恢复 Activity 时重新启动 LogView(确保日志持续更新)
|
||||
mLogView.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动日志 Activity 的静态方法(外部调用入口)
|
||||
* 配置 Intent 标志,以多任务/分屏模式启动,避免与主应用任务栈冲突
|
||||
* @param context 上下文(Activity/Fragment),用于启动 Activity
|
||||
*/
|
||||
public static void startLogActivity(Context context) {
|
||||
// 创建启动当前 Activity 的 Intent
|
||||
Intent intent = new Intent(context, LogActivity.class);
|
||||
|
||||
// 添加 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);
|
||||
|
||||
// 启动 Activity
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2024/08/12 13:44:06
|
||||
* @Describe LogUtils
|
||||
* @Describe 应用日志类
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:36
|
||||
* @Describe WinBoLl 应用日志管理工具类(单例逻辑)
|
||||
* 核心功能:日志分级控制、日志文件读写、TAG 过滤配置、应用内所有 TAG 自动扫描
|
||||
* 支持 Debug/Release 模式区分存储路径,日志持久化与清理
|
||||
*/
|
||||
import android.content.Context;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
@@ -28,353 +29,511 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
public class LogUtils {
|
||||
|
||||
/** 当前工具类的日志 TAG */
|
||||
public static final String TAG = "LogUtils";
|
||||
|
||||
/**
|
||||
* 日志级别枚举(从低到高:关闭→错误→警告→信息→调试→详细)
|
||||
* 级别越高,输出的日志越详细
|
||||
*/
|
||||
public static enum LOG_LEVEL { Off, Error, Warn, Info, Debug, Verbose }
|
||||
|
||||
static volatile boolean _IsInited = false;
|
||||
static Context _mContext;
|
||||
// 日志显示时间格式
|
||||
static SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmss_SSS]", Locale.getDefault());
|
||||
// 应用日志文件夹
|
||||
static File _mfLogCacheDir;
|
||||
static File _mfLogDataDir;
|
||||
// 应用日志文件
|
||||
static File _mfLogCatchFile;
|
||||
static File _mfLogUtilsBeanFile;
|
||||
static LogUtilsBean _mLogUtilsBean;
|
||||
public static Map<String, Boolean> mapTAGList = new HashMap<String, Boolean>();
|
||||
/** 是否初始化完成标志(volatile 保证多线程可见性) */
|
||||
private static volatile boolean sIsInited = false;
|
||||
/** 全局上下文(用于获取存储路径、包信息) */
|
||||
private static Context sContext;
|
||||
/** 日志时间格式化工具(格式:[yyyyMMdd_HHmmss_SSS],精确到毫秒) */
|
||||
private static SimpleDateFormat sSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmss_SSS]", Locale.getDefault());
|
||||
/** 日志缓存文件夹(Debug 模式下存储在外部缓存,Release 存储在内部缓存) */
|
||||
private static File sLogCacheDir;
|
||||
/** 日志配置文件夹(存储 TAG 配置文件) */
|
||||
private static File sLogDataDir;
|
||||
/** 日志存储文件(所有日志写入此文件) */
|
||||
private static File sLogFile;
|
||||
/** 日志配置文件(存储日志级别、TAG 启用状态等配置) */
|
||||
private static File sLogConfigFile;
|
||||
/** 日志配置实体类(封装日志级别等配置) */
|
||||
private static LogUtilsBean sLogConfigBean;
|
||||
/** TAG 过滤映射表(key:TAG 名称;value:是否启用该 TAG 的日志输出) */
|
||||
public static Map<String, Boolean> sTagEnableMap = new HashMap<>();
|
||||
|
||||
//
|
||||
// 初始化函数
|
||||
//
|
||||
/**
|
||||
* 初始化日志工具(默认日志级别:Off,不输出日志)
|
||||
* @param context 全局上下文(建议传入 Application 实例)
|
||||
*/
|
||||
public static void init(Context context) {
|
||||
_mContext = context;
|
||||
sContext = context;
|
||||
init(context, LOG_LEVEL.Off);
|
||||
}
|
||||
|
||||
//
|
||||
// 初始化函数
|
||||
//
|
||||
/**
|
||||
* 初始化日志工具(指定日志级别)
|
||||
* 1. 根据 Debug/Release 模式初始化日志存储路径;
|
||||
* 2. 加载日志配置文件;
|
||||
* 3. 扫描应用内所有类的 TAG 并初始化过滤映射表;
|
||||
* 4. 标记初始化完成。
|
||||
* @param context 全局上下文
|
||||
* @param logLevel 初始日志级别
|
||||
*/
|
||||
public static void init(Context context, LOG_LEVEL logLevel) {
|
||||
if (GlobalApplication.isDebuging()) {
|
||||
// 初始化日志缓存文件路径
|
||||
_mfLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG);
|
||||
if (!_mfLogCacheDir.exists()) {
|
||||
_mfLogCacheDir.mkdirs();
|
||||
}
|
||||
_mfLogCatchFile = new File(_mfLogCacheDir, "log.txt");
|
||||
|
||||
// 初始化日志配置文件路径
|
||||
_mfLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG);
|
||||
if (!_mfLogDataDir.exists()) {
|
||||
_mfLogDataDir.mkdirs();
|
||||
}
|
||||
_mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json");
|
||||
sContext = context;
|
||||
// 根据 Debug 模式选择存储路径(外部/内部存储)
|
||||
if (GlobalApplication.isDebugging()) {
|
||||
// Debug 模式:存储在外部缓存目录(可通过文件管理器查看)
|
||||
sLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG);
|
||||
sLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG);
|
||||
} else {
|
||||
// 初始化日志缓存文件路径
|
||||
_mfLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG);
|
||||
if (!_mfLogCacheDir.exists()) {
|
||||
_mfLogCacheDir.mkdirs();
|
||||
// Release 模式:存储在内部缓存目录(仅应用自身可访问)
|
||||
sLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG);
|
||||
sLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG);
|
||||
}
|
||||
|
||||
// 创建日志文件夹(不存在则创建)
|
||||
createDirIfNotExists(sLogCacheDir);
|
||||
createDirIfNotExists(sLogDataDir);
|
||||
|
||||
// 初始化日志文件和配置文件路径
|
||||
sLogFile = new File(sLogCacheDir, "log.txt");
|
||||
sLogConfigFile = new File(sLogDataDir, TAG + ".json");
|
||||
|
||||
// 加载日志配置(从文件读取,读取失败则创建默认配置)
|
||||
sLogConfigBean = LogUtilsBean.loadBeanFromFile(sLogConfigFile.getPath(), LogUtilsBean.class);
|
||||
if (sLogConfigBean == null) {
|
||||
sLogConfigBean = new LogUtilsBean();
|
||||
sLogConfigBean.setLogLevel(logLevel);
|
||||
// 保存默认配置到文件
|
||||
sLogConfigBean.saveBeanToFile(sLogConfigFile.getPath(), sLogConfigBean);
|
||||
}
|
||||
|
||||
// 扫描应用内所有类的 TAG 并添加到过滤映射表
|
||||
scanAllClassTags();
|
||||
// 加载已保存的 TAG 启用状态配置
|
||||
loadTagEnableSettings();
|
||||
// 标记初始化完成
|
||||
sIsInited = true;
|
||||
|
||||
// 打印初始化日志(调试用)
|
||||
d(TAG, String.format("TAG 过滤映射表初始化完成:%s", sTagEnableMap.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TAG 过滤映射表(外部可通过此方法获取所有 TAG 及其启用状态)
|
||||
* @return TAG 名称与启用状态的映射
|
||||
*/
|
||||
public static Map<String, Boolean> getTagEnableMap() {
|
||||
return sTagEnableMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载已保存的 TAG 启用状态配置
|
||||
* 从 LogUtilsClassTAGBean 列表中读取每个 TAG 的启用状态,更新到映射表
|
||||
*/
|
||||
private static void loadTagEnableSettings() {
|
||||
ArrayList<LogUtilsClassTAGBean> tagSettingList = new ArrayList<>();
|
||||
// 从文件加载 TAG 配置列表
|
||||
LogUtilsClassTAGBean.loadBeanList(sContext, tagSettingList, LogUtilsClassTAGBean.class);
|
||||
|
||||
// 遍历配置列表,更新 TAG 启用状态
|
||||
for (LogUtilsClassTAGBean tagSetting : tagSettingList) {
|
||||
String tag = tagSetting.getTag();
|
||||
boolean isEnable = tagSetting.getEnable();
|
||||
// 仅更新已存在的 TAG(避免无效配置)
|
||||
if (sTagEnableMap.containsKey(tag)) {
|
||||
sTagEnableMap.put(tag, isEnable);
|
||||
}
|
||||
_mfLogCatchFile = new File(_mfLogCacheDir, "log.txt");
|
||||
|
||||
// 初始化日志配置文件路径
|
||||
_mfLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG);
|
||||
if (!_mfLogDataDir.exists()) {
|
||||
_mfLogDataDir.mkdirs();
|
||||
}
|
||||
_mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json");
|
||||
}
|
||||
|
||||
// Toast.makeText(context,
|
||||
// "_mfLogUtilsBeanFile : " + _mfLogUtilsBeanFile
|
||||
// + "\n_mfLogCatchFile : " + _mfLogCatchFile,
|
||||
// Toast.LENGTH_SHORT).show();
|
||||
//
|
||||
_mLogUtilsBean = LogUtilsBean.loadBeanFromFile(_mfLogUtilsBeanFile.getPath(), LogUtilsBean.class);
|
||||
if (_mLogUtilsBean == null) {
|
||||
_mLogUtilsBean = new LogUtilsBean();
|
||||
_mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean);
|
||||
}
|
||||
|
||||
// 加载当前应用下的所有类的 TAG
|
||||
addClassTAGList();
|
||||
loadTAGBeanSettings();
|
||||
_IsInited = true;
|
||||
LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString()));
|
||||
}
|
||||
|
||||
public static Map<String, Boolean> getMapTAGList() {
|
||||
return mapTAGList;
|
||||
}
|
||||
|
||||
static void loadTAGBeanSettings() {
|
||||
ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>();
|
||||
LogUtilsClassTAGBean.loadBeanList(_mContext, list, LogUtilsClassTAGBean.class);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
LogUtilsClassTAGBean beanSetting = list.get(i);
|
||||
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
|
||||
if (entry.getKey().equals(beanSetting.getTag())) {
|
||||
entry.setValue(beanSetting.getEnable());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static void saveTAGBeanSettings() {
|
||||
ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>();
|
||||
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
|
||||
list.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue()));
|
||||
/**
|
||||
* 保存当前 TAG 启用状态配置到文件
|
||||
* 将映射表中的 TAG 及其启用状态转换为 LogUtilsClassTAGBean 列表,持久化到文件
|
||||
*/
|
||||
private static void saveTagEnableSettings() {
|
||||
ArrayList<LogUtilsClassTAGBean> tagSettingList = new ArrayList<>();
|
||||
// 遍历映射表,构建配置列表
|
||||
for (Map.Entry<String, Boolean> entry : sTagEnableMap.entrySet()) {
|
||||
tagSettingList.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
LogUtilsClassTAGBean.saveBeanList(_mContext, list, LogUtilsClassTAGBean.class);
|
||||
// 保存配置列表到文件
|
||||
LogUtilsClassTAGBean.saveBeanList(sContext, tagSettingList, LogUtilsClassTAGBean.class);
|
||||
}
|
||||
|
||||
static void addClassTAGList() {
|
||||
//ClassLoader classLoader = getClass().getClassLoader();
|
||||
/**
|
||||
* 扫描应用内所有类的 TAG 并添加到过滤映射表
|
||||
* 1. 通过 DexFile 读取 APK 中所有类;
|
||||
* 2. 过滤指定包名前缀(cc.winboll.studio)的类;
|
||||
* 3. 反射获取类中 public static final String TAG 字段的值;
|
||||
* 4. 将 TAG 加入映射表,默认禁用(false)。
|
||||
*/
|
||||
private static void scanAllClassTags() {
|
||||
try {
|
||||
//String packageName = context.getPackageName();
|
||||
String packageNamePrefix = "cc.winboll.studio";
|
||||
List<String> classNames = new ArrayList<>();
|
||||
String apkPath = _mContext.getPackageCodePath();
|
||||
//Log.d("APK_PATH", "The APK path is: " + apkPath);
|
||||
LogUtils.d(TAG, String.format("apkPath : %s", apkPath));
|
||||
//String apkPath = "/data/app/" + packageName + "-";
|
||||
// 应用 APK 路径(通过上下文获取)
|
||||
String apkPath = sContext.getPackageCodePath();
|
||||
d(TAG, String.format("APK 路径:%s", apkPath));
|
||||
|
||||
//DexFile dexfile = new DexFile(apkPath + "1/base.apk");
|
||||
DexFile dexfile = new DexFile(apkPath);
|
||||
// 读取 APK 中的所有类
|
||||
DexFile dexFile = new DexFile(apkPath);
|
||||
Enumeration<String> classNames = dexFile.entries();
|
||||
|
||||
int countTemp = 0;
|
||||
Enumeration<String> entries = dexfile.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
countTemp++;
|
||||
String className = entries.nextElement();
|
||||
if (className.startsWith(packageNamePrefix)) {
|
||||
classNames.add(className);
|
||||
int totalClassCount = 0; // 总类数(调试用)
|
||||
List<String> targetClassNames = new ArrayList<>(); // 目标包名下的类名列表
|
||||
String targetPackagePrefix = "cc.winboll.studio"; // 目标包名前缀
|
||||
|
||||
// 过滤目标包名下的类
|
||||
while (classNames.hasMoreElements()) {
|
||||
totalClassCount++;
|
||||
String className = classNames.nextElement();
|
||||
if (className.startsWith(targetPackagePrefix)) {
|
||||
targetClassNames.add(className);
|
||||
}
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, String.format("countTemp : %d\nClassNames size : %d", countTemp, classNames.size()));
|
||||
// 打印扫描统计(调试用)
|
||||
d(TAG, String.format("APK 总类数:%d,目标包下类数:%d", totalClassCount, targetClassNames.size()));
|
||||
|
||||
for (String className : classNames) {
|
||||
// 反射获取每个类的 TAG 字段
|
||||
for (String className : targetClassNames) {
|
||||
try {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
// 获取类中所有声明的字段
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
for (Field field : fields) {
|
||||
if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && field.getType() == String.class && "TAG".equals(field.getName())) {
|
||||
// 过滤条件:public static String 类型,且字段名是 "TAG"
|
||||
if (Modifier.isStatic(field.getModifiers())
|
||||
&& Modifier.isPublic(field.getModifiers())
|
||||
&& field.getType() == String.class
|
||||
&& "TAG".equals(field.getName())) {
|
||||
// 获取 TAG 字段的值(静态字段,传入 null 即可)
|
||||
String tagValue = (String) field.get(null);
|
||||
//Log.d("TAG_INFO", "Class: " + className + ", TAG value: " + tagValue);
|
||||
//LogUtils.d(TAG, String.format("Tag Value : %s", tagValue));
|
||||
//mapTAGList.put(tagValue, true);
|
||||
mapTAGList.put(tagValue, false);
|
||||
// 添加到映射表,默认禁用
|
||||
sTagEnableMap.put(tagValue, false);
|
||||
}
|
||||
}
|
||||
} catch (NoClassDefFoundError | ClassNotFoundException | IllegalAccessException e) {
|
||||
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
//LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
//Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
// 捕获反射异常,避免单个类扫描失败影响整体
|
||||
d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
//Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
// 捕获 APK 读取异常
|
||||
d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
public static void setTAGListEnable(String tag, boolean isEnable) {
|
||||
Iterator<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator();
|
||||
/**
|
||||
* 设置单个 TAG 的启用状态
|
||||
* @param tag TAG 名称
|
||||
* @param isEnable 是否启用(true:输出该 TAG 的日志;false:不输出)
|
||||
*/
|
||||
public static void setTagEnable(String tag, boolean isEnable) {
|
||||
// 遍历映射表,更新目标 TAG 的状态
|
||||
Iterator<Map.Entry<String, Boolean>> iterator = sTagEnableMap.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Boolean> entry = iterator.next();
|
||||
if (tag.equals(entry.getKey())) {
|
||||
entry.setValue(isEnable);
|
||||
//System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
|
||||
break;
|
||||
}
|
||||
}
|
||||
saveTAGBeanSettings();
|
||||
LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString()));
|
||||
// 保存配置到文件(持久化)
|
||||
saveTagEnableSettings();
|
||||
d(TAG, String.format("TAG 配置更新:%s", sTagEnableMap.toString()));
|
||||
}
|
||||
|
||||
public static void setALlTAGListEnable(boolean isEnable) {
|
||||
Iterator<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator();
|
||||
/**
|
||||
* 设置所有 TAG 的启用状态(批量控制)
|
||||
* @param isEnable 是否启用(true:所有 TAG 均输出日志;false:所有 TAG 均不输出)
|
||||
*/
|
||||
public static void setAllTagsEnable(boolean isEnable) {
|
||||
// 遍历映射表,批量更新所有 TAG 的状态
|
||||
Iterator<Map.Entry<String, Boolean>> iterator = sTagEnableMap.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, Boolean> entry = iterator.next();
|
||||
entry.setValue(isEnable);
|
||||
//System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
|
||||
}
|
||||
saveTAGBeanSettings();
|
||||
LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString()));
|
||||
// 保存配置到文件(持久化)
|
||||
saveTagEnableSettings();
|
||||
d(TAG, String.format("所有 TAG 配置更新:%s", sTagEnableMap.toString()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置全局日志级别(控制日志输出的详细程度)
|
||||
* @param logLevel 目标日志级别
|
||||
*/
|
||||
public static void setLogLevel(LOG_LEVEL logLevel) {
|
||||
LogUtils._mLogUtilsBean.setLogLevel(logLevel);
|
||||
_mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean);
|
||||
if (sLogConfigBean != null) {
|
||||
sLogConfigBean.setLogLevel(logLevel);
|
||||
// 保存配置到文件(持久化)
|
||||
sLogConfigBean.saveBeanToFile(sLogConfigFile.getPath(), sLogConfigBean);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前全局日志级别
|
||||
* @return 当前日志级别
|
||||
*/
|
||||
public static LOG_LEVEL getLogLevel() {
|
||||
return LogUtils._mLogUtilsBean.getLogLevel();
|
||||
return sLogConfigBean != null ? sLogConfigBean.getLogLevel() : LOG_LEVEL.Off;
|
||||
}
|
||||
|
||||
static boolean isLoggable(String tag, LOG_LEVEL logLevel) {
|
||||
if (!_IsInited) {
|
||||
/**
|
||||
* 判断当前日志是否可输出(校验初始化状态、TAG 启用状态、日志级别)
|
||||
* @param tag 日志 TAG
|
||||
* @param logLevel 日志级别
|
||||
* @return true:可输出;false:不可输出
|
||||
*/
|
||||
private static boolean isLoggable(String tag, LOG_LEVEL logLevel) {
|
||||
// 未初始化:不输出
|
||||
if (!sIsInited) {
|
||||
return false;
|
||||
}
|
||||
if (mapTAGList.get(tag) == null
|
||||
|| !mapTAGList.get(tag)) {
|
||||
}
|
||||
// TAG 未配置或未启用:不输出
|
||||
if (sTagEnableMap.get(tag) == null || !sTagEnableMap.get(tag)) {
|
||||
return false;
|
||||
}
|
||||
if (!isInTheLevel(logLevel)) {
|
||||
}
|
||||
// 日志级别未达到:不输出
|
||||
if (!isLevelMatched(logLevel)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean isInTheLevel(LOG_LEVEL logLevel) {
|
||||
return (LogUtils._mLogUtilsBean.getLogLevel().ordinal() == logLevel.ordinal()
|
||||
|| LogUtils._mLogUtilsBean.getLogLevel().ordinal() > logLevel.ordinal());
|
||||
/**
|
||||
* 判断日志级别是否匹配(当前全局级别 >= 目标级别时可输出)
|
||||
* 例:全局级别为 Debug(4),则 Error(1)、Warn(2)、Info(3)、Debug(4)均可输出
|
||||
* @param logLevel 目标日志级别
|
||||
* @return true:级别匹配;false:不匹配
|
||||
*/
|
||||
private static boolean isLevelMatched(LOG_LEVEL logLevel) {
|
||||
if (sLogConfigBean == null) {
|
||||
return false;
|
||||
}
|
||||
// 枚举的 ordinal() 方法返回索引(Off=0,Error=1,...,Verbose=5)
|
||||
return sLogConfigBean.getLogLevel().ordinal() >= logLevel.ordinal();
|
||||
}
|
||||
|
||||
//
|
||||
// 获取应用日志文件夹
|
||||
//
|
||||
/**
|
||||
* 获取日志缓存文件夹路径(外部可通过此方法获取日志存储目录)
|
||||
* @return 日志缓存文件夹
|
||||
*/
|
||||
public static File getLogCacheDir() {
|
||||
return _mfLogCacheDir;
|
||||
return sLogCacheDir;
|
||||
}
|
||||
|
||||
//
|
||||
// 调试日志写入函数
|
||||
//
|
||||
public static void e(String szTAG, String szMessage) {
|
||||
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Error)) {
|
||||
saveLog(szTAG, LogUtils.LOG_LEVEL.Error, szMessage);
|
||||
/**
|
||||
* 输出 Error 级别日志
|
||||
* @param tag TAG 名称
|
||||
* @param message 日志内容
|
||||
*/
|
||||
public static void e(String tag, String message) {
|
||||
if (isLoggable(tag, LOG_LEVEL.Error)) {
|
||||
saveLog(tag, LOG_LEVEL.Error, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 调试日志写入函数
|
||||
//
|
||||
public static void w(String szTAG, String szMessage) {
|
||||
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Warn)) {
|
||||
saveLog(szTAG, LogUtils.LOG_LEVEL.Warn, szMessage);
|
||||
/**
|
||||
* 输出 Warn 级别日志
|
||||
* @param tag TAG 名称
|
||||
* @param message 日志内容
|
||||
*/
|
||||
public static void w(String tag, String message) {
|
||||
if (isLoggable(tag, LOG_LEVEL.Warn)) {
|
||||
saveLog(tag, LOG_LEVEL.Warn, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 调试日志写入函数
|
||||
//
|
||||
public static void i(String szTAG, String szMessage) {
|
||||
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Info)) {
|
||||
saveLog(szTAG, LogUtils.LOG_LEVEL.Info, szMessage);
|
||||
/**
|
||||
* 输出 Info 级别日志
|
||||
* @param tag TAG 名称
|
||||
* @param message 日志内容
|
||||
*/
|
||||
public static void i(String tag, String message) {
|
||||
if (isLoggable(tag, LOG_LEVEL.Info)) {
|
||||
saveLog(tag, LOG_LEVEL.Info, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 调试日志写入函数
|
||||
//
|
||||
public static void d(String szTAG, String szMessage) {
|
||||
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) {
|
||||
saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, szMessage);
|
||||
/**
|
||||
* 输出 Debug 级别日志(基础版)
|
||||
* @param tag TAG 名称
|
||||
* @param message 日志内容
|
||||
*/
|
||||
public static void d(String tag, String message) {
|
||||
if (isLoggable(tag, LOG_LEVEL.Debug)) {
|
||||
saveLog(tag, LOG_LEVEL.Debug, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 调试日志写入函数
|
||||
// 包含线程调试堆栈信息
|
||||
//
|
||||
public static void d(String szTAG, String szMessage, StackTraceElement[] listStackTrace) {
|
||||
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) {
|
||||
StringBuilder sbMessage = new StringBuilder(szMessage);
|
||||
sbMessage.append(" \nAt ");
|
||||
sbMessage.append(listStackTrace[2].getMethodName());
|
||||
sbMessage.append(" (");
|
||||
sbMessage.append(listStackTrace[2].getFileName());
|
||||
sbMessage.append(":");
|
||||
sbMessage.append(listStackTrace[2].getLineNumber());
|
||||
sbMessage.append(")");
|
||||
saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString());
|
||||
/**
|
||||
* 输出 Debug 级别日志(带调用栈信息)
|
||||
* 包含调用方法名、文件名、行号,便于调试定位
|
||||
* @param tag TAG 名称
|
||||
* @param message 日志内容
|
||||
* @param stackTrace 线程调用栈(通常传入 Thread.currentThread().getStackTrace())
|
||||
*/
|
||||
public static void d(String tag, String message, StackTraceElement[] stackTrace) {
|
||||
if (isLoggable(tag, LOG_LEVEL.Debug)) {
|
||||
StringBuilder sb = new StringBuilder(message);
|
||||
// 拼接调用栈信息(stackTrace[2] 为实际调用处)
|
||||
sb.append(" \nAt ")
|
||||
.append(stackTrace[2].getMethodName())
|
||||
.append(" (")
|
||||
.append(stackTrace[2].getFileName())
|
||||
.append(":")
|
||||
.append(stackTrace[2].getLineNumber())
|
||||
.append(")");
|
||||
saveLog(tag, LOG_LEVEL.Debug, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 调试日志写入函数
|
||||
// 包含异常信息和线程调试堆栈信息
|
||||
//
|
||||
public static void d(String szTAG, Exception e, StackTraceElement[] listStackTrace) {
|
||||
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) {
|
||||
StringBuilder sbMessage = new StringBuilder(e.getClass().toGenericString());
|
||||
sbMessage.append(" : ");
|
||||
sbMessage.append(e.getMessage());
|
||||
sbMessage.append(" \nAt ");
|
||||
sbMessage.append(listStackTrace[2].getMethodName());
|
||||
sbMessage.append(" (");
|
||||
sbMessage.append(listStackTrace[2].getFileName());
|
||||
sbMessage.append(":");
|
||||
sbMessage.append(listStackTrace[2].getLineNumber());
|
||||
sbMessage.append(")");
|
||||
saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString());
|
||||
/**
|
||||
* 输出 Debug 级别日志(带异常信息和调用栈)
|
||||
* 包含异常类型、异常信息、调用位置,便于异常定位
|
||||
* @param tag TAG 名称
|
||||
* @param e 异常对象
|
||||
* @param stackTrace 线程调用栈
|
||||
*/
|
||||
public static void d(String tag, Exception e, StackTraceElement[] stackTrace) {
|
||||
if (isLoggable(tag, LOG_LEVEL.Debug)) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
// 拼接异常信息
|
||||
sb.append(e.getClass().toGenericString())
|
||||
.append(" : ")
|
||||
.append(e.getMessage())
|
||||
// 拼接调用栈信息
|
||||
.append(" \nAt ")
|
||||
.append(stackTrace[2].getMethodName())
|
||||
.append(" (")
|
||||
.append(stackTrace[2].getFileName())
|
||||
.append(":")
|
||||
.append(stackTrace[2].getLineNumber())
|
||||
.append(")");
|
||||
saveLog(tag, LOG_LEVEL.Debug, sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 调试日志写入函数
|
||||
//
|
||||
public static void v(String szTAG, String szMessage) {
|
||||
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Verbose)) {
|
||||
saveLog(szTAG, LogUtils.LOG_LEVEL.Verbose, szMessage);
|
||||
/**
|
||||
* 输出 Verbose 级别日志(最详细级别)
|
||||
* @param tag TAG 名称
|
||||
* @param message 日志内容
|
||||
*/
|
||||
public static void v(String tag, String message) {
|
||||
if (isLoggable(tag, LOG_LEVEL.Verbose)) {
|
||||
saveLog(tag, LOG_LEVEL.Verbose, message);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 日志文件保存函数
|
||||
//
|
||||
static void saveLog(String szTAG, LogUtils.LOG_LEVEL logLevel, String szMessage) {
|
||||
/**
|
||||
* 核心日志保存方法(将日志写入文件)
|
||||
* 日志格式:[级别] [时间戳] [TAG]
|
||||
* 日志内容
|
||||
* @param tag TAG 名称
|
||||
* @param logLevel 日志级别
|
||||
* @param message 日志内容
|
||||
*/
|
||||
private static void saveLog(String tag, LOG_LEVEL logLevel, String message) {
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
BufferedWriter out = null;
|
||||
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_mfLogCatchFile, true), "UTF-8"));
|
||||
out.write("[" + logLevel + "] " + mSimpleDateFormat.format(System.currentTimeMillis()) + " [" + szTAG + "]\n" + szMessage + "\n");
|
||||
out.close();
|
||||
// 以追加模式打开日志文件,UTF-8 编码
|
||||
writer = new BufferedWriter(
|
||||
new OutputStreamWriter(
|
||||
new FileOutputStream(sLogFile, true),
|
||||
"UTF-8"
|
||||
)
|
||||
);
|
||||
// 拼接日志内容(级别 + 时间 + TAG + 消息)
|
||||
String logContent = String.format(
|
||||
"[%s] %s [%s]\n%s\n",
|
||||
logLevel.name(), // 日志级别(如 Debug)
|
||||
sSimpleDateFormat.format(System.currentTimeMillis()), // 时间戳
|
||||
tag, // TAG 名称
|
||||
message // 日志内容
|
||||
);
|
||||
// 写入文件
|
||||
writer.write(logContent);
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "IOException : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 历史日志加载函数
|
||||
//
|
||||
public static String loadLog() {
|
||||
if (_mfLogCatchFile.exists()) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
try {
|
||||
BufferedReader in = null;
|
||||
in = new BufferedReader(new InputStreamReader(new FileInputStream(_mfLogCatchFile), "UTF-8"));
|
||||
String line = "";
|
||||
while ((line = in.readLine()) != null) {
|
||||
sb.append(line);
|
||||
sb.append("\n");
|
||||
// 日志写入失败时,输出内部调试日志
|
||||
d(TAG, "日志写入失败:" + e.getMessage());
|
||||
} finally {
|
||||
// 关闭流,避免资源泄漏
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "IOException : " + e.getMessage());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
//
|
||||
// 清理日志函数
|
||||
//
|
||||
public static void cleanLog() {
|
||||
if (_mfLogCatchFile.exists()) {
|
||||
try {
|
||||
UTF8FileUtils.writeStringToFile(_mfLogCatchFile.getPath(), "");
|
||||
//LogUtils.d(TAG, "cleanLog");
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载历史日志(读取日志文件所有内容)
|
||||
* @return 历史日志字符串(空字符串表示文件不存在或读取失败)
|
||||
*/
|
||||
public static String loadLog() {
|
||||
// 日志文件不存在,返回空
|
||||
if (sLogFile == null || !sLogFile.exists()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder logContent = new StringBuilder();
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
// 以 UTF-8 编码读取日志文件
|
||||
reader = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
new FileInputStream(sLogFile),
|
||||
"UTF-8"
|
||||
)
|
||||
);
|
||||
String line;
|
||||
// 逐行读取并拼接
|
||||
while ((line = reader.readLine()) != null) {
|
||||
logContent.append(line).append("\n");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// 读取失败时,输出内部调试日志
|
||||
d(TAG, "日志读取失败:" + e.getMessage());
|
||||
} finally {
|
||||
// 关闭流,避免资源泄漏
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return logContent.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理历史日志(清空日志文件内容)
|
||||
*/
|
||||
public static void cleanLog() {
|
||||
if (sLogFile == null || !sLogFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 写入空字符串到文件,实现清空
|
||||
UTF8FileUtils.writeStringToFile(sLogFile.getPath(), "");
|
||||
} catch (IOException e) {
|
||||
// 清空失败时,输出内部调试日志(带调用栈)
|
||||
d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:创建文件夹(不存在则创建)
|
||||
* @param dir 目标文件夹
|
||||
*/
|
||||
private static void createDirIfNotExists(File dir) {
|
||||
if (dir != null && !dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
import cc.winboll.studio.libappbase.views.HorizontalListView;
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -214,7 +213,7 @@ public class LogView extends RelativeLayout {
|
||||
}
|
||||
|
||||
// 加载标签列表
|
||||
Map<String, Boolean> mapTAGList = LogUtils.getMapTAGList();
|
||||
Map<String, Boolean> mapTAGList = LogUtils.getTagEnableMap();
|
||||
boolean isAllSelect = true;
|
||||
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
|
||||
if (entry.getValue() == false) {
|
||||
@@ -238,7 +237,7 @@ public class LogView extends RelativeLayout {
|
||||
mSelectAllTAGCheckBox.setOnClickListener(new View.OnClickListener(){
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.setALlTAGListEnable(mSelectAllTAGCheckBox.isChecked());
|
||||
LogUtils.setAllTagsEnable(mSelectAllTAGCheckBox.isChecked());
|
||||
//LogUtils.setALlTAGListEnable(false);
|
||||
//mTAGListAdapter.notifyDataSetChanged();
|
||||
mTAGListAdapter.reload();
|
||||
@@ -484,7 +483,7 @@ public class LogView extends RelativeLayout {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LogUtils.setTAGListEnable(item.getTag(), ((CheckBox)v).isChecked());
|
||||
LogUtils.setTagEnable(item.getTag(), ((CheckBox)v).isChecked());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:51
|
||||
* @Describe 吐司工具类(单例模式)
|
||||
* 简化 Android 吐司的创建与展示,通过 Handler 确保主线程显示,统一管理上下文,避免内存泄漏
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class ToastUtils {
|
||||
|
||||
/** 工具类日志 TAG(用于调试输出) */
|
||||
public static final String TAG = "ToastUtils";
|
||||
/** 消息标识:显示短时长吐司 */
|
||||
private static final int MSG_SHOW_SHORT_TOAST = 1001;
|
||||
|
||||
/** 单例实例(volatile 保证多线程下可见性,避免指令重排) */
|
||||
private static volatile ToastUtils sInstance;
|
||||
/** 全局上下文(建议传入 Application 实例,避免内存泄漏) */
|
||||
private Context mContext;
|
||||
/** 主线程 Handler(用于接收并处理吐司显示消息,确保 UI 操作在主线程) */
|
||||
private static Handler _mMainHandler;
|
||||
|
||||
/**
|
||||
* 私有构造方法(禁止外部直接创建实例,确保单例)
|
||||
* 初始化主线程 Handler,绑定主线程 Looper
|
||||
*/
|
||||
private ToastUtils() {
|
||||
// 初始化 Handler,绑定主线程 Looper(确保吐司在主线程显示)
|
||||
_mMainHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
// 处理消息:显示吐司
|
||||
if (msg.what == MSG_SHOW_SHORT_TOAST && msg.obj != null) {
|
||||
String message = (String) msg.obj;
|
||||
showToastInternal(message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例(双重检查锁定,高效且线程安全)
|
||||
* @return ToastUtils 单例对象
|
||||
*/
|
||||
private static ToastUtils getInstance() {
|
||||
if (sInstance == null) { // 第一次检查(无锁,提升效率)
|
||||
synchronized (ToastUtils.class) { // 加锁,确保线程安全
|
||||
if (sInstance == null) { // 第二次检查(避免多线程并发创建多个实例)
|
||||
sInstance = new ToastUtils();
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化工具类(必须在 Application 或 Activity 启动时调用)
|
||||
* 传入全局上下文,用于创建 Toast 实例
|
||||
* @param context 全局上下文(推荐传入 getApplicationContext())
|
||||
*/
|
||||
public static void init(Context context) {
|
||||
if (context == null) {
|
||||
throw new IllegalArgumentException("初始化上下文不能为 null!");
|
||||
}
|
||||
// 初始化全局上下文(使用 Application 上下文,避免内存泄漏)
|
||||
getInstance().mContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部接口:显示短时长吐司(默认显示 2 秒)
|
||||
* 接收外部消息参数,通过 Handler 发送消息到主线程
|
||||
* @param message 吐司展示的文本内容(非空)
|
||||
*/
|
||||
public static void show(String message) {
|
||||
if (message == null || message.isEmpty()) {
|
||||
return; // 空消息直接返回,避免无效显示
|
||||
}
|
||||
// 校验工具类是否初始化
|
||||
if (getInstance().mContext == null) {
|
||||
throw new IllegalStateException("ToastUtils 未初始化!请先调用 init(Context) 方法");
|
||||
}
|
||||
// 发送消息到主线程 Handler
|
||||
getInstance().sendToastMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部私有方法:发送吐司消息(通过 Handler 传递)
|
||||
* 使用实例初始化时的全局上下文确保消息发送有效性
|
||||
* @param message 吐司文本内容
|
||||
*/
|
||||
private void sendToastMessage(String message) {
|
||||
// 校验 Handler 和上下文是否有效
|
||||
if (_mMainHandler == null || mContext == null) {
|
||||
return;
|
||||
}
|
||||
// 创建消息对象,携带吐司内容
|
||||
Message msg = _mMainHandler.obtainMessage(MSG_SHOW_SHORT_TOAST);
|
||||
msg.obj = message;
|
||||
// 发送消息(放入主线程消息队列)
|
||||
_mMainHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部私有方法:实际显示吐司(运行在主线程)
|
||||
* @param message 吐司文本内容
|
||||
*/
|
||||
private void showToastInternal(String message) {
|
||||
// 校验上下文有效性
|
||||
if (mContext == null) {
|
||||
return;
|
||||
}
|
||||
// 显示短时长吐司
|
||||
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 可选:释放资源(如在应用退出时调用)
|
||||
* 移除 Handler 未处理的消息,避免内存泄漏
|
||||
*/
|
||||
public static void release() {
|
||||
if (getInstance()._mMainHandler != null) {
|
||||
_mMainHandler.removeCallbacksAndMessages(null); // 移除所有未处理消息
|
||||
_mMainHandler = null;
|
||||
}
|
||||
sInstance = null; // 销毁单例实例
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package cc.winboll.studio.libappbase;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2024/07/19 14:30:57
|
||||
* @Describe UTF-8编码文件工具类
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/11 20:45
|
||||
* @Describe UTF-8 编码文件操作工具类
|
||||
* 提供字符串与文件的相互转换,强制使用 UTF-8 编码,确保跨平台字符兼容性
|
||||
*/
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
@@ -15,35 +16,71 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class UTF8FileUtils {
|
||||
|
||||
public static final String TAG = "FileUtils";
|
||||
/** 工具类日志 TAG(用于调试输出) */
|
||||
public static final String TAG = "UTF8FileUtils";
|
||||
|
||||
//
|
||||
// 把字符串写入文件,指定 UTF-8 编码
|
||||
//
|
||||
public static void writeStringToFile(String szFilePath, String szContent) throws IOException {
|
||||
File file = new File(szFilePath);
|
||||
if (!file.getParentFile().exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
/**
|
||||
* 将字符串写入文件(强制 UTF-8 编码)
|
||||
* 若文件父目录不存在,自动创建;覆盖原有文件内容
|
||||
* @param filePath 文件路径(包含文件名,如 "/sdcard/test.txt")
|
||||
* @param content 要写入的字符串内容
|
||||
* @throws IOException 写入失败时抛出(如权限不足、路径无效等)
|
||||
*/
|
||||
public static void writeStringToFile(String filePath, String content) throws IOException {
|
||||
// 根据路径创建文件对象
|
||||
File file = new File(filePath);
|
||||
// 获取父目录,若不存在则递归创建
|
||||
File parentDir = file.getParentFile();
|
||||
if (parentDir != null && !parentDir.exists()) {
|
||||
parentDir.mkdirs();
|
||||
}
|
||||
|
||||
// 初始化文件输出流(覆盖模式)
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
// 包装为 UTF-8 编码的字符输出流
|
||||
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||
writer.write(szContent);
|
||||
writer.close();
|
||||
|
||||
try {
|
||||
// 写入字符串内容
|
||||
writer.write(content);
|
||||
} finally {
|
||||
// 强制关闭流,避免资源泄漏(即使写入失败也确保流关闭)
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 读取文件到字符串,指定 UTF-8 编码
|
||||
//
|
||||
public static String readStringFromFile(String szFilePath) throws IOException {
|
||||
File file = new File(szFilePath);
|
||||
/**
|
||||
* 从文件读取字符串(强制 UTF-8 编码)
|
||||
* 逐字符读取文件内容,拼接为完整字符串返回
|
||||
* @param filePath 文件路径(包含文件名,如 "/sdcard/test.txt")
|
||||
* @return 文件内容字符串(空文件返回空字符串)
|
||||
* @throws IOException 读取失败时抛出(如文件不存在、权限不足等)
|
||||
*/
|
||||
public static String readStringFromFile(String filePath) throws IOException {
|
||||
// 根据路径创建文件对象
|
||||
File file = new File(filePath);
|
||||
// 初始化文件输入流
|
||||
FileInputStream inputStream = new FileInputStream(file);
|
||||
// 包装为 UTF-8 编码的字符输入流
|
||||
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
|
||||
// 字符串构建器,用于拼接读取的字符
|
||||
StringBuilder content = new StringBuilder();
|
||||
int character;
|
||||
while ((character = reader.read()) != -1) {
|
||||
content.append((char) character);
|
||||
int charCode; // 存储单个字符的 ASCII 码
|
||||
|
||||
try {
|
||||
// 逐字符读取(-1 表示读取到文件末尾)
|
||||
while ((charCode = reader.read()) != -1) {
|
||||
// 将 ASCII 码转换为字符,追加到字符串
|
||||
content.append((char) charCode);
|
||||
}
|
||||
} finally {
|
||||
// 强制关闭流,避免资源泄漏
|
||||
reader.close();
|
||||
}
|
||||
reader.close();
|
||||
|
||||
// 返回读取的完整字符串
|
||||
return content.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.RadioButton;
|
||||
import cc.winboll.studio.libappbase.BuildConfig;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
import cc.winboll.studio.libappbase.models.UserInfoModel;
|
||||
import cc.winboll.studio.libappbase.utils.RSAUtils;
|
||||
import cc.winboll.studio.libappbase.utils.YunUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/06/04 13:29
|
||||
* @Describe 用户登录框
|
||||
*/
|
||||
public class LogonActivity extends Activity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "LogonActivity";
|
||||
|
||||
public static final String DEBUG_HOST = "http://10.8.0.250:456";
|
||||
public static final String YUN_HOST = "https://yun.winboll.cc";
|
||||
|
||||
|
||||
String mHost = "";
|
||||
RadioButton mrbYunHost;
|
||||
RadioButton mrbDebugHost;
|
||||
LogView mLogView;
|
||||
|
||||
@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_logon);
|
||||
mLogView = findViewById(R.id.logview);
|
||||
mLogView.start();
|
||||
|
||||
mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
|
||||
if (BuildConfig.DEBUG) {
|
||||
mrbYunHost = findViewById(R.id.rb_yunhost);
|
||||
mrbDebugHost = findViewById(R.id.rb_debughost);
|
||||
mrbYunHost.setChecked(!BuildConfig.DEBUG);
|
||||
mrbDebugHost.setChecked(BuildConfig.DEBUG);
|
||||
} else {
|
||||
findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSwitchHost(View view) {
|
||||
if (view.getId() == R.id.rb_yunhost) {
|
||||
mrbDebugHost.setChecked(false);
|
||||
mHost = YUN_HOST;
|
||||
} else if (view.getId() == R.id.rb_debughost) {
|
||||
mrbYunHost.setChecked(false);
|
||||
mHost = DEBUG_HOST;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mLogView.start();
|
||||
}
|
||||
|
||||
public void onTestLogin(View view) {
|
||||
LogUtils.d(TAG, "onTestLogin");
|
||||
final YunUtils yunUtils = YunUtils.getInstance(this);
|
||||
|
||||
UserInfoModel userInfoModel = new UserInfoModel();
|
||||
userInfoModel.setUsername("jian");
|
||||
userInfoModel.setPassword("kkiio");
|
||||
userInfoModel.setToken("aaa111");
|
||||
yunUtils.login(mHost, userInfoModel);
|
||||
}
|
||||
|
||||
public void onTestRSA(View view) {
|
||||
LogUtils.d(TAG, "onTestRSA");
|
||||
RSAUtils utils = RSAUtils.getInstance(this);
|
||||
|
||||
try {
|
||||
// 测试 1:首次生成密钥对
|
||||
LogUtils.d(TAG, "==== 首次生成密钥对 ====");
|
||||
if (utils.keysExist()) {
|
||||
LogUtils.d(TAG, "密钥对已生成");
|
||||
} else {
|
||||
utils.generateAndSaveKeys();
|
||||
LogUtils.d(TAG, "密钥对生成成功。");
|
||||
}
|
||||
|
||||
// 测试 2:获取密钥对(自动读取已生成的文件)
|
||||
KeyPair keyPair = utils.getOrGenerateKeys();
|
||||
PublicKey publicKey = keyPair.getPublic();
|
||||
PrivateKey privateKey = keyPair.getPrivate();
|
||||
|
||||
// 打印密钥信息
|
||||
LogUtils.d(TAG, "\n==== 密钥信息 ====");
|
||||
LogUtils.d(TAG, "公钥算法:" + publicKey.getAlgorithm());
|
||||
LogUtils.d(TAG, "公钥编码长度:" + publicKey.getEncoded().length + "字节");
|
||||
LogUtils.d(TAG, "私钥算法:" + privateKey.getAlgorithm());
|
||||
LogUtils.d(TAG, "私钥编码长度:" + privateKey.getEncoded().length + "字节");
|
||||
|
||||
// 测试 3:重复调用时检查是否复用文件
|
||||
LogUtils.d(TAG, "\n==== 二次调用 ====");
|
||||
KeyPair reusedPair = utils.getOrGenerateKeys();
|
||||
LogUtils.d(TAG, "是否为同一公钥:" + (publicKey.equals(reusedPair.getPublic()))); // true(单例引用)
|
||||
LogUtils.d(TAG, "操作完成");
|
||||
|
||||
String testMessage = "Hello, RSA Encryption!";
|
||||
|
||||
// 1. 获取或生成密钥对
|
||||
PublicKey publicKeyReused = reusedPair.getPublic();
|
||||
PrivateKey privateKeyReused = reusedPair.getPrivate();
|
||||
|
||||
// 2. 公钥加密
|
||||
byte[] encryptedData = utils.encryptWithPublicKey(testMessage, publicKeyReused);
|
||||
LogUtils.d(TAG, "加密后数据(字节长度):" + encryptedData.length);
|
||||
|
||||
// 3. 私钥解密
|
||||
String decryptedMessage = utils.decryptWithPrivateKey(encryptedData, privateKeyReused);
|
||||
LogUtils.d(TAG, "解密结果: " + decryptedMessage);
|
||||
|
||||
// 4. 验证解密是否成功
|
||||
if (testMessage.equals(decryptedMessage)) {
|
||||
LogUtils.d(TAG, "加密解密测试通过!");
|
||||
} else {
|
||||
LogUtils.d(TAG, "测试失败:内容不一致");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import cc.winboll.studio.libappbase.BuildConfig;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import java.io.IOException;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import android.widget.RadioButton;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/06/04 11:06
|
||||
* @Describe 云宝云
|
||||
*/
|
||||
public class YunActivity extends Activity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "YunActivity";
|
||||
|
||||
public static final String DEBUG_HOST = "http://10.8.0.250:456";
|
||||
public static final String YUN_HOST = "https://yun.winboll.cc";
|
||||
|
||||
String mHost = "";
|
||||
RadioButton mrbYunHost;
|
||||
RadioButton mrbDebugHost;
|
||||
LogView mLogView;
|
||||
|
||||
@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_yun);
|
||||
mLogView = findViewById(R.id.logview);
|
||||
mLogView.start();
|
||||
|
||||
mHost = BuildConfig.DEBUG ? DEBUG_HOST: YUN_HOST;
|
||||
if (BuildConfig.DEBUG) {
|
||||
mrbYunHost = findViewById(R.id.rb_yunhost);
|
||||
mrbDebugHost = findViewById(R.id.rb_debughost);
|
||||
mrbYunHost.setChecked(!BuildConfig.DEBUG);
|
||||
mrbDebugHost.setChecked(BuildConfig.DEBUG);
|
||||
} else {
|
||||
findViewById(R.id.ll_hostbar).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
public void onSwitchHost(View view) {
|
||||
if (view.getId() == R.id.rb_yunhost) {
|
||||
mrbDebugHost.setChecked(false);
|
||||
mHost = YUN_HOST;
|
||||
} else if (view.getId() == R.id.rb_debughost) {
|
||||
mrbYunHost.setChecked(false);
|
||||
mHost = DEBUG_HOST;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mLogView.start();
|
||||
}
|
||||
|
||||
public void onTestYun(View view) {
|
||||
LogUtils.d(TAG, "onTestYun");
|
||||
(new Thread(new Runnable(){
|
||||
@Override
|
||||
public void run() {
|
||||
testYun();
|
||||
}
|
||||
})).start();
|
||||
}
|
||||
|
||||
void testYun() {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request = new Request.Builder()
|
||||
.url(mHost + "/backups/")
|
||||
.build();
|
||||
|
||||
Response response = null;
|
||||
try {
|
||||
response = client.newCall(request).execute();
|
||||
if (response.isSuccessful()) {
|
||||
String responseBody = "";
|
||||
if (response.body() != null) {
|
||||
responseBody = response.body().string();
|
||||
}
|
||||
|
||||
// 正则匹配:任意主机名 -> Test OK(主机名部分匹配非空字符)
|
||||
boolean isMatch = responseBody.matches(".+? -> Test OK");
|
||||
|
||||
if (isMatch) {
|
||||
LogUtils.d(TAG, responseBody);
|
||||
} else {
|
||||
LogUtils.d(TAG, "响应内容不匹配,内容:" + responseBody);
|
||||
}
|
||||
} else {
|
||||
LogUtils.d(TAG, "请求失败,状态码:" + response.code());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, "读取响应体失败:" + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "异常:" + e.getMessage());
|
||||
e.printStackTrace(); // Java 7 需显式打印堆栈
|
||||
} finally {
|
||||
// 手动关闭 Response(Java 7 不支持 try-with-resources)
|
||||
if (response != null && response.body() != null) {
|
||||
response.body().close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/02 10:28:08
|
||||
* @Describe 应用调试模型
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class APPModel extends BaseBean {
|
||||
|
||||
public static final String TAG = "APPModel";
|
||||
|
||||
// 应用是否处于正在调试状态
|
||||
//
|
||||
boolean isDebuging = false;
|
||||
|
||||
public APPModel() {
|
||||
this.isDebuging = false;
|
||||
}
|
||||
|
||||
public APPModel(boolean isDebuging) {
|
||||
this.isDebuging = isDebuging;
|
||||
}
|
||||
|
||||
public void setIsDebuging(boolean isDebuging) {
|
||||
this.isDebuging = isDebuging;
|
||||
}
|
||||
|
||||
public boolean isDebuging() {
|
||||
return isDebuging;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return APPModel.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isDebuging").value(isDebuging());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("isDebuging")) {
|
||||
setIsDebuging(jsonReader.nextBoolean());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/06/05 11:26
|
||||
*/
|
||||
|
||||
public class ResponseData {
|
||||
|
||||
public static final String STATUS_SUCCESS = "success";
|
||||
public static final String STATUS_ERROR = "error";
|
||||
|
||||
private String status;
|
||||
private String message;
|
||||
private UserInfoModel data;
|
||||
|
||||
public ResponseData() {
|
||||
this.status = "";
|
||||
this.message = "";
|
||||
this.data = new UserInfoModel();
|
||||
}
|
||||
|
||||
public ResponseData(String status, String message, UserInfoModel data) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void setStatus(String status) {
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setData(UserInfoModel data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public UserInfoModel getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/06/04 19:14
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class UserInfoModel extends BaseBean {
|
||||
|
||||
public static final String TAG = "UserInfoModel";
|
||||
|
||||
String username;
|
||||
String password;
|
||||
String token;
|
||||
|
||||
public UserInfoModel() {
|
||||
this.username = "";
|
||||
this.password = "";
|
||||
this.token = "";
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return UserInfoModel.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("username").value(getUsername());
|
||||
jsonWriter.name("password").value(getPassword());
|
||||
jsonWriter.name("token").value(getToken());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("username")) {
|
||||
setUsername(jsonReader.nextString());
|
||||
} else if (name.equals("password")) {
|
||||
setPassword(jsonReader.nextString());
|
||||
} else if (name.equals("token")) {
|
||||
setToken(jsonReader.nextString());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.models;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/05/10 10:16
|
||||
* @Describe WinBoLLModel
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WinBoLLModel extends BaseBean {
|
||||
|
||||
public static final String TAG = "WinBoLLModel";
|
||||
|
||||
String appPackageName;
|
||||
String appMainServiveName;
|
||||
|
||||
public WinBoLLModel() {
|
||||
this.appPackageName = "";
|
||||
this.appMainServiveName = "";
|
||||
}
|
||||
|
||||
public WinBoLLModel(boolean isDebuging, String appPackageName, String appMainServiveName) {
|
||||
this.appPackageName = appPackageName;
|
||||
this.appMainServiveName = appMainServiveName;
|
||||
}
|
||||
|
||||
public WinBoLLModel(String appPackageName, String appMainServiveName) {
|
||||
this.appPackageName = appPackageName;
|
||||
this.appMainServiveName = appMainServiveName;
|
||||
}
|
||||
|
||||
public void setAppPackageName(String appPackageName) {
|
||||
this.appPackageName = appPackageName;
|
||||
}
|
||||
|
||||
public String getAppPackageName() {
|
||||
return appPackageName;
|
||||
}
|
||||
|
||||
public void setAppMainServiveName(String appMainServiveName) {
|
||||
this.appMainServiveName = appMainServiveName;
|
||||
}
|
||||
|
||||
public String getAppMainServiveName() {
|
||||
return appMainServiveName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return APPModel.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("appPackageName").value(getAppPackageName());
|
||||
jsonWriter.name("appMainServiveName").value(getAppMainServiveName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("appPackageName")) {
|
||||
setAppPackageName(jsonReader.nextString());
|
||||
} else if (name.equals("appMainServiveName")) {
|
||||
setAppMainServiveName(jsonReader.nextString());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.models;
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/05/10 09:36
|
||||
* @Describe WinBoLL 应用消息数据模型
|
||||
*/
|
||||
public class WinBoLLNewsBean extends BaseBean {
|
||||
|
||||
public static final String TAG = "WinBoLLNewsBean";
|
||||
|
||||
String message;
|
||||
|
||||
public WinBoLLNewsBean() {
|
||||
this.message = "";
|
||||
}
|
||||
|
||||
public WinBoLLNewsBean(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return WinBoLLNewsBean.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("message").value(getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("message")) {
|
||||
setMessage(jsonReader.nextString());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.receiver;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/13 21:19:09
|
||||
* @Describe MyBroadcastReceiver
|
||||
*/
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.R;
|
||||
|
||||
public class MyBroadcastReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String TAG = "MyBroadcastReceiver";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (context.getString(R.string.action_sos).equals(intent.getAction())) {
|
||||
String message = intent.getStringExtra("message");
|
||||
String sosPackage = intent.getStringExtra("sosPackage");
|
||||
|
||||
// 处理接收到的广播消息
|
||||
LogUtils.d(TAG, String.format("MyBroadcastReceiver action %s \n%s\n%s", intent.getAction(), sosPackage, message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.sos;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/02 09:36:29
|
||||
* @Describe WinBoLL 应用 SOS 机理保护类
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SOS {
|
||||
|
||||
public static final String TAG = "SOS";
|
||||
|
||||
public static final String ACTION_SOS = SOS.class.getName() + ".ACTION_SOS";
|
||||
public static final String EXTRA_OBJECT = "EXTRA_OBJECT";
|
||||
|
||||
public static void sosToAppBase(Context context, String sosService) {
|
||||
LogUtils.d(TAG, "sosToAppBase()");
|
||||
String szToPackage = "cc.winboll.studio.appbase";
|
||||
sos(context, szToPackage, sosService);
|
||||
|
||||
}
|
||||
|
||||
public static void sosToAppBaseBeta(Context context, String sosService) {
|
||||
LogUtils.d(TAG, "sosToAppBaseBeta()");
|
||||
String szToPackage = "cc.winboll.studio.appbase.beta";
|
||||
sos(context, szToPackage, sosService);
|
||||
|
||||
}
|
||||
|
||||
static void sos(Context context, String szToPackage, String sosService) {
|
||||
LogUtils.d(TAG, "sos(...)");
|
||||
Intent intent = new Intent(ACTION_SOS);
|
||||
intent.putExtra(EXTRA_OBJECT, genSOSObject(context.getPackageName(), sosService));
|
||||
intent.setPackage(szToPackage);
|
||||
LogUtils.d(TAG, String.format("ACTION_SOS :\nTo Package : %sSOS Service : %s\n", szToPackage, sosService));
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
public static SOSObject parseSOSObject(String szSOSObject) {
|
||||
try {
|
||||
return SOSObject.parseStringToBean(szSOSObject, SOSObject.class);
|
||||
} catch (IOException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String sosObjectToString(SOSObject object) {
|
||||
return object.toString();
|
||||
}
|
||||
|
||||
public static String genSOSObject(String objectPackageName, String objectServiveName) {
|
||||
return (new SOSObject(objectPackageName, objectServiveName)).toString();
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.sos;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/27 14:00:21
|
||||
* @Describe Simple Operate Signal Service Center.
|
||||
* 简单操作信号服务中心
|
||||
*/
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.IInterface;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import java.io.FileDescriptor;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class SOSCenterService extends Service {
|
||||
|
||||
public static final String TAG = "SOSCenterService";
|
||||
|
||||
private final IBinder binder =(IBinder)new SOSBinder();
|
||||
|
||||
SOSCenterServiceModel mSOSCenterServiceModel;
|
||||
static MainThread _MainThread;
|
||||
public static synchronized MainThread getMainThreadInstance() {
|
||||
if (_MainThread == null) {
|
||||
_MainThread = new MainThread();
|
||||
}
|
||||
return _MainThread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return binder;
|
||||
}
|
||||
|
||||
public class SOSBinder implements IBinder {
|
||||
|
||||
@Override
|
||||
public void dump(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpAsync(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInterfaceDescriptor() throws RemoteException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinderAlive() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void linkToDeath(IBinder.DeathRecipient deathRecipient, int p) throws RemoteException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pingBinder() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IInterface queryLocalInterface(String string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean transact(int p, Parcel parcel, Parcel parcel1, int p1) throws RemoteException {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unlinkToDeath(IBinder.DeathRecipient deathRecipient, int p) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final String TAG = "SOSBinder";
|
||||
SOSCenterService getService() {
|
||||
return SOSCenterService.this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
LogUtils.d(TAG, "onCreate");
|
||||
mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
|
||||
if(mSOSCenterServiceModel == null) {
|
||||
mSOSCenterServiceModel = new SOSCenterServiceModel();
|
||||
SOSCenterServiceModel.saveBean(this, mSOSCenterServiceModel);
|
||||
}
|
||||
runMainThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
LogUtils.d(TAG, "onStartCommand");
|
||||
|
||||
runMainThread();
|
||||
|
||||
return mSOSCenterServiceModel.isEnable() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
void runMainThread() {
|
||||
mSOSCenterServiceModel = mSOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
|
||||
if (mSOSCenterServiceModel.isEnable()
|
||||
&& _MainThread == null) {
|
||||
getMainThreadInstance().start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy");
|
||||
mSOSCenterServiceModel = SOSCenterServiceModel.loadBean(this, SOSCenterServiceModel.class);
|
||||
if (mSOSCenterServiceModel.isEnable()) {
|
||||
LogUtils.d(TAG, "mSOSCenterServiceModel.isEnable()");
|
||||
// ISOSAPP iSOSAPP = (ISOSAPP)getApplication();
|
||||
// iSOSAPP.helpISOSService(getISOSServiceIntentWhichAskForHelp());
|
||||
}
|
||||
if (_MainThread != null) {
|
||||
_MainThread.isExist = true;
|
||||
_MainThread = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopISOSService(Context context) {
|
||||
LogUtils.d(TAG, "stopISOSService");
|
||||
SOSCenterServiceModel bean = new SOSCenterServiceModel();
|
||||
bean.setIsEnable(false);
|
||||
SOSCenterServiceModel.saveBean(context, bean);
|
||||
context.stopService(new Intent(context, SOSCenterServiceModel.class));
|
||||
}
|
||||
|
||||
public static void startISOSService(Context context) {
|
||||
LogUtils.d(TAG, "startISOSService");
|
||||
SOSCenterServiceModel bean = new SOSCenterServiceModel();
|
||||
bean.setIsEnable(true);
|
||||
SOSCenterServiceModel.saveBean(context, bean);
|
||||
context.startService(new Intent(context, SOSCenterServiceModel.class));
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return "Hello from SOSCenterServiceModel";
|
||||
}
|
||||
|
||||
static class MainThread extends Thread {
|
||||
volatile boolean isExist = false;
|
||||
|
||||
public void setIsExist(boolean isExist) {
|
||||
this.isExist = isExist;
|
||||
}
|
||||
|
||||
public boolean isExist() {
|
||||
return isExist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
super.run();
|
||||
while (!isExist) {
|
||||
LogUtils.d(TAG, "run");
|
||||
try {
|
||||
sleep(1000);
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.sos;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/03/02 09:49:45
|
||||
* @Describe SOSCenterServiceModel
|
||||
* Simple Operate Signal Service Model.
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SOSCenterServiceModel extends BaseBean {
|
||||
|
||||
public static final String TAG = "SOSCenterServiceModel";
|
||||
|
||||
boolean isEnable;
|
||||
|
||||
public SOSCenterServiceModel() {
|
||||
this.isEnable = false;
|
||||
}
|
||||
|
||||
public void setIsEnable(boolean isEnable) {
|
||||
this.isEnable = isEnable;
|
||||
}
|
||||
|
||||
public boolean isEnable() {
|
||||
return isEnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return SOSCenterServiceModel.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isEnable").value(isEnable());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("isEnable")) {
|
||||
setIsEnable(jsonReader.nextBoolean());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.sos;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/27 14:04:35
|
||||
* @Describe SOSCenterServiceReceiver
|
||||
*/
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
public class SOSCenterServiceReceiver extends BroadcastReceiver {
|
||||
|
||||
public static final String TAG = "SOSCenterServiceReceiver";
|
||||
|
||||
public static final String ACTION_SOS = SOSCenterServiceReceiver.class.getName() + ".ACTION_SOS";
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action.equals(ACTION_SOS)) {
|
||||
// 处理接收到的广播消息
|
||||
LogUtils.d(TAG, String.format("Action %s \n%s\n%s", action));
|
||||
} else {
|
||||
LogUtils.d(TAG, String.format("%s", action));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.sos;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/27 14:12:05
|
||||
* @Describe SOSBean
|
||||
*/
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import cc.winboll.studio.libappbase.BaseBean;
|
||||
import java.io.IOException;
|
||||
|
||||
public class SOSObject extends BaseBean {
|
||||
|
||||
public static final String TAG = "SOSObject";
|
||||
|
||||
String objectPackageName;
|
||||
String objectServiveName;
|
||||
|
||||
public SOSObject() {
|
||||
this.objectPackageName = "";
|
||||
this.objectServiveName = "";
|
||||
}
|
||||
|
||||
public SOSObject(String objectPackageName, String objectServiveName) {
|
||||
this.objectPackageName = objectPackageName;
|
||||
this.objectServiveName = objectServiveName;
|
||||
}
|
||||
|
||||
public void setObjectPackageName(String objectPackageName) {
|
||||
this.objectPackageName = objectPackageName;
|
||||
}
|
||||
|
||||
public String getObjectPackageName() {
|
||||
return objectPackageName;
|
||||
}
|
||||
|
||||
public void setObjectServiveName(String objectServiveName) {
|
||||
this.objectServiveName = objectServiveName;
|
||||
}
|
||||
|
||||
public String getObjectServiveName() {
|
||||
return objectServiveName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return SOSObject.class.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("objectPackageName").value(getObjectPackageName());
|
||||
jsonWriter.name("objectServiveName").value(getObjectServiveName());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
|
||||
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
|
||||
if (name.equals("objectPackageName")) {
|
||||
setObjectPackageName(jsonReader.nextString());
|
||||
} else if (name.equals("objectServiveName")) {
|
||||
setObjectServiveName(jsonReader.nextString());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
|
||||
jsonReader.beginObject();
|
||||
while (jsonReader.hasNext()) {
|
||||
String name = jsonReader.nextName();
|
||||
if (!initObjectsFromJsonReader(jsonReader, name)) {
|
||||
jsonReader.skipValue();
|
||||
}
|
||||
}
|
||||
// 结束 JSON 对象
|
||||
jsonReader.endObject();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/06/04 20:15
|
||||
* @Describe 文件操作类
|
||||
*/
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileUtils {
|
||||
public static final String TAG = "FileUtils";
|
||||
|
||||
/**
|
||||
* 读取文件为字节数组(Java 7 语法)
|
||||
*/
|
||||
public static byte[] readFileToByteArray(String filePath) {
|
||||
FileInputStream fis = null;
|
||||
ByteArrayOutputStream bos = null;
|
||||
try {
|
||||
fis = new FileInputStream(filePath);
|
||||
bos = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
bos.write(buffer, 0, bytesRead);
|
||||
}
|
||||
return bos.toByteArray();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
// 手动关闭流(Java 7 不支持 try-with-resources)
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (bos != null) {
|
||||
try {
|
||||
bos.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入字节数组到文件(Java 7 语法)
|
||||
*/
|
||||
public static boolean writeByteArrayToFile(byte[] data, String filePath) {
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(filePath);
|
||||
fos.write(data);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 原字符串读写方法(适配 Java 7)
|
||||
public static String readFileToString(String filePath) {
|
||||
BufferedReader reader = null;
|
||||
try {
|
||||
reader = new BufferedReader(new FileReader(filePath));
|
||||
StringBuilder content = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
content.append(line).append(System.getProperty("line.separator"));
|
||||
}
|
||||
// 去除最后一个换行符(可选)
|
||||
if (content.length() > 0) {
|
||||
content.deleteCharAt(content.length() - 1);
|
||||
}
|
||||
return content.toString();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
} finally {
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean writeStringToFile(String content, String filePath, boolean append) {
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
writer = new BufferedWriter(new FileWriter(filePath, append));
|
||||
writer.write(content);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/06/04 13:36
|
||||
* @Describe RSA加密工具
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.util.Base64;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Objects;
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class RSAUtils {
|
||||
private static final String TAG = "RSAUtils";
|
||||
private static final int KEY_SIZE = 2048;
|
||||
private static final String KEY_ALGORITHM = "RSA";
|
||||
private static final String PUBLIC_KEY_FILE = "public.key";
|
||||
private static final String PRIVATE_KEY_FILE = "private.key";
|
||||
private static final String CIPHER_ALGORITHM = KEY_ALGORITHM + "/ECB/PKCS1Padding"; // 保留原加密方式
|
||||
|
||||
private final String keyPath;
|
||||
private static volatile RSAUtils INSTANCE;
|
||||
|
||||
/**
|
||||
* 构造方法:初始化密钥存储路径(内部存储)
|
||||
*/
|
||||
private RSAUtils(Context context) {
|
||||
keyPath = context.getFilesDir() + File.separator + "keys" + File.separator; // 修正路径格式
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
public static synchronized RSAUtils getInstance(Context context) {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new RSAUtils(context);
|
||||
}
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查密钥文件是否存在
|
||||
*/
|
||||
public boolean keysExist() {
|
||||
File publicKeyFile = new File(keyPath + PUBLIC_KEY_FILE);
|
||||
File privateKeyFile = new File(keyPath + PRIVATE_KEY_FILE);
|
||||
return publicKeyFile.exists() && privateKeyFile.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成密钥对并保存到文件
|
||||
*/
|
||||
public void generateAndSaveKeys() throws Exception {
|
||||
LogUtils.d(TAG, "开始生成 RSA 密钥对(2048位)");
|
||||
KeyPairGenerator generator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
|
||||
generator.initialize(KEY_SIZE);
|
||||
KeyPair keyPair = generator.generateKeyPair();
|
||||
|
||||
saveKey(PUBLIC_KEY_FILE, keyPair.getPublic().getEncoded());
|
||||
saveKey(PRIVATE_KEY_FILE, keyPair.getPrivate().getEncoded());
|
||||
LogUtils.d(TAG, "密钥对生成并保存成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取或生成密钥对(线程安全)
|
||||
*/
|
||||
public KeyPair getOrGenerateKeys() throws Exception {
|
||||
if (!keysExist()) {
|
||||
synchronized (RSAUtils.class) { // 双重检查锁,避免多线程重复生成
|
||||
if (!keysExist()) {
|
||||
generateAndSaveKeys();
|
||||
}
|
||||
}
|
||||
}
|
||||
return readKeysFromFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从文件读取密钥对
|
||||
*/
|
||||
private KeyPair readKeysFromFile() throws Exception {
|
||||
LogUtils.d(TAG, "读取密钥对文件");
|
||||
try {
|
||||
byte[] publicKeyBytes = readFileToBytes(keyPath + PUBLIC_KEY_FILE);
|
||||
byte[] privateKeyBytes = readFileToBytes(keyPath + PRIVATE_KEY_FILE);
|
||||
|
||||
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(publicKeyBytes);
|
||||
PKCS8EncodedKeySpec privateSpec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||
|
||||
KeyFactory factory = KeyFactory.getInstance(KEY_ALGORITHM);
|
||||
PublicKey publicKey = factory.generatePublic(publicSpec);
|
||||
PrivateKey privateKey = factory.generatePrivate(privateSpec);
|
||||
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
|
||||
LogUtils.e(TAG, "密钥文件读取失败:" + e.getMessage());
|
||||
throw new Exception("密钥文件损坏或格式错误", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存密钥到文件(通用方法)
|
||||
*/
|
||||
private void saveKey(String fileName, byte[] keyBytes) throws IOException {
|
||||
Objects.requireNonNull(keyBytes, "密钥字节数据不可为空");
|
||||
File dir = new File(keyPath);
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
throw new IOException("创建密钥目录失败:" + keyPath);
|
||||
}
|
||||
|
||||
FileOutputStream fos = null;
|
||||
try {
|
||||
fos = new FileOutputStream(keyPath + fileName);
|
||||
fos.write(keyBytes);
|
||||
} finally {
|
||||
if (fos != null) {
|
||||
try {
|
||||
fos.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取文件为字节数组(Java 7 兼容)
|
||||
*/
|
||||
private byte[] readFileToBytes(String filePath) throws IOException {
|
||||
File file = new File(filePath);
|
||||
if (!file.exists() || file.isDirectory()) {
|
||||
throw new IOException("文件不存在或为目录:" + filePath);
|
||||
}
|
||||
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(file);
|
||||
byte[] data = new byte[(int) file.length()];
|
||||
int bytesRead = fis.read(data);
|
||||
if (bytesRead != data.length) {
|
||||
throw new IOException("文件读取不完整");
|
||||
}
|
||||
return data;
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "关闭文件流失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公钥加密(带参数校验)
|
||||
*/
|
||||
public byte[] encryptWithPublicKey(String plainText, PublicKey publicKey) throws Exception {
|
||||
Objects.requireNonNull(plainText, "明文不可为空");
|
||||
Objects.requireNonNull(publicKey, "公钥不可为空");
|
||||
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
|
||||
// 检查数据长度是否超过 RSA 限制(2048位密钥最大明文为 214字节,PKCS1Padding)
|
||||
int maxPlainTextSize = cipher.getBlockSize() - 11; // PKCS1Padding 固定填充长度
|
||||
if (plainText.getBytes("UTF-8").length > maxPlainTextSize) {
|
||||
throw new IllegalArgumentException("明文过长,最大支持 " + maxPlainTextSize + " 字节");
|
||||
}
|
||||
|
||||
return cipher.doFinal(plainText.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 私钥解密(带参数校验)
|
||||
*/
|
||||
public String decryptWithPrivateKey(byte[] encryptedData, PrivateKey privateKey) throws Exception {
|
||||
Objects.requireNonNull(encryptedData, "密文不可为空");
|
||||
Objects.requireNonNull(privateKey, "私钥不可为空");
|
||||
|
||||
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
byte[] decryptedBytes = cipher.doFinal(encryptedData);
|
||||
return new String(decryptedBytes, "UTF-8");
|
||||
}
|
||||
/**
|
||||
* 将 HTTP 传输的 Base64 字符串还原为加密字节数组(Java 7 兼容)
|
||||
* @param httpString Base64 字符串(非 null)
|
||||
* @return 加密字节数组
|
||||
* @throws IllegalArgumentException 解码失败时抛出
|
||||
*/
|
||||
public byte[] httpStringToEncryptBytes(String httpString) {
|
||||
Objects.requireNonNull(httpString, "HTTP 字符串不可为空");
|
||||
|
||||
// 计算缺失的填充符数量(Java 7 不支持 repeat(),手动拼接)
|
||||
int pad = httpString.length() % 4;
|
||||
StringBuilder paddedString = new StringBuilder(httpString);
|
||||
if (pad != 0) {
|
||||
for (int i = 0; i < pad; i++) {
|
||||
paddedString.append('='); // 补全 '='
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 Base64 解码(Android 原生 Base64 类兼容 Java 7)
|
||||
return Base64.decode(paddedString.toString(), Base64.URL_SAFE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package cc.winboll.studio.libappbase.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2025/02/17 19:38:20
|
||||
* @Describe 服务工具集
|
||||
*/
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.util.List;
|
||||
|
||||
public class ServiceUtils {
|
||||
|
||||
public static final String TAG = "ServiceUtils";
|
||||
|
||||
/**
|
||||
* 检查指定服务是否正在运行
|
||||
* @param context 上下文
|
||||
* @param serviceClass 服务类
|
||||
* @return true 如果服务正在运行,否则返回 false
|
||||
*/
|
||||
public static boolean isServiceRunning(Context context, String serviceClassName) {
|
||||
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
||||
if (activityManager == null) {
|
||||
return false;
|
||||
}
|
||||
List<ActivityManager.RunningServiceInfo> runningServices;
|
||||
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Intent intent = new Intent(context, serviceClass);
|
||||
// runningServices = activityManager.getRunningServices(100, intent);
|
||||
// } else {
|
||||
runningServices = activityManager.getRunningServices(100);
|
||||
//}
|
||||
for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) {
|
||||
if (serviceClassName.equals(serviceInfo.service.getClassName())) {
|
||||
LogUtils.d(TAG, "Service is running: " + serviceInfo.service.getClassName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "Service is not running: " + serviceClassName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user