Compare commits

..

8 Commits

Author SHA1 Message Date
f36ee0d9b1 <appbase>APK 15.20.9 release Publish. 2026-05-12 09:16:45 +08:00
fe0b2f97ea 重构日志窗口UI主题颜色系统,统一使用toolbarTextColor和toolbarBackgroundColor
【主要变更】

1. 新增主题颜色属性
   - 新增 toolbarTextColor 属性定义(普通模式:黑色, 深色模式:#E0E0E0)
   - 新增 toolbarBackgroundColor 属性定义(普通模式:绿色, 深色模式:深蓝色)

2. 移除废弃颜色属性
   - 移除 buttonTextColor 属性定义
   - 移除 buttonBackgroundColor 属性定义

3. 更新布局文件(普通模式+深色模式)
   - view_log.xml: TextView(LV:)、CheckBox(Selectable/ALL) 使用 toolbarTextColor
   - view_log.xml: Button(Clean/Copy) 使用 toolbarBackgroundColor
   - item_logtag.xml: CheckBox 使用 toolbarTextColor
   - activity_main.xml: 所有Button使用 toolbarBackgroundColor
   - activity_crash_test.xml: 所有Button使用 toolbarBackgroundColor
   - activity_nfc_rsa_operate.xml: Button使用 toolbarBackgroundColor
   - dialog_winboll_host.xml: Button使用 toolbarBackgroundColor

4. 更新Java代码
   - LogView.java: CheckBox和TextView使用主题属性获取 toolbarTextColor
   - LogTagSpinner.java: 下拉框文本颜色使用 toolbarTextColor

5. 更新样式定义
   - styles.xml: 主题属性引用新的 toolbarTextColor/toolbarBackgroundColor
   - attrs.xml: 移除废弃属性,保留 toolbarTextColor/toolbarBackgroundColor
   - colors.xml: 添加新的颜色定义

【适配说明】
- 统一普通模式和深色模式的颜色管理
- 确保日志窗口所有控件颜色与主窗口保持一致
2026-05-12 09:13:16 +08:00
43da2bc03a 统一日志窗口Clean/Copy按钮样式与主窗口保持一致 2026-05-12 00:33:14 +08:00
6baca2779d 同步build.gradle至origin/winboll分支最新配置 2026-05-11 21:13:50 +08:00
ff8cb2fb42 <libappbase>Library Release 15.20.8 2026-05-11 20:30:07 +08:00
e42219ac5a <appbase>APK 15.20.8 release Publish. 2026-05-11 20:29:55 +08:00
42112eb677 LibAppBase: 添加崩溃通知分享日志功能
- 在崩溃通知中添加"分享日志"按钮,点击可分享崩溃日志
- 新增 ShareLogActivity 窗口类处理分享逻辑
- 崩溃日志先保存到缓存文件,再读取分享
- 移除广播接收器方案,简化实现
- 更新 AndroidManifest 注册新 Activity

修改文件:
- libappbase/src/main/AndroidManifest.xml
- libappbase/src/main/java/.../utils/CrashHandleNotifyUtils.java
- libappbase/src/main/java/.../utils/ShareLogActivity.java (新增)

影响范围: 崩溃通知功能
2026-05-11 20:27:40 +08:00
f3114a8121 <libappbase>Library Release 15.20.7 2026-05-11 19:10:54 +08:00
27 changed files with 379 additions and 145 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Mon May 11 19:10:34 HKT 2026
stageCount=8
#Tue May 12 09:16:45 HKT 2026
stageCount=10
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.7
publishVersion=15.20.9
buildCount=0
baseBetaVersion=15.20.8
baseBetaVersion=15.20.10

View File

@@ -31,7 +31,7 @@
android:text="关于应用"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onAboutActivity"
@@ -43,7 +43,7 @@
android:text="应用崩溃测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onCrashTest"
@@ -55,7 +55,7 @@
android:text="应用日志测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTest"
@@ -67,7 +67,7 @@
android:text="应用日志测试(新窗口)"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTestNewTask"
@@ -79,7 +79,7 @@
android:text="应用吐司测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onToastUtilsTest"
@@ -93,7 +93,7 @@
android:text="多开窗口"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onMultiInstance"

View File

@@ -35,7 +35,7 @@
android:text="返回"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onBack"
@@ -47,7 +47,7 @@
android:text="测试崩溃"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onTestCrash"

View File

@@ -31,7 +31,7 @@
android:text="关于应用"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onAboutActivity"
@@ -43,7 +43,7 @@
android:text="应用崩溃测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onCrashTest"
@@ -55,7 +55,7 @@
android:text="应用日志测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTest"
@@ -67,7 +67,7 @@
android:text="应用日志测试(新窗口)"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onLogTestNewTask"
@@ -79,7 +79,7 @@
android:text="应用吐司测试"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onToastUtilsTest"
@@ -93,7 +93,7 @@
android:text="多开窗口"
android:textSize="16sp"
android:textColor="?attr/activityTextColor"
android:background="?attr/buttonBackgroundColor"
android:background="?attr/toolbarBackgroundColor"
android:paddingVertical="12dp"
android:layout_marginHorizontal="24dp"
android:onClick="onMultiInstance"

View File

@@ -6,8 +6,9 @@
<style name="MyDebugActivityTheme" parent="DebugActivityTheme">
<item name="colorTittle">?attr/mainWindowDarkTextColor</item>
<item name="colorTittleBackgound">@color/buttonBackgroundColor</item>
<item name="colorTittleBackgound">?attr/toolbarBackgroundColor</item>
<item name="colorText">?attr/debugTextColor</item>
<item name="colorTextBackgound">?attr/mainWindowDarkBackgroundColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
</resources>

View File

@@ -6,8 +6,9 @@
<style name="MyDebugActivityTheme" parent="DebugActivityTheme">
<item name="colorTittle">?attr/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/buttonBackgroundColor</item>
<item name="colorTittleBackgound">?attr/toolbarBackgroundColor</item>
<item name="colorText">?attr/debugTextColor</item>
<item name="colorTextBackgound">?attr/mainWindowBackgroundColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
</resources>

View File

@@ -93,11 +93,12 @@ allprojects {
}
subprojects {
// 1. 对纯 Java 模块的 JavaCompile 任务配置(强制Java 7
// 1. 对纯 Java 模块的 JavaCompile 任务配置(升级为 Java 11
tasks.withType(JavaCompile) {
options.compilerArgs << "-parameters"
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
// 可选:确保编码一致
options.encoding = "UTF-8"
}
}

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Mon May 11 19:10:34 HKT 2026
stageCount=8
#Tue May 12 09:16:45 HKT 2026
stageCount=10
libraryProject=libappbase
baseVersion=15.20
publishVersion=15.20.7
publishVersion=15.20.9
buildCount=0
baseBetaVersion=15.20.8
baseBetaVersion=15.20.10

View File

@@ -47,6 +47,13 @@
<activity android:name="cc.winboll.studio.libappbase.activities.FTPBackupsActivity"/>
<activity
android:name=".utils.ShareLogActivity"
android:label="ShareLogActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@android:style/Theme.NoDisplay"/>
</application>
</manifest>

View File

@@ -8,6 +8,7 @@ package cc.winboll.studio.libappbase;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
@@ -248,7 +249,10 @@ public class LogView extends RelativeLayout {
}
mSelectAllTAGCheckBox.setLayoutParams(layoutParams2);
//mSelectAllTAGCheckBox.setPadding(0,0,0,0);
mSelectAllTAGCheckBox.setTextColor(mContext.getResources().getColor(R.color.white));
TypedArray ta1 = mContext.obtainStyledAttributes(new int[] { R.attr.toolbarTextColor });
int toolbarTextColor1 = ta1.getColor(0, mContext.getResources().getColor(R.color.white));
ta1.recycle();
mSelectAllTAGCheckBox.setTextColor(toolbarTextColor1);
mSelectAllTAGCheckBox.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
@@ -502,10 +506,17 @@ public class LogView extends RelativeLayout {
}
holder.tvText.setLayoutParams(layoutParams);
holder.tvText.setPadding(0,0,0,0);
holder.tvText.setTextColor(mContext.getResources().getColor(R.color.white));
TypedArray ta2 = mContext.obtainStyledAttributes(new int[] { R.attr.toolbarTextColor });
int toolbarTextColor2 = ta2.getColor(0, mContext.getResources().getColor(R.color.white));
ta2.recycle();
holder.tvText.setTextColor(toolbarTextColor2);
holder.cbChecked.setChecked(item.isChecked());
holder.cbChecked.setLayoutParams(layoutParams);
holder.cbChecked.setPadding(0,0,0,0);
TypedArray ta3 = mContext.obtainStyledAttributes(new int[] { R.attr.toolbarTextColor });
int toolbarTextColor3 = ta3.getColor(0, mContext.getResources().getColor(R.color.white));
ta3.recycle();
holder.cbChecked.setTextColor(toolbarTextColor3);
holder.cbChecked.setOnClickListener(new View.OnClickListener(){
@Override

View File

@@ -1,11 +1,11 @@
package cc.winboll.studio.libappbase.utils;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
@@ -13,13 +13,19 @@ import cc.winboll.studio.libappbase.CrashHandler;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
/**
* 应用崩溃处理通知实用工具集(类库兼容版)
* 核心功能:作为独立类库使用,发送崩溃通知,点击跳转宿主应用的 GlobalCrashActivity 并传递日志
* 适配说明:移除固定包名依赖,通过外部传入宿主包名,支持任意应用集成使用
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @CreateTime 2025/11/29 21:12:00
* @EditTime 2026/05/11 15:38:21
* @EditTime 2026/05/11 21:55:00
*/
public class CrashHandleNotifyUtils {
@@ -35,17 +41,18 @@ public class CrashHandleNotifyUtils {
private static final int API_LEVEL_ANDROID_12 = 31;
/** PendingIntent.FLAG_IMMUTABLE 常量值API 31+ */
private static final int FLAG_IMMUTABLE = 0x00000040;
/** 通知内容最大行数控制在3行超出部分省略 */
private static final int NOTIFICATION_MAX_LINES = 3;
/** 通知摘要最大长度 */
private static final int SUMMARY_MAX_LENGTH = 200;
/** 展开按钮广播Action */
private static final String ACTION_EXPAND = "cc.winboll.studio.libappbase.ACTION_EXPAND_CRASH";
/** 分享日志请求码 */
private static final int REQUEST_CODE_SHARE_LOG = 0x002;
/** 缓存崩溃日志子目录 */
private static final String CRASH_LOG_CACHE_SUBDIR = "crashnotify";
/** 缓存崩溃日志文件名 */
private static final String CRASH_LOG_CACHE_FILENAME = "crash_log.txt";
private static Context sHostContext = null;
// ====================== 静态成员 ======================
private static String sHostPackageName = "";
private static String sHostAppName = "";
private static Class<?> sReportCrashActivity = null;
private static String sCrashLogCacheFilePath = "";
// ====================== 正则表达式定义 ======================
private static final String REGEX_EXCEPTION_TYPE = "([\\w.]+Exception|[\\w.]+Error)";
@@ -61,18 +68,29 @@ public class CrashHandleNotifyUtils {
* @param errorLog 崩溃日志内容
* @param reportCrashActivity 崩溃详情跳转Activity类
*/
public static void handleUncaughtException(final Application hostApp,
final String hostPackageName,
final String errorLog,
final Class<?> reportCrashActivity) {
public static void handleUncaughtException(final android.app.Application hostApp,
final String hostPackageName,
final String errorLog,
final Class<?> reportCrashActivity) {
LogUtils.d(TAG, "handleUncaughtException 进入方法");
// 校验入参
if (hostApp == null || TextUtils.isEmpty(hostPackageName) || TextUtils.isEmpty(errorLog)) {
LogUtils.e(TAG, "handleUncaughtException 参数为空校验不通过");
return;
}
sHostPackageName = hostPackageName;
final String hostAppName = getHostAppName(hostApp, hostPackageName);
sendCrashNotification(hostApp, hostPackageName, hostAppName, errorLog, reportCrashActivity);
final String crashLogFilePath = saveCrashLogToCache(hostApp, errorLog);
if (TextUtils.isEmpty(crashLogFilePath)) {
LogUtils.e(TAG, "保存崩溃日志到缓存文件失败");
return;
}
sCrashLogCacheFilePath = crashLogFilePath;
final Intent shareIntent = new Intent(hostApp, ShareLogActivity.class);
shareIntent.putExtra(ShareLogActivity.EXTRA_CRASH_LOG_FILEPATH, crashLogFilePath);
shareIntent.putExtra(ShareLogActivity.EXTRA_CRASH_LOG_SUBJECT, "崩溃日志");
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent sharePendingIntent = createSharePendingIntent(hostApp, shareIntent);
sendCrashNotification(hostApp, hostPackageName, hostAppName, errorLog, reportCrashActivity, sharePendingIntent);
}
/**
@@ -81,9 +99,9 @@ public class CrashHandleNotifyUtils {
* @param intent 携带崩溃信息Intent
* @param reportCrashActivity 崩溃详情Activity
*/
public static void handleUncaughtException(final Application hostApp,
final Intent intent,
final Class<?> reportCrashActivity) {
public static void handleUncaughtException(final android.app.Application hostApp,
final Intent intent,
final Class<?> reportCrashActivity) {
LogUtils.d(TAG, "handleUncaughtException 重载方法进入");
String hostPackageName = intent.getStringExtra("EXTRA_HOST_PACKAGE_NAME");
if (TextUtils.isEmpty(hostPackageName)) {
@@ -94,6 +112,16 @@ public class CrashHandleNotifyUtils {
handleUncaughtException(hostApp, hostPackageName, errorLog, reportCrashActivity);
}
/**
* 资源释放
* @param hostContext 宿主上下文
*/
public static void release(final Context hostContext) {
LogUtils.d(TAG, "release 执行资源释放");
sHostPackageName = "";
sCrashLogCacheFilePath = "";
}
// ====================== 内部工具方法 ======================
/**
* 获取宿主应用名称
@@ -104,14 +132,87 @@ public class CrashHandleNotifyUtils {
private static String getHostAppName(final Context hostContext, final String hostPackageName) {
try {
return hostContext.getPackageManager()
.getApplicationLabel(hostContext.getPackageManager()
.getApplicationInfo(hostPackageName, 0)).toString();
.getApplicationLabel(hostContext.getPackageManager()
.getApplicationInfo(hostPackageName, 0)).toString();
} catch (Exception e) {
LogUtils.e(TAG, "获取宿主应用名称失败", e);
return "未知应用";
}
}
/**
* 保存崩溃日志到缓存文件
* @param hostContext 宿主上下文
* @param crashLog 崩溃日志内容
* @return 缓存文件路径,失败返回空字符串
*/
private static String saveCrashLogToCache(final Context hostContext, final String crashLog) {
if (hostContext == null || TextUtils.isEmpty(crashLog)) {
return "";
}
BufferedReader reader = null;
FileOutputStream fos = null;
try {
final File cacheDir = new File(hostContext.getCacheDir(), CRASH_LOG_CACHE_SUBDIR);
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
final File cacheFile = new File(cacheDir, CRASH_LOG_CACHE_FILENAME);
if (cacheFile.exists()) {
cacheFile.delete();
}
cacheFile.createNewFile();
fos = new FileOutputStream(cacheFile);
fos.write(crashLog.getBytes("UTF-8"));
fos.flush();
LogUtils.d(TAG, "saveCrashLogToCache 保存崩溃日志到缓存: " + cacheFile.getAbsolutePath());
return cacheFile.getAbsolutePath();
} catch (Exception e) {
LogUtils.e(TAG, "saveCrashLogToCache 异常", e);
return "";
} finally {
if (reader != null) {
try { reader.close(); } catch (Exception e) {}
}
if (fos != null) {
try { fos.close(); } catch (Exception e) {}
}
}
}
/**
* 读取缓存的崩溃日志文件内容
* @param hostContext 宿主上下文
* @return 日志内容,失败返回空字符串
*/
private static String readCachedCrashLog(final Context hostContext) {
if (hostContext == null || TextUtils.isEmpty(sCrashLogCacheFilePath)) {
return "";
}
BufferedReader reader = null;
try {
final File cacheFile = new File(sCrashLogCacheFilePath);
if (!cacheFile.exists()) {
LogUtils.w(TAG, "readCachedCrashLog 缓存文件不存在");
return "";
}
reader = new BufferedReader(new InputStreamReader(new FileInputStream(cacheFile), "UTF-8"));
final StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
} catch (Exception e) {
LogUtils.e(TAG, "readCachedCrashLog 异常", e);
return "";
} finally {
if (reader != null) {
try { reader.close(); } catch (Exception e) {}
}
}
}
/**
* 发送崩溃系统通知
* @param hostContext 宿主上下文
@@ -119,45 +220,65 @@ public class CrashHandleNotifyUtils {
* @param hostAppName 宿主应用名
* @param errorLog 崩溃日志
* @param reportCrashActivity 跳转Activity
* @param sharePendingIntent 分享日志PendingIntent
*/
private static void sendCrashNotification(final Context hostContext,
final String hostPackageName,
final String hostAppName,
final String errorLog,
final Class<?> reportCrashActivity) {
private static void sendCrashNotification(final Context hostContext,
final String hostPackageName,
final String hostAppName,
final String errorLog,
final Class<?> reportCrashActivity,
final PendingIntent sharePendingIntent) {
final NotificationManager notificationManager =
(NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE);
(NotificationManager) hostContext.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
LogUtils.e(TAG, "获取NotificationManager失败");
return;
}
// 8.0以上创建通知渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createCrashNotifyChannel(hostContext, notificationManager);
}
final PendingIntent jumpIntent = getGlobalCrashPendingIntent(hostContext,
hostPackageName, errorLog, reportCrashActivity);
hostPackageName, errorLog, reportCrashActivity);
if (jumpIntent == null) {
LogUtils.e(TAG, "构建跳转PendingIntent失败");
return;
}
final Notification notification = buildNotification(hostContext, hostPackageName, hostAppName, errorLog, jumpIntent);
final Notification notification = buildNotification(hostContext, hostPackageName, hostAppName, errorLog, jumpIntent, sharePendingIntent);
notificationManager.notify(CRASH_NOTIFY_ID, notification);
LogUtils.d(TAG, "崩溃通知发送成功,宿主包名:" + hostPackageName);
}
/**
* 创建分享日志PendingIntent
* @param hostContext 宿主上下文
* @param shareIntent 分享意图
* @return PendingIntent实例
*/
private static PendingIntent createSharePendingIntent(final Context hostContext, final Intent shareIntent) {
int flags = PendingIntent.FLAG_UPDATE_CURRENT;
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
flags |= FLAG_IMMUTABLE;
}
return PendingIntent.getActivity(
hostContext,
REQUEST_CODE_SHARE_LOG,
shareIntent,
flags
);
}
/**
* 创建通知渠道适配Android O及以上
* @param hostContext 宿主上下文
* @param notificationManager 通知管理器
*/
private static void createCrashNotifyChannel(final Context hostContext,
final NotificationManager notificationManager) {
final NotificationManager notificationManager) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
android.app.NotificationChannel channel = new android.app.NotificationChannel(
CRASH_NOTIFY_CHANNEL_ID,
CRASH_NOTIFY_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
CRASH_NOTIFY_CHANNEL_ID,
CRASH_NOTIFY_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
);
channel.setDescription("应用崩溃通知(由 WinBoLL Studio 类库提供,点击查看详情)");
notificationManager.createNotificationChannel(channel);
@@ -174,9 +295,9 @@ private static void sendCrashNotification(final Context hostContext,
* @return PendingIntent实例
*/
private static PendingIntent getGlobalCrashPendingIntent(final Context hostContext,
final String hostPackageName,
final String errorLog,
final Class<?> reportCrashActivity) {
final String hostPackageName,
final String errorLog,
final Class<?> reportCrashActivity) {
try {
final Intent crashIntent = new Intent(hostContext, reportCrashActivity);
crashIntent.setPackage(hostPackageName);
@@ -187,11 +308,11 @@ private static void sendCrashNotification(final Context hostContext,
if (Build.VERSION.SDK_INT >= API_LEVEL_ANDROID_12) {
flags |= FLAG_IMMUTABLE;
}
return PendingIntent.getActivity(
hostContext,
CRASH_NOTIFY_ID,
crashIntent,
flags
return PendingIntent.getActivity(
hostContext,
CRASH_NOTIFY_ID,
crashIntent,
flags
);
} catch (Exception e) {
LogUtils.e(TAG, "构建跳转Intent异常", e);
@@ -206,32 +327,36 @@ return PendingIntent.getActivity(
* @param hostAppName 宿主应用名
* @param errorLog 崩溃日志
* @param jumpIntent 点击跳转意图
* @param shareIntent 分享日志意图
* @return 构建好的Notification
*/
@SuppressWarnings("deprecation")
private static Notification buildNotification(final Context hostContext,
final String hostPackageName,
final String hostAppName,
final String errorLog,
final PendingIntent jumpIntent) {
final String hostPackageName,
final String hostAppName,
final String errorLog,
final PendingIntent jumpIntent,
final PendingIntent shareIntent) {
Notification.Builder builder = new Notification.Builder(hostContext);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
builder.setChannelId(CRASH_NOTIFY_CHANNEL_ID);
}
String briefInfo = extractBriefInfo(errorLog);
Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
final String briefInfo = extractBriefInfo(errorLog);
final Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle();
bigTextStyle.setBigContentTitle(hostAppName + " 崩溃");
bigTextStyle.bigText(briefInfo);
bigTextStyle.setSummaryText("点击查看详情");
builder.setStyle(bigTextStyle);
builder.setSmallIcon(hostContext.getApplicationInfo().icon)
.setContentTitle(hostAppName + " 崩溃")
.setContentText(briefInfo.split("\n")[0])
.setContentIntent(jumpIntent)
.setAutoCancel(true)
.setWhen(System.currentTimeMillis())
.setPriority(Notification.PRIORITY_DEFAULT);
.setContentTitle(hostAppName + " 崩溃")
.setContentText(briefInfo.split("\n")[0])
.setContentIntent(jumpIntent)
.setAutoCancel(true)
.setWhen(System.currentTimeMillis())
.setPriority(Notification.PRIORITY_DEFAULT);
if (shareIntent != null) {
builder.addAction(android.R.drawable.ic_menu_send, "分享日志", shareIntent);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
return builder.build();
} else {
@@ -248,10 +373,10 @@ return PendingIntent.getActivity(
if (errorLog == null || errorLog.isEmpty()) {
return "无崩溃日志";
}
String brief = extractBriefInfo(errorLog);
String firstLine = brief.split("\n")[0];
final String brief = extractBriefInfo(errorLog);
final String firstLine = brief.split("\n")[0];
if (firstLine.length() > 80) {
firstLine = firstLine.substring(0, 80) + "...";
return firstLine.substring(0, 80) + "...";
}
return firstLine;
}
@@ -265,7 +390,7 @@ return PendingIntent.getActivity(
if (crashLog == null || crashLog.isEmpty()) {
return "无崩溃日志";
}
StringBuilder brief = new StringBuilder();
final StringBuilder brief = new StringBuilder();
try {
java.util.regex.Pattern exceptionPattern = java.util.regex.Pattern.compile(REGEX_EXCEPTION_TYPE);
java.util.regex.Matcher exceptionMatcher = exceptionPattern.matcher(crashLog);
@@ -315,13 +440,4 @@ return PendingIntent.getActivity(
}
return brief.toString();
}
/**
* 资源释放预留方法
* @param hostContext 宿主上下文
*/
public static void release(final Context hostContext) {
LogUtils.d(TAG, "CrashHandleNotifyUtils 执行资源释放");
}
}

View File

@@ -0,0 +1,91 @@
package cc.winboll.studio.libappbase.utils;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
/**
* 分享崩溃日志窗口类
* @Author ZhanGSKen<zhangsken@qq.com>
* @CreateTime 2026/05/11 22:30:00
*/
public class ShareLogActivity extends Activity {
public static final String TAG = "ShareLogActivity";
public static final String EXTRA_CRASH_LOG_FILEPATH = "crash_log_filepath";
public static final String EXTRA_CRASH_LOG_SUBJECT = "crash_log_subject";
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate 进入方法");
final Intent intent = getIntent();
if (intent == null) {
Log.e(TAG, "onCreate intent 为空");
finish();
return;
}
final String crashLogFilePath = intent.getStringExtra(EXTRA_CRASH_LOG_FILEPATH);
if (crashLogFilePath == null || crashLogFilePath.isEmpty()) {
Log.e(TAG, "onCreate crashLogFilePath 为空");
Toast.makeText(this, "日志文件路径无效", Toast.LENGTH_SHORT).show();
finish();
return;
}
final String subject = intent.getStringExtra(EXTRA_CRASH_LOG_SUBJECT);
handleShareCrashLog(crashLogFilePath, subject);
}
private void handleShareCrashLog(final String crashLogFilePath, final String subject) {
Log.d(TAG, "handleShareCrashLog crashLogFilePath = " + crashLogFilePath);
final File crashLogFile = new File(crashLogFilePath);
if (!crashLogFile.exists()) {
Log.e(TAG, "handleShareCrashLog 文件不存在");
Toast.makeText(this, "日志文件不存在", Toast.LENGTH_SHORT).show();
finish();
return;
}
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(new FileInputStream(crashLogFile), "UTF-8"));
final StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
final String logContent = sb.toString();
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, logContent);
if (subject != null && !subject.isEmpty()) {
shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
} else {
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "崩溃日志");
}
startActivity(Intent.createChooser(shareIntent, "分享日志到"));
Log.d(TAG, "handleShareCrashLog 分享成功");
} catch (Exception e) {
Log.e(TAG, "handleShareCrashLog 异常", e);
Toast.makeText(this, "分享失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
} finally {
if (reader != null) {
try { reader.close(); } catch (Exception e) {}
}
finish();
}
}
}

View File

@@ -3,6 +3,7 @@ package cc.winboll.studio.libappbase.widget;
import android.content.Context;
import android.graphics.Color;
import android.text.TextUtils;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
@@ -153,9 +154,12 @@ public class LogTagSpinner extends Spinner {
itemTv.setPadding(mTextPadding, 0, mTextPadding, 0);
// 4. 文字对齐(垂直居中+靠左,符合常规 UI 设计)
//itemTv.setGravity(View.GRAVITY_CENTER_VERTICAL | View.GRAVITY_START);
// 5. 文字颜色(统一深色,可改为项目颜色资源
itemTv.setTextColor(this.mContext.getColor(R.color.white));
itemTv.setBackgroundColor(this.mContext.getColor(R.color.btn_gray_normal));
// 5. 文字颜色(使用主题属性 ?attr/toolbarTextColor
TypedArray ta = mContext.obtainStyledAttributes(new int[] { R.attr.toolbarTextColor });
int toolbarTextColor = ta.getColor(0, mContext.getResources().getColor(R.color.white));
ta.recycle();
itemTv.setTextColor(toolbarTextColor);
itemTv.setBackgroundColor(this.mContext.getResources().getColor(R.color.btn_gray_normal));
// 6. 文字溢出处理(最多 2 行,超出省略,避免长标签换行过多)
itemTv.setSingleLine(false);
itemTv.setMaxLines(2);

View File

@@ -50,8 +50,8 @@
android:layout_height="wrap_content"
android:text="功能按钮待激活"
android:textSize="16sp"
android:textColor="?attr/buttonTextColor"
android:backgroundTint="?attr/buttonBackgroundColor"
android:textColor="?attr/toolbarTextColor"
android:backgroundTint="?attr/toolbarBackgroundColor"
android:padding="14dp"
android:enabled="false"/>

View File

@@ -53,8 +53,8 @@
android:layout_height="wrap_content"
android:text="确认"
android:textSize="14sp"
android:backgroundTint="?attr/buttonBackgroundColor"
android:textColor="?attr/buttonTextColor"/>
android:backgroundTint="?attr/toolbarBackgroundColor"
android:textColor="?attr/toolbarTextColor"/>
</LinearLayout>

View File

@@ -18,6 +18,7 @@
android:layout_width="wrap_content"
android:layout_height="@dimen/log_button_height"
android:textSize="@dimen/log_text_size"
android:textColor="?attr/toolbarTextColor"
android:id="@+id/viewlogtagCheckBox1"/>
</LinearLayout>

View File

@@ -15,13 +15,13 @@
android:background="?attr/colorTittleBackgound"
android:id="@+id/viewlogRelativeLayoutToolbar">
<Button
<Button
android:layout_width="@dimen/log_button_width"
android:layout_height="@dimen/log_button_height"
android:textSize="@dimen/log_text_size"
android:text="Clean"
android:textColor="?attr/colorText"
android:backgroundTint="?attr/colorTittleBackgound"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:layout_centerVertical="true"
android:id="@+id/viewlogButtonClean"
android:layout_marginLeft="5dp"/>
@@ -36,7 +36,7 @@
android:layout_centerVertical="true"
android:id="@+id/viewlogTextView1"
android:background="?attr/colorTittleBackgound"
android:textColor="?attr/colorText"/>
android:textColor="?attr/toolbarTextColor"/>
<cc.winboll.studio.libappbase.widget.LogTagSpinner
android:layout_width="wrap_content"
@@ -56,14 +56,14 @@
android:background="?attr/colorTittleBackgound"
android:id="@+id/viewlogCheckBoxSelectable"
android:padding="@dimen/log_text_padding"
android:textColor="?attr/colorText"/>
android:textColor="?attr/toolbarTextColor"/>
<Button
android:layout_width="@dimen/log_button_width"
android:layout_height="@dimen/log_button_height"
android:textSize="@dimen/log_text_size"
android:textColor="?attr/colorText"
android:backgroundTint="?attr/colorTittleBackgound"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:text="Copy"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
@@ -89,7 +89,7 @@
android:padding="2dp"
android:id="@+id/viewlogCheckBox1"
android:background="?attr/colorTittleBackgound"
android:textColor="?attr/colorText"
android:textColor="?attr/toolbarTextColor"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"/>

View File

@@ -50,8 +50,8 @@
android:layout_height="wrap_content"
android:text="功能按钮待激活"
android:textSize="16sp"
android:textColor="?attr/buttonTextColor"
android:backgroundTint="?attr/buttonBackgroundColor"
android:textColor="?attr/toolbarTextColor"
android:backgroundTint="?attr/toolbarBackgroundColor"
android:padding="14dp"
android:enabled="false"/>

View File

@@ -53,8 +53,8 @@
android:layout_height="wrap_content"
android:text="确认"
android:textSize="14sp"
android:backgroundTint="?attr/buttonBackgroundColor"
android:textColor="?attr/buttonTextColor"/>
android:backgroundTint="?attr/toolbarBackgroundColor"
android:textColor="?attr/toolbarTextColor"/>
</LinearLayout>

View File

@@ -18,6 +18,7 @@
android:layout_width="wrap_content"
android:layout_height="@dimen/log_button_height"
android:textSize="@dimen/log_text_size"
android:textColor="?attr/toolbarTextColor"
android:id="@+id/viewlogtagCheckBox1"/>
</LinearLayout>

View File

@@ -20,8 +20,8 @@
android:layout_height="@dimen/log_button_height"
android:textSize="@dimen/log_text_size"
android:text="Clean"
android:textColor="?attr/colorText"
android:backgroundTint="?attr/colorTittleBackgound"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:layout_centerVertical="true"
android:id="@+id/viewlogButtonClean"
android:layout_marginLeft="5dp"/>
@@ -36,7 +36,7 @@
android:layout_centerVertical="true"
android:id="@+id/viewlogTextView1"
android:background="?attr/colorTittleBackgound"
android:textColor="?attr/colorText"/>
android:textColor="?attr/toolbarTextColor"/>
<cc.winboll.studio.libappbase.widget.LogTagSpinner
android:layout_width="wrap_content"
@@ -56,14 +56,14 @@
android:background="?attr/colorTittleBackgound"
android:id="@+id/viewlogCheckBoxSelectable"
android:padding="@dimen/log_text_padding"
android:textColor="?attr/colorText"/>
android:textColor="?attr/toolbarTextColor"/>
<Button
android:layout_width="@dimen/log_button_width"
android:layout_height="@dimen/log_button_height"
android:textSize="@dimen/log_text_size"
android:textColor="?attr/colorText"
android:backgroundTint="?attr/colorTittleBackgound"
android:textColor="?attr/activityTextColor"
android:background="?attr/toolbarBackgroundColor"
android:text="Copy"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
@@ -89,7 +89,7 @@
android:padding="2dp"
android:id="@+id/viewlogCheckBox1"
android:background="?attr/colorTittleBackgound"
android:textColor="?attr/colorText"
android:textColor="?attr/toolbarTextColor"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"/>

View File

@@ -20,11 +20,7 @@
<attr name="aboutViewDividerColor" format="color" />
</declare-styleable>
<!-- ButtonStyle 样式属性 -->
<declare-styleable name="ButtonStyle">
<attr name="buttonBackgroundColor" format="color" />
<attr name="buttonTextColor" format="color" />
</declare-styleable>
<!-- DialogStyle 样式属性 -->
<declare-styleable name="DialogStyle">

View File

@@ -63,7 +63,9 @@
<!-- ============== 主题颜色 ============== -->
<color name="mainWindowBackgroundColor">#FF0D1B2A</color>
<color name="mainWindowTextColor">#FFE0E0E0</color>
<color name="buttonBackgroundColor">#FF1E3A5F</color>
<color name="toolbarTextColor">#FFE0E0E0</color>
<color name="toolbarBackgroundColor">#FF1E3A5F</color>
<color name="debugTextColor">#FF00FF00</color>
</resources>

View File

@@ -8,12 +8,12 @@
<item name="aboutViewTextColor">?attr/mainWindowDarkTextColor</item>
<item name="aboutViewTitleColor">?attr/mainWindowDarkTextColor</item>
<item name="aboutViewDividerColor">?attr/mainWindowTextColor</item>
<item name="buttonBackgroundColor">@color/buttonBackgroundColor</item>
<item name="buttonTextColor">?attr/mainWindowDarkTextColor</item>
<item name="dialogBackgroundColor">?attr/mainWindowDarkBackgroundColor</item>
<item name="dialogTextColor">?attr/mainWindowDarkTextColor</item>
<item name="toolbarBackgroundColor">?attr/buttonBackgroundColor</item>
<item name="toolbarTextColor">?attr/mainWindowDarkTextColor</item>
<item name="toolbarBackgroundColor">@color/toolbarBackgroundColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
<item name="textViewBackgroundColor">?attr/mainWindowDarkBackgroundColor</item>
<item name="textViewTextColor">?attr/mainWindowDarkTextColor</item>
<item name="editTextBackgroundColor">?attr/mainWindowDarkBackgroundColor</item>
@@ -29,12 +29,14 @@
<!-- DebugActivityTheme 深色模式样式 -->
<style name="DebugActivityTheme" parent="@android:style/Theme.DeviceDefault.NoActionBar">
<item name="android:statusBarColor">@color/mainWindowBackgroundColor</item>
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="colorTittle">?attr/mainWindowDarkTextColor</item>
<item name="colorTittleBackgound">@color/buttonBackgroundColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">?attr/debugTextColor</item>
<item name="colorTextBackgound">?attr/mainWindowDarkBackgroundColor</item>
<item name="debugTextColor">@color/debugTextColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
<!-- DialogStyle 对话框样式 -->

View File

@@ -26,11 +26,7 @@
<attr name="aboutViewDividerColor" format="color" />
</declare-styleable>
<!-- ButtonStyle 样式属性 -->
<declare-styleable name="ButtonStyle">
<attr name="buttonBackgroundColor" format="color" />
<attr name="buttonTextColor" format="color" />
</declare-styleable>
<!-- DialogStyle 样式属性 -->
<declare-styleable name="DialogStyle">

View File

@@ -63,7 +63,9 @@
<!-- ============== 主题颜色 ============== -->
<color name="mainWindowBackgroundColor">#FFF5F5F5</color>
<color name="mainWindowTextColor">#FF000000</color>
<color name="buttonBackgroundColor">#FF00B322</color>
<color name="toolbarTextColor">#FF000000</color>
<color name="toolbarBackgroundColor">#FF00B322</color>
<color name="debugTextColor">#FF808080</color>
</resources>

View File

@@ -8,12 +8,12 @@
<item name="aboutViewTextColor">?attr/mainWindowTextColor</item>
<item name="aboutViewTitleColor">?attr/mainWindowTextColor</item>
<item name="aboutViewDividerColor">?attr/mainWindowDarkTextColor</item>
<item name="buttonBackgroundColor">@color/buttonBackgroundColor</item>
<item name="buttonTextColor">?attr/mainWindowTextColor</item>
<item name="dialogBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="dialogTextColor">?attr/mainWindowTextColor</item>
<item name="toolbarBackgroundColor">?attr/buttonBackgroundColor</item>
<item name="toolbarTextColor">?attr/mainWindowTextColor</item>
<item name="toolbarBackgroundColor">@color/toolbarBackgroundColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
<item name="textViewBackgroundColor">?attr/mainWindowBackgroundColor</item>
<item name="textViewTextColor">?attr/mainWindowTextColor</item>
<item name="editTextBackgroundColor">?attr/mainWindowBackgroundColor</item>
@@ -29,12 +29,14 @@
<!-- DebugActivityTheme 普通模式样式 -->
<style name="DebugActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
<item name="android:statusBarColor">@color/buttonBackgroundColor</item>
<item name="android:statusBarColor">@color/toolbarBackgroundColor</item>
<item name="colorTittle">?attr/mainWindowTextColor</item>
<item name="colorTittleBackgound">@color/buttonBackgroundColor</item>
<item name="colorTittleBackgound">@color/toolbarBackgroundColor</item>
<item name="colorText">?attr/debugTextColor</item>
<item name="colorTextBackgound">?attr/mainWindowBackgroundColor</item>
<item name="debugTextColor">@color/debugTextColor</item>
<item name="toolbarTextColor">@color/toolbarTextColor</item>
</style>
<!-- DialogStyle 对话框样式 -->