Compare commits
244 Commits
appbase-v1
...
winboll
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b6bbec1ba | |||
| 59bba52bdd | |||
| 208c22f164 | |||
| ddab730cdc | |||
| 84b0e8c8d3 | |||
| 88debe27de | |||
| 7ec69f9cbf | |||
| 8479d5ec74 | |||
| 57e2dc7084 | |||
| 9471e63949 | |||
| beb0247706 | |||
| e3a1948c87 | |||
| d30d731764 | |||
| d0f6ccac9b | |||
| efbb2f6681 | |||
| 3b900eafe5 | |||
| 4d6006dbf9 | |||
| c0fbfcaa1b | |||
| 4e2e03d0fb | |||
| c2f96568a7 | |||
| 4cfece1a50 | |||
| f481a4807f | |||
| 7b532adb2c | |||
| 2fa62394ef | |||
| aecca3fc88 | |||
| 204e2c6f0f | |||
| ab03d3166d | |||
| 1045e01a55 | |||
| c01be3f909 | |||
| 7ff4bf64f7 | |||
| c3b50a23f6 | |||
| 2878e69c22 | |||
| 44d04eaa77 | |||
| 99de32cf57 | |||
| 80c22ecb6d | |||
| cf79d84d00 | |||
| 9a0a44fdde | |||
| 0876efc5ec | |||
| 51575a7b62 | |||
| a8a51c836e | |||
| 5996a9306e | |||
| f4f4768bfc | |||
| 5a9a138463 | |||
| ae601c1445 | |||
| c5cd274b0f | |||
| 07a53da918 | |||
| d52c87cacb | |||
| 6e32f0539d | |||
| 2aaf18f29f | |||
| 9892f3de2d | |||
|
|
c06a325c42 | ||
|
|
7897100659 | ||
|
|
51793077bd | ||
| ada29fb2b4 | |||
|
|
306f62f7ca | ||
|
|
50e2bd375d | ||
| 7d73845a28 | |||
| 052e8e8b82 | |||
|
|
e590928e67 | ||
|
|
8346eed8f4 | ||
|
|
2bde16523e | ||
|
|
0a34a90acc | ||
| 0f5d2cb34e | |||
|
|
9cec7bccdc | ||
|
|
faf8c39fe9 | ||
| 2480c8c1f0 | |||
|
|
81950699b3 | ||
| 47ea47cddc | |||
|
|
2404a9c532 | ||
| f039d798ae | |||
| 781d490667 | |||
|
|
2b8a026c0c | ||
|
|
82518af2d6 | ||
|
|
bb98d6bb1b | ||
|
|
230038f6f3 | ||
|
|
f8980446a8 | ||
|
|
643b84aece | ||
|
|
05850394ed | ||
|
|
2f1c4a5cc1 | ||
|
|
bec55fd51a | ||
|
|
e01fb0460f | ||
| e46fccf504 | |||
|
|
e6183780b3 | ||
| 74a1ef7fd3 | |||
| 1414c902d0 | |||
|
|
f7b30bebc0 | ||
| 564289a114 | |||
| a08daf142b | |||
|
|
bb1f3fba30 | ||
|
|
1aaf7ef639 | ||
| 66549aea4d | |||
| 7c6b215d89 | |||
|
|
042e25a3dc | ||
|
|
e2410531ab | ||
| b1bf37b859 | |||
|
|
93891313b3 | ||
|
|
2db3f2b872 | ||
| 1fb91e9bfa | |||
| 7e50147d0b | |||
|
|
6e469b2c18 | ||
|
|
f26c1ed0ef | ||
|
|
23ba2bdf0c | ||
|
|
53c871d61d | ||
| cb81e6e9e4 | |||
|
|
e53df4e5e9 | ||
|
|
25c7269029 | ||
|
|
8b290ebc3f | ||
| 4af10c74e9 | |||
|
|
7883ef93be | ||
|
|
398cb33a58 | ||
|
|
ac1858fba7 | ||
|
|
580db768cb | ||
|
|
5a77a5e9e0 | ||
| 8a4aea7464 | |||
|
|
5fa241fa64 | ||
| 2e6dc5ead2 | |||
|
|
a1ec3cd4c1 | ||
|
|
1896b72469 | ||
|
|
80a83cba4c | ||
|
|
d73824007e | ||
|
|
a4409c96d5 | ||
|
|
cc3ec5022a | ||
| fb7a372f29 | |||
| c3dbae9edb | |||
|
|
b93434887d | ||
| dc5a28dbef | |||
|
|
53271b21cf | ||
|
|
17d2422690 | ||
|
|
55d72c3f82 | ||
|
|
74f685e07d | ||
|
|
e3826148fa | ||
|
|
bf52f86b3d | ||
|
|
566e84f53f | ||
| 74240104b9 | |||
| 1d0a9e468b | |||
| 2d3cee1121 | |||
|
|
2be6d5e122 | ||
| 6376ff4ccf | |||
|
|
dd20060754 | ||
|
|
298b337392 | ||
| 377ec4de09 | |||
|
|
033da4d544 | ||
|
|
1398ffc064 | ||
| 247f3f9b49 | |||
|
|
cec2e82550 | ||
|
|
ac5f425624 | ||
| 5646b589e0 | |||
|
|
5a1716341b | ||
| f943db17e0 | |||
|
|
d7a9cb2a20 | ||
| de34c33706 | |||
|
|
10b8da2e21 | ||
|
|
ca4e4c7feb | ||
| 4108371c20 | |||
|
|
e5c8624d9b | ||
| 561330697b | |||
|
|
f7b2c0d4c0 | ||
|
|
c3978a1e3c | ||
| 7f8fdc2eb8 | |||
|
|
bcd4fc5abd | ||
|
|
6a90bbb263 | ||
| 7e061d18bb | |||
|
|
0afe1de9bd | ||
|
|
98874bedc9 | ||
|
|
72cbe4f066 | ||
| b144d6d94c | |||
|
|
da7329ffb3 | ||
| 9511b594aa | |||
|
|
46ede050e1 | ||
| 0a6796a9bc | |||
|
|
838568f4cd | ||
| 598e02444f | |||
|
|
25eaa83f6e | ||
| 64e9f1e911 | |||
|
|
ceb57382d9 | ||
|
|
0491d096f9 | ||
| 452429ee9c | |||
|
|
a465c80ed7 | ||
|
|
fa462be666 | ||
|
|
a02acc3e73 | ||
| 2b745f362b | |||
|
|
5f5652170f | ||
| 0e41d954ca | |||
|
|
f0f248b018 | ||
|
|
721a0af8c0 | ||
|
|
977ff5497b | ||
|
|
1e17d326a0 | ||
|
|
2d78681d9d | ||
|
|
31f6c3a9ec | ||
|
|
00df478c32 | ||
|
|
5c05eb62ff | ||
|
|
efe0a0f136 | ||
| 3eeb808e07 | |||
| 56a3e12521 | |||
|
|
6ce48c8881 | ||
| eb0881ae6b | |||
| c88dcd7316 | |||
|
|
0ab2fdea66 | ||
|
|
1ee069afca | ||
|
|
ce131235c1 | ||
|
|
58369560b9 | ||
| c3472c5b31 | |||
|
|
c4bf7dc364 | ||
| 64018b4314 | |||
|
|
7707901f29 | ||
|
|
1eb77df403 | ||
| b2c5607eca | |||
|
|
22dd286d29 | ||
| d0d02cea2f | |||
|
|
460a664d7d | ||
| 5e198d9c68 | |||
|
|
963a3bb7cd | ||
| 5e39963eea | |||
|
|
71c7af6a9e | ||
| b59073247c | |||
|
|
8271f663a1 | ||
| eb8d37c340 | |||
|
|
d65a839878 | ||
| ce83a08eb8 | |||
| 7679c9a0d9 | |||
|
|
23eca69a3c | ||
| 1362d7a5cf | |||
|
|
8963d2a5df | ||
|
|
f2726ddc7a | ||
|
|
fa09da4e56 | ||
| 52185ed7da | |||
| 355a1c70e5 | |||
| a808310a7c | |||
|
|
839a9e2054 | ||
|
|
471ca23585 | ||
|
|
56be1767bb | ||
|
|
b66d53da1b | ||
|
|
ec25013cb8 | ||
|
|
2f2c23a803 | ||
|
|
17c01625be | ||
|
|
c464f0cfa1 | ||
|
|
4b9d6a41bf | ||
|
|
0411d564b9 | ||
|
|
cc365f979e | ||
|
|
6435bd28ac | ||
|
|
9b190e7dfa | ||
|
|
e7f2263860 | ||
|
|
e7b5cfd9b8 | ||
|
|
a4b7c59919 |
@@ -28,12 +28,17 @@ android {
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.winboll
|
||||
}
|
||||
debug {
|
||||
signingConfig signingConfigs.winboll
|
||||
}
|
||||
signingConfig signingConfigs.winboll
|
||||
}
|
||||
release {
|
||||
signingConfig signingConfigs.winboll
|
||||
minifyEnabled true // 开启混淆(核心开关)
|
||||
shrinkResources true // 可选:移除无用资源(进一步减小体积)
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), // 官方默认规则(优化版)
|
||||
'proguard-rules.pro' // 自定义规则文件
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
flavorDimensions "WinBoLLApp"
|
||||
@@ -71,6 +76,7 @@ android {
|
||||
// 2. 配置 Beta Debug 版应用包输出
|
||||
//
|
||||
if((variant.flavorName == "beta" && variant.buildType.name == "debug")
|
||||
|| (variant.flavorName == "beta" && variant.buildType.name == "release")
|
||||
|| (variant.flavorName == "stage" && variant.buildType.name == "debug")
|
||||
|| (variant.flavorName == "stage" && variant.buildType.name == "release")) {
|
||||
println "Project root directory: " + project.rootDir.toString()
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
|
||||
########
|
||||
## ☁ ☁ ☁ WinBoLL APP ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ WinBoLL Studio Android 应用开源项目。☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ WinBoLL 网站地址 https://www.winboll.cc/ ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ WinBoLL 源码地址 <https://gitea.winboll.cc/Studio/APPBase> ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ GitHub 源码地址 <https://github.com/ZhanGSKen/APPBase.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
# ☁ ☁ ☁ 码云 源码地址 <https://gitee.com/zhangsken/appbase.git> ☁ ☁ ☁ ☁ ☁ ☁ ☁ ☁
|
||||
|
||||
## WinBoLL 提问
|
||||
同样是 /sdcard 目录,在开发 Android 应用时,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# AES
|
||||
|
||||
#### 介绍
|
||||
安卓视图元素类库
|
||||
WinBoLL 安卓可视化元素类库测试应用。
|
||||
|
||||
#### 软件架构
|
||||
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||
@@ -32,4 +32,4 @@
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
|
||||
#### 参考文档
|
||||
#### 参考文档
|
||||
|
||||
@@ -18,28 +18,30 @@ 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.aes"
|
||||
minSdkVersion 24
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
// versionName 更新后需要手动设置
|
||||
// 项目模块目录的 build.gradle 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.10"
|
||||
versionName "15.11"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
// 米盟 SDK
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libmimo_1011.so"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Sep 29 13:04:18 HKT 2025
|
||||
stageCount=3
|
||||
#Wed Nov 26 15:54:26 GMT 2025
|
||||
stageCount=7
|
||||
libraryProject=libaes
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.2
|
||||
buildCount=0
|
||||
baseBetaVersion=15.10.3
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.6
|
||||
buildCount=32
|
||||
baseBetaVersion=15.11.7
|
||||
|
||||
132
aes/proguard-rules.pro
vendored
132
aes/proguard-rules.pro
vendored
@@ -9,9 +9,129 @@
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
# ============================== 基础通用规则 ==============================
|
||||
# 保留系统组件
|
||||
-keep public class * extends android.app.Activity
|
||||
-keep public class * extends android.app.Service
|
||||
-keep public class * extends android.content.BroadcastReceiver
|
||||
-keep public class * extends android.content.ContentProvider
|
||||
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||
-keep public class * extends android.preference.Preference
|
||||
|
||||
# 保留 WinBoLL 核心包及子类(合并简化规则)
|
||||
-keep class cc.winboll.studio.** { *; }
|
||||
-keepclassmembers class cc.winboll.studio.** { *; }
|
||||
|
||||
# 保留所有类中的 public static final String TAG 字段(便于日志定位)
|
||||
-keepclassmembers class * {
|
||||
public static final java.lang.String TAG;
|
||||
}
|
||||
|
||||
# 保留序列化类(避免Parcelable/Gson解析异常)
|
||||
-keep class * implements android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator *;
|
||||
}
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
|
||||
# 保留 R 文件(避免资源ID混淆)
|
||||
-keepclassmembers class **.R$* {
|
||||
public static <fields>;
|
||||
}
|
||||
|
||||
# 保留 native 方法(避免JNI调用失败)
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# 保留注解和泛型(避免反射/序列化异常)
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Signature
|
||||
|
||||
# 屏蔽 Java 8+ 警告(适配 Java 7 语法)
|
||||
-dontwarn java.lang.invoke.*
|
||||
-dontwarn android.support.v8.renderscript.*
|
||||
-dontwarn java.util.function.**
|
||||
|
||||
# ============================== 第三方框架专项规则 ==============================
|
||||
# OkHttp 4.4.1(米盟广告请求依赖,完善Lambda兼容)
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-keep class okhttp3.internal.** { *; }
|
||||
-keep class okio.** { *; }
|
||||
-dontwarn okhttp3.internal.platform.**
|
||||
-dontwarn okio.**
|
||||
|
||||
# Glide 4.9.0(米盟广告图片加载依赖)
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
-keepclassmembers class * implements com.bumptech.glide.module.AppGlideModule {
|
||||
<init>();
|
||||
}
|
||||
-dontwarn com.bumptech.glide.**
|
||||
|
||||
# Gson 2.8.5(米盟广告数据序列化依赖)
|
||||
-keep class com.google.gson.** { *; }
|
||||
-keep interface com.google.gson.** { *; }
|
||||
-keepclassmembers class * {
|
||||
@com.google.gson.annotations.SerializedName <fields>;
|
||||
}
|
||||
|
||||
# 米盟 SDK(核心广告组件,完整保留避免加载失败)
|
||||
-keep class com.miui.zeus.** { *; }
|
||||
-keep interface com.miui.zeus.** { *; }
|
||||
# 保留米盟日志字段(便于广告加载失败排查)
|
||||
-keepclassmembers class com.miui.zeus.mimo.sdk.** {
|
||||
public static final java.lang.String TAG;
|
||||
}
|
||||
|
||||
# RecyclerView 1.0.0(米盟广告布局渲染依赖)
|
||||
-keep class androidx.recyclerview.** { *; }
|
||||
-keep interface androidx.recyclerview.** { *; }
|
||||
-keepclassmembers class androidx.recyclerview.widget.RecyclerView$Adapter {
|
||||
public *;
|
||||
}
|
||||
|
||||
# 其他第三方框架(按引入依赖保留,无则可删除)
|
||||
# XXPermissions 18.63
|
||||
-keep class com.hjq.permissions.** { *; }
|
||||
-keep interface com.hjq.permissions.** { *; }
|
||||
|
||||
# ZXing 二维码(核心解析组件)
|
||||
-keep class com.google.zxing.** { *; }
|
||||
-keep class com.journeyapps.zxing.** { *; }
|
||||
|
||||
# Jsoup HTML解析
|
||||
-keep class org.jsoup.** { *; }
|
||||
|
||||
# Pinyin4j 拼音搜索
|
||||
-keep class net.sourceforge.pinyin4j.** { *; }
|
||||
|
||||
# JSch SSH组件
|
||||
-keep class com.jcraft.jsch.** { *; }
|
||||
|
||||
# AndroidX 基础组件
|
||||
-keep class androidx.appcompat.** { *; }
|
||||
-keep interface androidx.appcompat.** { *; }
|
||||
|
||||
# ============================== 优化与调试配置 ==============================
|
||||
# 优化级别(平衡混淆效果与性能)
|
||||
-optimizationpasses 5
|
||||
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||
|
||||
# 调试辅助(保留行号便于崩溃定位)
|
||||
-verbose
|
||||
-dontpreverify
|
||||
-dontusemixedcaseclassnames
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
|
||||
<activity android:name=".TestActivityManagerActivity"/>
|
||||
|
||||
<activity android:name=".SettingsActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -83,11 +83,11 @@ public class AboutActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
appInfo.setAppGitOwner("Studio");
|
||||
appInfo.setAppGitAPPBranch(szBranchName);
|
||||
appInfo.setAppGitAPPSubProjectFolder(szBranchName);
|
||||
appInfo.setAppHomePage("https://discuz.winboll.cc/forum.php?mod=viewthread&tid=3&extra=page%3D1");
|
||||
appInfo.setAppHomePage("https://www.winboll.cc/apks/index.php?project=AES");
|
||||
appInfo.setAppAPKName("AES");
|
||||
appInfo.setAppAPKFolderName("AES");
|
||||
//appInfo.setIsAddDebugTools(false);
|
||||
appInfo.setIsAddDebugTools(BuildConfig.DEBUG);
|
||||
//appInfo.setIsAddDebugTools(BuildConfig.DEBUG);
|
||||
return new AboutView(mContext, appInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ package cc.winboll.studio.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;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
@@ -19,15 +18,16 @@ public class App extends GlobalApplication {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
setIsDebugging(BuildConfig.DEBUG);
|
||||
WinBoLLActivityManager.init(this);
|
||||
|
||||
// 初始化 Toast 框架
|
||||
ToastUtils.init(this);
|
||||
// 设置 Toast 布局样式
|
||||
//ToastUtils.setView(R.layout.view_toast);
|
||||
ToastUtils.setStyle(new WhiteToastStyle());
|
||||
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
ToastUtils.release();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,9 +90,9 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActi
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.toolbar_library, menu);
|
||||
if(App.isDebuging()) {
|
||||
getMenuInflater().inflate(cc.winboll.studio.libapputils.R.menu.toolbar_studio_debug, menu);
|
||||
getMenuInflater().inflate(R.menu.toolbar_main, menu);
|
||||
if(App.isDebugging()) {
|
||||
getMenuInflater().inflate(cc.winboll.studio.libaes.R.menu.toolbar_studio_debug, menu);
|
||||
}
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
@@ -185,8 +185,10 @@ public class MainActivity extends DrawerFragmentActivity implements IWinBoLLActi
|
||||
} else if (nItemId == R.id.item_drawerfragmentactivity) {
|
||||
Intent intent = new Intent(this, TestDrawerFragmentActivity.class);
|
||||
startActivity(intent);
|
||||
}
|
||||
else if (nItemId == R.id.item_about) {
|
||||
} else if (nItemId == R.id.item_settings) {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (nItemId == R.id.item_about) {
|
||||
Intent intent = new Intent(this, AboutActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package cc.winboll.studio.aes;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import cc.winboll.studio.libaes.enums.ADsMode;
|
||||
import cc.winboll.studio.libaes.views.ADsControlView;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/26 18:01
|
||||
* @Describe SettingsActivity
|
||||
*/
|
||||
public class SettingsActivity extends Activity {
|
||||
|
||||
public static final String TAG = "SettingsActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_settings);
|
||||
ADsControlView adsControlView = (ADsControlView) findViewById(R.id.ads_control_view);
|
||||
|
||||
// adsControlView.setOnAdsModeSelectedListener(new ADsControlView.OnAdsModeSelectedListener() {
|
||||
// @Override
|
||||
// public void onModeSelected(ADsMode selectedMode) {
|
||||
// if (selectedMode == ADsMode.STANDALONE) {
|
||||
// // 处理单机模式逻辑(如释放米盟资源)
|
||||
// ToastUtils.show("STANDALONE");
|
||||
// } else if (selectedMode == ADsMode.MIMO_SDK) {
|
||||
// // 处理米盟SDK模式逻辑(如初始化SDK)
|
||||
// ToastUtils.show("MIMO_SDK");
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
}
|
||||
|
||||
}
|
||||
12
aes/src/main/res/layout/activity_settings.xml
Normal file
12
aes/src/main/res/layout/activity_settings.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?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">
|
||||
<cc.winboll.studio.libaes.views.ADsControlView
|
||||
android:id="@+id/ads_control_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
@@ -32,4 +32,7 @@
|
||||
<item
|
||||
android:id="@+id/item_drawerfragmentactivity"
|
||||
android:title="Test DrawerFragmentActivity"/>
|
||||
<item
|
||||
android:id="@+id/item_settings"
|
||||
android:title="Settings"/>
|
||||
</menu>
|
||||
@@ -38,13 +38,12 @@ android {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 Java 7 兼容性(已适配项目技术栈)
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Nov 13 05:16:16 HKT 2025
|
||||
stageCount=1
|
||||
#Fri Nov 21 11:41:04 HKT 2025
|
||||
stageCount=2
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.0
|
||||
publishVersion=15.11.1
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.1
|
||||
baseBetaVersion=15.11.2
|
||||
|
||||
109
appbase/proguard-rules.pro
vendored
109
appbase/proguard-rules.pro
vendored
@@ -15,3 +15,112 @@
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# ============================== 基础通用规则 ==============================
|
||||
# 保留系统组件
|
||||
-keep public class * extends android.app.Activity
|
||||
-keep public class * extends android.app.Service
|
||||
-keep public class * extends android.content.BroadcastReceiver
|
||||
-keep public class * extends android.content.ContentProvider
|
||||
-keep public class * extends android.app.backup.BackupAgentHelper
|
||||
-keep public class * extends android.preference.Preference
|
||||
|
||||
# 保留 WinBoLL 核心包及子类(适配你的两个包名)
|
||||
#-keep public class * extends com.winboll.WinBoLLActivity
|
||||
#-keep public class * extends com.winboll.WinBoLLFragment
|
||||
# 主包名
|
||||
-keep class cc.winboll.studio.*.** { *; }
|
||||
# beta包名
|
||||
-keep class cc.winboll.studio.*.beta.** { *; }
|
||||
-keepclassmembers class cc.winboll.studio.*.** { *; }
|
||||
-keepclassmembers class cc.winboll.studio.*.beta.** { *; }
|
||||
|
||||
# 保留所有类中的 public static final String TAG 字段
|
||||
-keepclassmembers class * {
|
||||
public static final java.lang.String TAG;
|
||||
}
|
||||
|
||||
# 保留序列化类
|
||||
-keep class * implements android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator *;
|
||||
}
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
|
||||
# 保留 R 文件
|
||||
-keepclassmembers class **.R$* {
|
||||
public static <fields>;
|
||||
}
|
||||
|
||||
# 保留 native 方法
|
||||
-keepclasseswithmembernames class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
# 保留注解和泛型
|
||||
-keepattributes *Annotation*
|
||||
-keepattributes Signature
|
||||
|
||||
# 屏蔽 Java 8+ 警告(适配 Java 7)
|
||||
-dontwarn java.lang.invoke.*
|
||||
-dontwarn android.support.v8.renderscript.*
|
||||
-dontwarn java.util.function.**
|
||||
|
||||
# ============================== 第三方框架规则 ==============================
|
||||
# Retrofit + OkHttp
|
||||
-keep class retrofit2.** { *; }
|
||||
-keep interface retrofit2.** { *; }
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-keep class okio.** { *; }
|
||||
-keepclasseswithmembers class * {
|
||||
@retrofit2.http.* <methods>;
|
||||
}
|
||||
|
||||
# Glide 4.x
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$ImageType {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
-dontwarn com.bumptech.glide.load.resource.bitmap.VideoDecoder
|
||||
|
||||
# GreenDAO 3.x
|
||||
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
|
||||
public static java.lang.String TABLENAME;
|
||||
}
|
||||
-keep class **$Properties
|
||||
# 实体类包名(按实际调整)
|
||||
#-keep class cc.winboll.studio.appbase.model.** { *; }
|
||||
|
||||
# ButterKnife 8.x
|
||||
-keep class butterknife.** { *; }
|
||||
-dontwarn butterknife.internal.**
|
||||
-keep class **$$ViewBinder { *; }
|
||||
-keepclasseswithmembernames class * {
|
||||
@butterknife.BindView <fields>;
|
||||
@butterknife.OnClick <methods>;
|
||||
}
|
||||
|
||||
# EventBus 3.x
|
||||
-keepclassmembers class ** {
|
||||
@org.greenrobot.eventbus.Subscribe <methods>;
|
||||
}
|
||||
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
|
||||
|
||||
# ============================== 优化与调试 ==============================
|
||||
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
|
||||
-optimizationpasses 5
|
||||
-verbose
|
||||
-dontpreverify
|
||||
-dontusemixedcaseclassnames
|
||||
# 保留行号(便于崩溃定位)
|
||||
-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
|
||||
@@ -11,51 +11,57 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:spacing="12dp">
|
||||
android:layout_weight="1.0">
|
||||
|
||||
<Button
|
||||
<LinearLayout
|
||||
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="onCrashTest"
|
||||
android:layout_margin="10dp"/>
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:spacing="12dp">
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="应用日志测试"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="#81C7F5"
|
||||
android:paddingVertical="12dp"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:onClick="onLogTest"
|
||||
android:layout_margin="10dp"/>
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="应用崩溃测试"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="#81C7F5"
|
||||
android:paddingVertical="12dp"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:onClick="onCrashTest"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="应用吐司测试"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="#81C7F5"
|
||||
android:paddingVertical="12dp"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:onClick="onToastUtilsTest"
|
||||
android:layout_margin="10dp"/>
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="应用日志测试"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="#81C7F5"
|
||||
android:paddingVertical="12dp"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:onClick="onLogTest"
|
||||
android:layout_margin="10dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
<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"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
35
apputils/README.md
Normal file
35
apputils/README.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# APPUtils
|
||||
|
||||
#### 介绍
|
||||
应用开发工具套件类
|
||||
|
||||
#### 软件架构
|
||||
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||
也适配安卓应用 [AndroidIDE] 的 Gradle 编译结构。
|
||||
|
||||
|
||||
#### Gradle 编译说明
|
||||
调试版编译命令 :gradle assembleBetaDebug
|
||||
阶段版编译命令 :git pull && bash .winboll/bashPublishAPKAddTag.sh apputils
|
||||
阶段版类库发布命令 :git pull &&bash .winboll/bashPublishLIBAddTag.sh libapputils
|
||||
|
||||
#### 使用说明
|
||||
|
||||
#### 参与贡献
|
||||
|
||||
1. Fork 本仓库
|
||||
2. 新建 Feat_xxx 分支
|
||||
3. 提交代码 : ZhanGSKen(ZhanGSKen<zhangsken@188.com>)
|
||||
4. 新建 Pull Request
|
||||
|
||||
|
||||
#### 特技
|
||||
|
||||
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
||||
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
||||
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
||||
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
||||
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
||||
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
||||
|
||||
#### 参考文档
|
||||
@@ -29,7 +29,7 @@ android {
|
||||
// versionName 更新后需要手动设置
|
||||
// 项目模块目录的 build.gradle 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.8"
|
||||
versionName "15.10"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Sep 01 07:56:33 HKT 2025
|
||||
stageCount=7
|
||||
#Mon Sep 29 01:16:05 HKT 2025
|
||||
stageCount=3
|
||||
libraryProject=libapputils
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.6
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.2
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.7
|
||||
baseBetaVersion=15.10.3
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.apputils">
|
||||
|
||||
|
||||
<!-- 读取外部存储权限(Android 10 以下) -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
android:name=".App"
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/ic_winboll"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/MyUtilsTheme"
|
||||
android:supportsRtl="true">
|
||||
android:supportsRtl="true"
|
||||
android:resizeableActivity="true"
|
||||
android:screenOrientation="unspecified"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
@@ -35,6 +41,8 @@
|
||||
|
||||
<activity android:name=".QRCodeDecodeActivity"/>
|
||||
|
||||
<activity android:name=".QRGeneratorActivity"/>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -15,12 +15,11 @@ import android.view.MenuItem;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.apputils.R;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import cc.winboll.studio.libapputils.views.SimpleWebView;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class AssetsHtmlActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
public class AssetsHtmlActivity extends Activity {
|
||||
|
||||
public static final String TAG = "AssetsHtmlActivity";
|
||||
|
||||
@@ -32,16 +31,6 @@ public class AssetsHtmlActivity extends WinBoLLActivity implements IWinBoLLActiv
|
||||
|
||||
// Assets 文件夹里的 Html 文件的名称
|
||||
String mszHtmlFileName;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
|
||||
@@ -15,9 +15,10 @@ import android.widget.Toolbar;
|
||||
import cc.winboll.studio.apputils.R;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import cc.winboll.studio.libappbase.LogActivity;
|
||||
|
||||
final public class MainActivity extends Activity {
|
||||
|
||||
@@ -26,21 +27,21 @@ final public class MainActivity extends Activity {
|
||||
public static final int REQUEST_QRCODEDECODE_ACTIVITY = 0;
|
||||
|
||||
Toolbar mToolbar;
|
||||
LogView mLogView;
|
||||
//LogView mLogView;
|
||||
//
|
||||
// @Override
|
||||
// public Activity getActivity() {
|
||||
// return this;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
mLogView = findViewById(R.id.logview);
|
||||
mLogView.start();
|
||||
// mLogView = findViewById(R.id.logview);
|
||||
// mLogView.start();
|
||||
|
||||
// 初始化工具栏
|
||||
mToolbar = findViewById(R.id.toolbar);
|
||||
@@ -145,13 +146,21 @@ final public class MainActivity extends Activity {
|
||||
}
|
||||
|
||||
public void onTestLogActivity(View view) {
|
||||
// Intent intent = new Intent(this, LogActivity.class);
|
||||
// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
// intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
// startActivity(intent);
|
||||
/* 分屏代码有效
|
||||
// 1. 创建启动 SecondActivity 的 Intent
|
||||
Intent splitIntent = new Intent(MainActivity.this, LogActivity.class);
|
||||
|
||||
//WinBoLLActivityManager.getInstance().printAvtivityListInfo();
|
||||
//WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, LogActivity.class);
|
||||
// 2. 添加分屏启动必需的两个标志(API 30 兼容)
|
||||
// FLAG_ACTIVITY_LAUNCH_ADJACENT:相邻分屏显示
|
||||
// FLAG_ACTIVITY_NEW_TASK:分屏需要新任务栈
|
||||
splitIntent.addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
|
||||
| Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
|
||||
// 3. 启动分屏活动(若设备不支持分屏,会默认全屏启动)
|
||||
startActivity(splitIntent);
|
||||
*/
|
||||
|
||||
LogActivity.startLogActivity(this);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -217,10 +226,9 @@ final public class MainActivity extends Activity {
|
||||
if (item.getItemId() == R.id.item_exit) {
|
||||
//exit();
|
||||
return true;
|
||||
// } else if (item.getItemId() == R.id.item_teststringtoqrcodeview) {
|
||||
// Intent intent = new Intent(this, TestStringToQRCodeViewActivity.class);
|
||||
// startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY);
|
||||
// //WinBoLLActivityManager.getInstance(this).startWinBoLLActivity(this, TestStringToQrCodeViewActivity.class);
|
||||
} else if (item.getItemId() == R.id.item_testqrgeneratoractivity) {
|
||||
Intent intent = new Intent(this, QRGeneratorActivity.class);
|
||||
startActivity(intent);
|
||||
} else if (item.getItemId() == R.id.item_testqrcodedecodeactivity) {
|
||||
Intent intent = new Intent(this, QRCodeDecodeActivity.class);
|
||||
startActivityForResult(intent, REQUEST_QRCODEDECODE_ACTIVITY);
|
||||
@@ -268,7 +276,7 @@ final public class MainActivity extends Activity {
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void onTestAssetsHtmlActivity(View view) {
|
||||
Intent intent = new Intent(this, AssetsHtmlActivity.class);
|
||||
intent.putExtra(AssetsHtmlActivity.EXTRA_HTMLFILENAME, "javascript_test.html");
|
||||
@@ -281,7 +289,7 @@ final public class MainActivity extends Activity {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
mLogView.start();
|
||||
//mLogView.start();
|
||||
}
|
||||
|
||||
/*@Override
|
||||
|
||||
@@ -1,89 +1,323 @@
|
||||
package cc.winboll.studio.apputils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/01/18 10:32:21
|
||||
* @Describe 二维码扫码解码窗口
|
||||
* @Describe 二维码解码窗口
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toolbar;
|
||||
import cc.winboll.studio.apputils.R;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.LuminanceSource;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.RGBLuminanceSource;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.ResultPoint;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.journeyapps.barcodescanner.BarcodeCallback;
|
||||
import com.journeyapps.barcodescanner.BarcodeResult;
|
||||
import com.journeyapps.barcodescanner.DecoratedBarcodeView;
|
||||
import com.journeyapps.barcodescanner.DefaultDecoderFactory;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
public class QRCodeDecodeActivity extends Activity {
|
||||
|
||||
public static final String TAG = "QRCodeDecodeActivity";
|
||||
|
||||
public static final String EXTRA_RESULT = "EXTRA_RESULT";
|
||||
private static final int REQUEST_CAMERA_PERMISSION = 1;
|
||||
private static final int REQUEST_PICK_IMAGE = 2;
|
||||
private static final int REQUEST_READ_STORAGE_PERMISSION = 3;
|
||||
// 图片压缩阈值:超过1000px时压缩,避免内存溢出和解码效率低
|
||||
private static final int MAX_BITMAP_SIZE = 1000;
|
||||
|
||||
TextView resultTextView;
|
||||
DecoratedBarcodeView barcodeView;
|
||||
|
||||
// @Override
|
||||
// public Activity getActivity() {
|
||||
// return this;
|
||||
// }
|
||||
private TextView resultTextView;
|
||||
private DecoratedBarcodeView barcodeView;
|
||||
private Button btnDecodeFromAlbum;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_qrcodedecode);
|
||||
|
||||
// 初始化工具栏
|
||||
// Toolbar mToolbar = findViewById(R.id.toolbar);
|
||||
// setActionBar(mToolbar);
|
||||
|
||||
//resultTextView = findViewById(R.id.activityqrcodedecodeTextView1);
|
||||
barcodeView = findViewById(R.id.activityqrcodedecodeDecoratedBarcodeView1);
|
||||
// 请求相机权限
|
||||
// if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA)
|
||||
// != PackageManager.PERMISSION_GRANTED) {
|
||||
// ActivityCompat.requestPermissions(this,
|
||||
// new String[]{android.Manifest.permission.CAMERA},
|
||||
// REQUEST_CAMERA_PERMISSION);
|
||||
// } else {
|
||||
// startScanning();
|
||||
// }
|
||||
startScanning();
|
||||
initToolbar();
|
||||
initViews();
|
||||
checkCameraPermission();
|
||||
setAlbumDecodeClickListener();
|
||||
}
|
||||
|
||||
private void startScanning() {
|
||||
barcodeView.getBarcodeView().setDecoderFactory(null);
|
||||
barcodeView.decodeContinuous(barcodeCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode,
|
||||
String[] permissions, int[] grantResults) {
|
||||
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startScanning();
|
||||
} else {
|
||||
// 权限被拒绝的处理
|
||||
private void initToolbar() {
|
||||
Toolbar mToolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
if (mToolbar != null) {
|
||||
setActionBar(mToolbar);
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BarcodeCallback barcodeCallback = new BarcodeCallback(){
|
||||
private void initViews() {
|
||||
resultTextView = (TextView) findViewById(R.id.activityqrcodedecodeTextView1);
|
||||
barcodeView = (DecoratedBarcodeView) findViewById(R.id.activityqrcodedecodeDecoratedBarcodeView1);
|
||||
btnDecodeFromAlbum = (Button) findViewById(R.id.btn_decode_from_album);
|
||||
// 初始化扫码解码器(支持所有常见码制,避免仅支持QR_CODE的局限)
|
||||
List<BarcodeFormat> formats = new ArrayList<BarcodeFormat>();
|
||||
formats.add(BarcodeFormat.QR_CODE);
|
||||
formats.add(BarcodeFormat.CODE_128);
|
||||
formats.add(BarcodeFormat.EAN_13);
|
||||
barcodeView.getBarcodeView().setDecoderFactory(new DefaultDecoderFactory(formats));
|
||||
}
|
||||
|
||||
private void setAlbumDecodeClickListener() {
|
||||
btnDecodeFromAlbum.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(new String[]{android.Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
REQUEST_READ_STORAGE_PERMISSION);
|
||||
} else {
|
||||
openAlbum();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void openAlbum() {
|
||||
Intent pickImageIntent = new Intent(Intent.ACTION_PICK);
|
||||
pickImageIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
|
||||
startActivityForResult(pickImageIntent, REQUEST_PICK_IMAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
if (requestCode == REQUEST_PICK_IMAGE && resultCode == RESULT_OK && data != null) {
|
||||
Uri selectedImageUri = data.getData();
|
||||
if (selectedImageUri != null) {
|
||||
try {
|
||||
// 1. 读取图片并压缩(关键优化:避免大图片解码失败)
|
||||
InputStream imageStream = getContentResolver().openInputStream(selectedImageUri);
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true; // 先获取图片尺寸,不加载像素
|
||||
BitmapFactory.decodeStream(imageStream, null, options);
|
||||
imageStream.close(); // 关闭流,重新读取
|
||||
|
||||
// 计算压缩比例:超过MAX_BITMAP_SIZE时按比例压缩
|
||||
options.inSampleSize = calculateInSampleSize(options, MAX_BITMAP_SIZE, MAX_BITMAP_SIZE);
|
||||
options.inJustDecodeBounds = false; // 开始加载压缩后的像素
|
||||
imageStream = getContentResolver().openInputStream(selectedImageUri);
|
||||
Bitmap originalBitmap = BitmapFactory.decodeStream(imageStream, null, options);
|
||||
imageStream.close();
|
||||
|
||||
if (originalBitmap == null) {
|
||||
showToast("图片损坏,无法解析");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 图片预处理:转为灰度图+提高对比度(解决模糊/低对比度图片识别问题)
|
||||
Bitmap processedBitmap = processBitmap(originalBitmap);
|
||||
|
||||
// 3. 解码预处理后的图片
|
||||
String decodeResult = decodeQrFromBitmap(processedBitmap);
|
||||
|
||||
// 4. 结果处理
|
||||
if (decodeResult != null && !decodeResult.isEmpty()) {
|
||||
resultTextView.setText("图片解码结果:" + decodeResult);
|
||||
showDecodeResultDialog(decodeResult);
|
||||
returnResultToPreviousPage(decodeResult);
|
||||
} else {
|
||||
// 尝试直接解码原图(防止预处理过度导致识别失败)
|
||||
String originalResult = decodeQrFromBitmap(originalBitmap);
|
||||
if (originalResult != null && !originalResult.isEmpty()) {
|
||||
resultTextView.setText("图片解码结果:" + originalResult);
|
||||
showDecodeResultDialog(originalResult);
|
||||
returnResultToPreviousPage(originalResult);
|
||||
} else {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("解码失败")
|
||||
.setMessage("图片中未识别到二维码/条码,建议选择清晰、完整的图片")
|
||||
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
// 回收Bitmap,避免内存泄漏
|
||||
if (!originalBitmap.isRecycled()) originalBitmap.recycle();
|
||||
if (!processedBitmap.isRecycled() && processedBitmap != originalBitmap) {
|
||||
processedBitmap.recycle();
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
showToast("图片处理失败:" + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
showToast("未选择图片");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心优化1:计算图片压缩比例
|
||||
*/
|
||||
private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
|
||||
final int height = options.outHeight;
|
||||
final int width = options.outWidth;
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
// 找到最接近reqWidth/reqHeight的压缩比例(2的倍数,保证图片质量)
|
||||
while ((halfHeight / inSampleSize) >= reqHeight
|
||||
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心优化2:图片预处理(灰度化+提高对比度)
|
||||
* 解决模糊、低亮度、低对比度图片识别率低的问题
|
||||
*/
|
||||
private Bitmap processBitmap(Bitmap bitmap) {
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
|
||||
// 创建灰度图
|
||||
Bitmap grayBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(grayBitmap);
|
||||
Paint paint = new Paint();
|
||||
|
||||
// 1. 灰度化矩阵
|
||||
ColorMatrix grayMatrix = new ColorMatrix();
|
||||
grayMatrix.setSaturation(0); // 饱和度设为0,转为灰度
|
||||
|
||||
// 2. 提高对比度矩阵(alpha=1.5,亮度=0,可根据需求调整)
|
||||
ColorMatrix contrastMatrix = new ColorMatrix();
|
||||
contrastMatrix.set(new float[]{
|
||||
1.5f, 0, 0, 0, 0, // 红通道对比度
|
||||
0, 1.5f, 0, 0, 0, // 绿通道对比度
|
||||
0, 0, 1.5f, 0, 0, // 蓝通道对比度
|
||||
0, 0, 0, 1f, 0 // alpha通道不变
|
||||
});
|
||||
|
||||
// 合并灰度+对比度矩阵
|
||||
ColorMatrix combinedMatrix = new ColorMatrix();
|
||||
combinedMatrix.postConcat(grayMatrix);
|
||||
combinedMatrix.postConcat(contrastMatrix);
|
||||
|
||||
paint.setColorFilter(new ColorMatrixColorFilter(combinedMatrix));
|
||||
canvas.drawBitmap(bitmap, 0, 0, paint);
|
||||
|
||||
return grayBitmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心优化3:修复解码参数,支持更多场景
|
||||
*/
|
||||
private String decodeQrFromBitmap(Bitmap bitmap) {
|
||||
if (bitmap == null) return null;
|
||||
|
||||
try {
|
||||
int width = bitmap.getWidth();
|
||||
int height = bitmap.getHeight();
|
||||
int[] pixels = new int[width * height];
|
||||
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
|
||||
|
||||
// 修复1:使用RGBLuminanceSource,避免YUV格式导致的颜色偏差
|
||||
LuminanceSource source = new RGBLuminanceSource(width, height, pixels);
|
||||
BinaryBitmap binaryBitmap = new BinaryBitmap(new HybridBinarizer(source));
|
||||
|
||||
// 修复2:完善解码参数,解决模糊、变形二维码识别问题
|
||||
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>();
|
||||
// 支持所有常见码制(不仅限于QR_CODE)
|
||||
List<BarcodeFormat> formats = new ArrayList<BarcodeFormat>();
|
||||
formats.add(BarcodeFormat.QR_CODE);
|
||||
formats.add(BarcodeFormat.CODE_128);
|
||||
formats.add(BarcodeFormat.EAN_8);
|
||||
formats.add(BarcodeFormat.EAN_13);
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, formats);
|
||||
// 字符编码:支持中文等多语言
|
||||
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
|
||||
// 容错模式:允许二维码有一定损坏(关键!解决轻微变形/污染的二维码)
|
||||
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
|
||||
// 不使用纯条码模式,兼容带logo的二维码
|
||||
hints.put(DecodeHintType.PURE_BARCODE, Boolean.FALSE);
|
||||
|
||||
MultiFormatReader reader = new MultiFormatReader();
|
||||
reader.setHints(hints);
|
||||
Result result = reader.decode(binaryBitmap);
|
||||
return result.getText();
|
||||
|
||||
} catch (NotFoundException e) {
|
||||
// 正常未识别到,不打印异常(避免日志冗余)
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 原有逻辑(不变) ====================
|
||||
private void checkCameraPermission() {
|
||||
if (checkSelfPermission(android.Manifest.permission.CAMERA)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(new String[]{android.Manifest.permission.CAMERA},
|
||||
REQUEST_CAMERA_PERMISSION);
|
||||
} else {
|
||||
startScanning();
|
||||
}
|
||||
}
|
||||
|
||||
private void startScanning() {
|
||||
barcodeView.decodeContinuous(barcodeCallback);
|
||||
}
|
||||
|
||||
private BarcodeCallback barcodeCallback = new BarcodeCallback() {
|
||||
@Override
|
||||
public void barcodeResult(BarcodeResult result) {
|
||||
if (result.getText() != null) {
|
||||
//Toast.makeText(MainActivity.this, "Scanned: " + result.getText(), Toast.LENGTH_SHORT).show();
|
||||
//ToastUtils.show("Scanned: " + result.getText());
|
||||
if (result != null && result.getText() != null) {
|
||||
barcodeView.pause();
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_RESULT, result.getText());
|
||||
setResult(RESULT_OK, intent);
|
||||
finish();
|
||||
String decodeResult = result.getText();
|
||||
resultTextView.setText("扫码结果:" + decodeResult);
|
||||
showDecodeResultDialog(decodeResult);
|
||||
returnResultToPreviousPage(decodeResult);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +326,111 @@ public class QRCodeDecodeActivity extends Activity {
|
||||
}
|
||||
};
|
||||
|
||||
private void showDecodeResultDialog(String result) {
|
||||
ScrollView scrollView = new ScrollView(this);
|
||||
scrollView.setPadding(dip2px(16), dip2px(16), dip2px(16), dip2px(16));
|
||||
|
||||
TextView dialogTv = new TextView(this);
|
||||
dialogTv.setTextSize(16);
|
||||
dialogTv.setTextColor(getResources().getColor(android.R.color.black));
|
||||
dialogTv.setText(result);
|
||||
scrollView.addView(dialogTv);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("解码结果");
|
||||
builder.setView(scrollView);
|
||||
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
barcodeView.resume();
|
||||
}
|
||||
});
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private void returnResultToPreviousPage(String result) {
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_RESULT, result);
|
||||
setResult(RESULT_OK, intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == REQUEST_CAMERA_PERMISSION) {
|
||||
if (grantResults != null && grantResults.length > 0) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
startScanning();
|
||||
} else {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("权限申请")
|
||||
.setMessage("扫码需要相机权限,请在设置中开启")
|
||||
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
finish();
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
} else if (requestCode == REQUEST_READ_STORAGE_PERMISSION) {
|
||||
if (grantResults != null && grantResults.length > 0) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
openAlbum();
|
||||
} else {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("权限申请")
|
||||
.setMessage("从相册解码需要存储权限,请在设置中开启")
|
||||
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
barcodeView.resume();
|
||||
if (barcodeView != null) {
|
||||
barcodeView.resume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
barcodeView.pause();
|
||||
if (barcodeView != null) {
|
||||
barcodeView.pause();
|
||||
}
|
||||
}
|
||||
|
||||
private int dip2px(float dpValue) {
|
||||
final float scale = getResources().getDisplayMetrics().density;
|
||||
return (int) (dpValue * scale + 0.5f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(android.view.MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void showToast(String message) {
|
||||
android.widget.Toast.makeText(this, message, android.widget.Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package cc.winboll.studio.apputils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/09/22 07:09
|
||||
* @Describe 二维码生成窗口
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.journeyapps.barcodescanner.BarcodeEncoder;
|
||||
|
||||
public class QRGeneratorActivity extends Activity {
|
||||
public static final String TAG = "QrGeneratorActivity";
|
||||
|
||||
// 控件引用
|
||||
private EditText etInputText;
|
||||
private ImageView ivQrPreview;
|
||||
private Button btnGenerateQr;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_qrgenerator);
|
||||
|
||||
// 初始化控件
|
||||
initViews();
|
||||
// 设置按钮点击事件
|
||||
setGenerateClickListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化布局控件
|
||||
*/
|
||||
private void initViews() {
|
||||
etInputText = findViewById(R.id.et_input_text);
|
||||
ivQrPreview = findViewById(R.id.iv_qr_preview);
|
||||
btnGenerateQr = findViewById(R.id.btn_generate_qr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置生成按钮点击事件:获取输入文字 → 生成二维码 → 显示到 ImageView
|
||||
*/
|
||||
private void setGenerateClickListener() {
|
||||
btnGenerateQr.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// 1. 获取输入框文字(去除前后空格)
|
||||
String inputText = etInputText.getText().toString().trim();
|
||||
|
||||
// 2. 空输入判断
|
||||
if (inputText.isEmpty()) {
|
||||
Toast.makeText(QRGeneratorActivity.this, "请先输入要生成二维码的文字", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 生成二维码 Bitmap(宽高 500px,可调整)
|
||||
Bitmap qrBitmap = generateQrCodeBitmap(inputText, 500, 500);
|
||||
|
||||
// 4. 显示二维码到 ImageView
|
||||
if (qrBitmap != null) {
|
||||
ivQrPreview.setImageBitmap(qrBitmap);
|
||||
} else {
|
||||
Toast.makeText(QRGeneratorActivity.this, "二维码生成失败,请重试", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心方法:生成二维码 Bitmap
|
||||
* @param content 二维码内容(输入的文字)
|
||||
* @param width 二维码宽度(px)
|
||||
* @param height 二维码高度(px)
|
||||
* @return 生成的二维码 Bitmap,失败返回 null
|
||||
*/
|
||||
private Bitmap generateQrCodeBitmap(String content, int width, int height) {
|
||||
try {
|
||||
// 初始化二维码编码器(指定格式为 QR_CODE)
|
||||
BarcodeEncoder encoder = new BarcodeEncoder();
|
||||
// 生成二维码 Bitmap(参数:内容、格式、宽、高)
|
||||
return encoder.encodeBitmap(content, BarcodeFormat.QR_CODE, width, height);
|
||||
} catch (WriterException e) {
|
||||
// 生成失败(如内容过长、宽高非法),打印异常信息
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,9 @@ package cc.winboll.studio.apputils;
|
||||
* @Describe WinBoLLActivity
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
|
||||
public class WinBoLLActivity extends Activity implements IWinBoLLActivity {
|
||||
public class WinBoLLActivity extends Activity {
|
||||
|
||||
public static final String TAG = "WinBoLLActivity";
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTag() {
|
||||
return TAG;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,13 +54,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<cc.winboll.studio.libappbase.LogView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:text="Button"
|
||||
android:id="@+id/logview"
|
||||
android:layout_weight="1.0"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<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:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="QRCodeDecodeActivity"/>
|
||||
<!-- 工具栏(原有) -->
|
||||
<Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<!-- 扫码控件(原有) -->
|
||||
<com.journeyapps.barcodescanner.DecoratedBarcodeView
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/activityqrcodedecodeDecoratedBarcodeView1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<!-- 新增:从相册解码按钮 -->
|
||||
<Button
|
||||
android:id="@+id/btn_decode_from_album"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/activityqrcodedecodeDecoratedBarcodeView1"/>
|
||||
android:text="从相册选图解码"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/white"
|
||||
android:background="#2196F3"
|
||||
android:paddingVertical="12dp"
|
||||
android:layout_margin="16dp"/>
|
||||
|
||||
<!-- 结果显示TextView(原有) -->
|
||||
<TextView
|
||||
android:id="@+id/activityqrcodedecodeTextView1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:text="等待扫码或选择图片..."
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
45
apputils/src/main/res/layout/activity_qrgenerator.xml
Normal file
45
apputils/src/main/res/layout/activity_qrgenerator.xml
Normal file
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp"
|
||||
android:gravity="top|center_horizontal">
|
||||
|
||||
<!-- 文字输入框 -->
|
||||
<EditText
|
||||
android:id="@+id/et_input_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="请输入要生成二维码的文字"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="3"
|
||||
android:maxLines="5"
|
||||
android:padding="12dp"
|
||||
android:background="@android:drawable/editbox_background_normal"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- 生成二维码按钮 -->
|
||||
<Button
|
||||
android:id="@+id/btn_generate_qr"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="生成二维码"
|
||||
android:textSize="16sp"
|
||||
android:background="#4CAF50"
|
||||
android:textColor="@android:color/white"
|
||||
android:paddingVertical="12dp"
|
||||
android:layout_marginBottom="24dp"/>
|
||||
|
||||
<!-- 二维码预览图片 -->
|
||||
<ImageView
|
||||
android:id="@+id/iv_qr_preview"
|
||||
android:layout_width="280dp"
|
||||
android:layout_height="280dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="@android:drawable/dialog_holo_light_frame"
|
||||
android:contentDescription="二维码预览"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/item_teststringtoqrcodeview"
|
||||
android:title="TestStringToQRCodeViewActivity"/>
|
||||
android:id="@+id/item_testqrgeneratoractivity"
|
||||
android:title="TestQRGeneratorActivity"/>
|
||||
<item
|
||||
android:id="@+id/item_testqrcodedecodeactivity"
|
||||
android:title="TestQRCodeDecodeActivity"/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Tue Jun 24 09:54:47 HKT 2025
|
||||
stageCount=3
|
||||
#Sun Sep 28 12:44:55 HKT 2025
|
||||
stageCount=4
|
||||
libraryProject=
|
||||
baseVersion=15.2
|
||||
publishVersion=15.2.2
|
||||
publishVersion=15.2.3
|
||||
buildCount=0
|
||||
baseBetaVersion=15.2.3
|
||||
baseBetaVersion=15.2.4
|
||||
|
||||
20
build.gradle
20
build.gradle
@@ -5,6 +5,16 @@ buildscript {
|
||||
// 设置本地Maven仓库路径
|
||||
url 'file:///sdcard/.m2/repository/'
|
||||
}
|
||||
|
||||
//米盟通过maven接入时,要做如下配置
|
||||
maven {
|
||||
url "https://repos.xiaomi.com/maven"
|
||||
credentials {
|
||||
username 'mimo-developer'
|
||||
password 'AKCp8ih1PFG9tV8qaLyws67dLGZi8udFM39SfsHgihN15cgsiRvHuxj8JzFmuZjaViVeNawaA'
|
||||
}
|
||||
}
|
||||
|
||||
// Nexus Maven 库地址
|
||||
// "WinBoLL Release"
|
||||
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
|
||||
@@ -40,6 +50,15 @@ allprojects {
|
||||
// 设置本地Maven仓库路径
|
||||
url 'file:///sdcard/.m2/repository/'
|
||||
}
|
||||
|
||||
//米盟通过maven接入时,要做如下配置
|
||||
maven {
|
||||
url "https://repos.xiaomi.com/maven"
|
||||
credentials {
|
||||
username 'mimo-developer'
|
||||
password 'AKCp8ih1PFG9tV8qaLyws67dLGZi8udFM39SfsHgihN15cgsiRvHuxj8JzFmuZjaViVeNawaA'
|
||||
}
|
||||
}
|
||||
|
||||
// Nexus Maven 库地址
|
||||
// "WinBoLL Release"
|
||||
@@ -117,4 +136,3 @@ task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ def genVersionName(def versionName){
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 32
|
||||
buildToolsVersion "32.0.0"
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "cc.winboll.studio.contacts"
|
||||
@@ -66,7 +66,7 @@ dependencies {
|
||||
// 应用介绍页类库
|
||||
api 'io.github.medyo:android-about-page:2.0.0'
|
||||
// 吐司类库
|
||||
api 'com.github.getActivity:ToastUtils:10.5'
|
||||
//api 'com.github.getActivity:ToastUtils:10.5'
|
||||
// 网络连接类库
|
||||
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sun Aug 31 06:05:42 CST 2025
|
||||
stageCount=17
|
||||
#Mon Nov 03 12:01:02 HKT 2025
|
||||
stageCount=22
|
||||
libraryProject=
|
||||
baseVersion=15.3
|
||||
publishVersion=15.3.16
|
||||
publishVersion=15.3.21
|
||||
buildCount=0
|
||||
baseBetaVersion=15.3.17
|
||||
baseBetaVersion=15.3.22
|
||||
|
||||
@@ -7,8 +7,8 @@ package cc.winboll.studio.contacts;
|
||||
*/
|
||||
import android.view.Gravity;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.WinBoLLActivityManager;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
|
||||
@@ -30,7 +30,7 @@ public class App extends GlobalApplication {
|
||||
// 设置 Toast 布局样式
|
||||
//ToastUtils.setView(R.layout.toast_custom_view);
|
||||
//ToastUtils.setStyle(new WhiteToastStyle());
|
||||
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ package cc.winboll.studio.contacts;
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -38,8 +39,10 @@ import cc.winboll.studio.contacts.fragments.CallLogFragment;
|
||||
import cc.winboll.studio.contacts.fragments.ContactsFragment;
|
||||
import cc.winboll.studio.contacts.fragments.LogFragment;
|
||||
import cc.winboll.studio.contacts.services.MainService;
|
||||
import cc.winboll.studio.contacts.utils.AppGoToSettingsUtil;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import java.util.ArrayList;
|
||||
@@ -48,10 +51,9 @@ import java.util.List;
|
||||
final public class MainActivity extends AppCompatActivity implements IWinBoLLActivity, ViewPager.OnPageChangeListener, View.OnClickListener {
|
||||
|
||||
public static final String TAG = "MainActivity";
|
||||
|
||||
public static final int REQUEST_HOME_ACTIVITY = 0;
|
||||
public static final int REQUEST_ABOUT_ACTIVITY = 1;
|
||||
|
||||
public static final int REQUEST_APP_SETTINGS = 2;
|
||||
public static final String ACTION_SOS = "cc.winboll.studio.libappbase.WinBoLL.ACTION_SOS";
|
||||
|
||||
static MainActivity _MainActivity;
|
||||
@@ -72,6 +74,13 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
||||
List<String> tabTitleList;
|
||||
|
||||
private static final int DIALER_REQUEST_CODE = 1;
|
||||
private static final int REQUEST_REQUIRED_PERMISSIONS = 1002;
|
||||
// 关键修改1:新增 READ_CALL_LOG 权限到必需权限列表(解决通话记录读取崩溃)
|
||||
private String[] REQUIRED_PERMISSIONS = new String[]{
|
||||
Manifest.permission.READ_CONTACTS, // 通讯录读取(原)
|
||||
Manifest.permission.CALL_PHONE, // 电话拨号(原)
|
||||
Manifest.permission.READ_CALL_LOG // 通话记录读取(新增,核心修复)
|
||||
};
|
||||
|
||||
|
||||
@Override
|
||||
@@ -88,9 +97,88 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
_MainActivity = this;
|
||||
|
||||
// 优先检查所有必需权限(含新增的 READ_CALL_LOG)
|
||||
if (!checkAllRequiredPermissions()) {
|
||||
requestAllRequiredPermissions();
|
||||
} else {
|
||||
initUIAndLogic(savedInstanceState);
|
||||
}
|
||||
|
||||
//ToastUtils.show("onCreate");
|
||||
}
|
||||
|
||||
// 权限检查方法(无需修改,自动包含新增的 READ_CALL_LOG)
|
||||
private boolean checkAllRequiredPermissions() {
|
||||
for (String permission : REQUIRED_PERMISSIONS) {
|
||||
if (ActivityCompat.checkSelfPermission(this, permission)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 权限申请方法(无需修改,自动申请新增的 READ_CALL_LOG)
|
||||
private void requestAllRequiredPermissions() {
|
||||
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_REQUIRED_PERMISSIONS);
|
||||
}
|
||||
|
||||
// 权限结果回调(无需修改,确保所有权限(含 READ_CALL_LOG)都通过才加载UI)
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
|
||||
if (requestCode == REQUEST_REQUIRED_PERMISSIONS) {
|
||||
boolean allPermissionsGranted = true;
|
||||
for (int result : grantResults) {
|
||||
if (result != PackageManager.PERMISSION_GRANTED) {
|
||||
allPermissionsGranted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPermissionsGranted) {
|
||||
initUIAndLogic(null);
|
||||
} else {
|
||||
// 关键修改2:更新提示文案,告知用户新增的“通话记录权限”
|
||||
showPermissionDeniedDialogAndExit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 核心修改:新增“设置权限”按钮,点击调用 AppGoToSettingsUtil 跳转设置页
|
||||
private void showPermissionDeniedDialogAndExit() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("权限不足,无法使用")
|
||||
// 文案修改:明确新增“通话记录读取”权限
|
||||
.setMessage("应用需要「通讯录读取」、「电话」和「通话记录读取」权限才能正常运行,请授予权限后重新打开应用。")
|
||||
.setCancelable(false)
|
||||
// 新增:左侧“设置权限”按钮(先添加的按钮在左侧)
|
||||
.setNegativeButton("设置权限", new AlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(android.content.DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
// 调用工具类跳转应用设置页(按需求实现)
|
||||
AppGoToSettingsUtil appGoToSettingsUtil = new AppGoToSettingsUtil();
|
||||
appGoToSettingsUtil.GoToSetting(MainActivity.this);
|
||||
}
|
||||
})
|
||||
// 原有:右侧“确定退出”按钮(后添加的按钮在右侧)
|
||||
.setPositiveButton("确定退出", new AlertDialog.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(android.content.DialogInterface dialog, int which) {
|
||||
dialog.dismiss();
|
||||
finishAndRemoveTask();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
// 初始化UI和逻辑(无需修改,权限通过后才加载 CallLogFragment)
|
||||
private void initUIAndLogic(Bundle savedInstanceState) {
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
// 初始化工具栏(仅加载基础UI)
|
||||
mToolbar = (Toolbar) findViewById(R.id.activitymainToolbar1);
|
||||
setSupportActionBar(mToolbar);
|
||||
getSupportActionBar().setSubtitle(TAG);
|
||||
@@ -98,34 +186,28 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
||||
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
|
||||
viewPager = (ViewPager) findViewById(R.id.viewPager);
|
||||
|
||||
// 创建Fragment列表(仅实例化,不加载数据)
|
||||
fragmentList = new ArrayList<Fragment>();
|
||||
tabTitleList = new ArrayList<String>();
|
||||
// CallLogFragment 仅在权限通过后才实例化(避免提前触发读取)
|
||||
fragmentList.add(CallLogFragment.newInstance(0));
|
||||
fragmentList.add(ContactsFragment.newInstance(1)); // 延迟加载联系人数据
|
||||
fragmentList.add(ContactsFragment.newInstance(1));
|
||||
fragmentList.add(LogFragment.newInstance(2));
|
||||
tabTitleList.add("通话记录");
|
||||
tabTitleList.add("联系人");
|
||||
tabTitleList.add("应用日志");
|
||||
|
||||
// 设置ViewPager适配器
|
||||
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragmentList, tabTitleList);
|
||||
viewPager.setAdapter(adapter);
|
||||
|
||||
// 关键:关闭预加载,仅当前页初始化
|
||||
viewPager.setOffscreenPageLimit(0);
|
||||
|
||||
// 关联TabLayout和ViewPager
|
||||
viewPager.setOffscreenPageLimit(0); // 关闭预加载,避免提前初始化 CallLogFragment
|
||||
tabLayout.setupWithViewPager(viewPager);
|
||||
|
||||
// 初始化服务状态(延迟启动非核心服务)
|
||||
// 原有服务启动、电话监听等逻辑...
|
||||
MainServiceBean mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
|
||||
if (mMainServiceBean == null) {
|
||||
mMainServiceBean = new MainServiceBean();
|
||||
MainServiceBean.saveBean(this, mMainServiceBean);
|
||||
}
|
||||
if (mMainServiceBean.isEnable()) {
|
||||
// 延迟1秒启动服务,避免阻塞启动
|
||||
new Handler().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -134,16 +216,14 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// 初始化电话状态监听(基础功能保留)
|
||||
telephonyManager = (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
|
||||
phoneStateListener = new MyPhoneStateListener();
|
||||
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
|
||||
}
|
||||
|
||||
|
||||
// ViewPager适配器(Java 7语法)
|
||||
// 以下为原有代码(无需修改)
|
||||
private class MyPagerAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private List<Fragment> fragmentList;
|
||||
private List<String> tabTitleList;
|
||||
|
||||
@@ -173,21 +253,18 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
||||
Intent intent = new Intent(Intent.ACTION_DIAL);
|
||||
intent.setData(android.net.Uri.parse("tel:" + phoneNumber));
|
||||
if (ActivityCompat.checkSelfPermission(_MainActivity, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
|
||||
Toast.makeText(_MainActivity, "拨号权限不足", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
_MainActivity.startActivity(intent);
|
||||
}
|
||||
|
||||
// OnPageChangeListener接口实现
|
||||
@Override
|
||||
public void onPageScrollStateChanged(int state) {}
|
||||
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {}
|
||||
|
||||
@Override
|
||||
public void onPageSelected(int position) {}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {}
|
||||
|
||||
@@ -239,9 +316,6 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
||||
super.onResume();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否是系统默认电话应用
|
||||
*/
|
||||
public boolean isDefaultPhoneCallApp() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
TelecomManager manger = (TelecomManager) getSystemService(TELECOM_SERVICE);
|
||||
@@ -272,7 +346,9 @@ final public class MainActivity extends AppCompatActivity implements IWinBoLLAct
|
||||
Toast.makeText(MainActivity.this, getString(R.string.app_name) + " 已成为默认电话应用",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
} else if (requestCode == REQUEST_APP_SETTINGS) {
|
||||
recreate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ import cc.winboll.studio.contacts.dun.Rules;
|
||||
import cc.winboll.studio.contacts.services.MainService;
|
||||
import cc.winboll.studio.contacts.views.DuInfoTextView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.winboll.IWinBoLLActivity;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
@@ -263,7 +263,7 @@ public class SettingsActivity extends AppCompatActivity implements IWinBoLLActiv
|
||||
@Override
|
||||
public void run() {
|
||||
if (tomCat.downloadBoBullToon()) {
|
||||
ToastUtils.show("BoBullToon downlaod OK!");
|
||||
LogUtils.d(TAG, "BoBullToon downlaod OK!");
|
||||
MainService.restartMainService(SettingsActivity.this);
|
||||
Rules.getInstance(SettingsActivity.this).reload();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.beans.CallLogModel;
|
||||
import cc.winboll.studio.contacts.utils.ContactUtils;
|
||||
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -76,6 +76,9 @@ public class CallLogAdapter extends RecyclerView.Adapter<CallLogAdapter.CallLogV
|
||||
// Set the clipboard's primary clip.
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
|
||||
} else if (nItemId == R.id.item_calllog_phonenumber_add_contact) {
|
||||
//ToastUtils.show(callLog.getPhoneNumber());
|
||||
ContactUtils.jumpToAddContact(mContext, callLog.getPhoneNumber());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -21,8 +21,9 @@ import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.beans.ContactModel;
|
||||
import cc.winboll.studio.contacts.utils.ContactUtils;
|
||||
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import java.util.List;
|
||||
|
||||
public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactViewHolder> {
|
||||
@@ -69,6 +70,11 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV
|
||||
// Set the clipboard's primary clip.
|
||||
clipboard.setPrimaryClip(clip);
|
||||
Toast.makeText(mContext, "Copy to clipboard.", Toast.LENGTH_SHORT).show();
|
||||
} else if (nItemId == R.id.item_calllog_phonenumber_edit_contact) {
|
||||
//ToastUtils.show("Test");
|
||||
Long nContactId = ContactUtils.getContactIdByPhone(mContext, contact.getNumber());
|
||||
//ToastUtils.show(String.format("%d", nContactId));
|
||||
ContactUtils.jumpToEditContact(mContext, contact.getNumber(), nContactId);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -112,7 +118,7 @@ public class ContactAdapter extends RecyclerView.Adapter<ContactAdapter.ContactV
|
||||
TextView contactName;
|
||||
TextView contactNumber;
|
||||
AOHPCTCSeekBar dialAOHPCTCSeekBar;
|
||||
|
||||
|
||||
public ContactViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
llPhoneNumberMain = itemView.findViewById(R.id.itemcontactLinearLayout1);
|
||||
|
||||
@@ -7,7 +7,6 @@ package cc.winboll.studio.contacts.adapters;
|
||||
*/
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
@@ -21,9 +20,8 @@ import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.beans.PhoneConnectRuleModel;
|
||||
import cc.winboll.studio.contacts.dun.Rules;
|
||||
import cc.winboll.studio.contacts.views.LeftScrollView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.dialogs.YesNoAlertDialog;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import android.content.Context;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.dun.Rules;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
@@ -25,7 +25,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.adapters.CallLogAdapter;
|
||||
import cc.winboll.studio.contacts.beans.CallLogModel;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
@@ -161,7 +160,7 @@ public class CallLogFragment extends Fragment {
|
||||
_CallLogFragment.triggerUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
@@ -31,7 +31,7 @@ import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.adapters.ContactAdapter;
|
||||
import cc.winboll.studio.contacts.beans.ContactModel;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
@@ -95,7 +95,7 @@ public class ContactsFragment extends Fragment {
|
||||
recyclerView = (RecyclerView) view.findViewById(R.id.contacts_recycler_view);
|
||||
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
contactList = new ArrayList<ContactModel>();
|
||||
contactAdapter = new ContactAdapter(getContext(), contactList);
|
||||
contactAdapter = new ContactAdapter(getActivity(), contactList);
|
||||
recyclerView.setAdapter(contactAdapter);
|
||||
// 初始隐藏列表,数据加载后显示
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
|
||||
@@ -14,7 +14,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
|
||||
public class LogFragment extends Fragment {
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import cc.winboll.studio.contacts.services.MainService;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class MainReceiver extends BroadcastReceiver {
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
package cc.winboll.studio.contacts.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/09/27 14:27
|
||||
* @Describe 调用应用属性设置页工具类
|
||||
* 来源:https://blog.csdn.net/zhuhai__yizhi/article/details/78737593
|
||||
* Created by zyy on 2018/3/12.
|
||||
* 直接跳转到权限后返回,可以监控权限授权情况,但是,跳转到应用详情页,无法监测权限情况
|
||||
* 是否要加以区分,若是应用详情页,则跳转回来后,onRestart检测所求权限,如果授权,则收回提示,如果没授权,则继续提示
|
||||
*/
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import cc.winboll.studio.contacts.MainActivity;
|
||||
|
||||
public class AppGoToSettingsUtil {
|
||||
|
||||
public static final String TAG = "AppGoToSettingsUtil";
|
||||
|
||||
public static final int ACTIVITY_RESULT_APP_SETTINGS = MainActivity.REQUEST_APP_SETTINGS;
|
||||
|
||||
/**
|
||||
* Build.MANUFACTURER判断各大手机厂商品牌
|
||||
*/
|
||||
private static final String MANUFACTURER_HUAWEI = "Huawei";//华为
|
||||
private static final String MANUFACTURER_MEIZU = "Meizu";//魅族
|
||||
private static final String MANUFACTURER_XIAOMI = "Xiaomi";//小米
|
||||
private static final String MANUFACTURER_SONY = "Sony";//索尼
|
||||
private static final String MANUFACTURER_OPPO = "OPPO";
|
||||
private static final String MANUFACTURER_LG = "LG";
|
||||
private static final String MANUFACTURER_VIVO = "vivo";
|
||||
private static final String MANUFACTURER_SAMSUNG = "samsung";//三星
|
||||
private static final String MANUFACTURER_LETV = "Letv";//乐视
|
||||
private static final String MANUFACTURER_ZTE = "ZTE";//中兴
|
||||
private static final String MANUFACTURER_YULONG = "YuLong";//酷派
|
||||
private static final String MANUFACTURER_LENOVO = "LENOVO";//联想
|
||||
|
||||
public static boolean isAppSettingOpen=false;
|
||||
/**
|
||||
* 跳转到相应品牌手机系统权限设置页,如果跳转不成功,则跳转到应用详情页
|
||||
* 这里需要改造成返回true或者false,应用详情页:true,应用权限页:false
|
||||
* @param activity
|
||||
*/
|
||||
public static void GoToSetting(Activity activity) {
|
||||
switch (Build.MANUFACTURER) {
|
||||
case MANUFACTURER_HUAWEI://华为
|
||||
Huawei(activity);
|
||||
break;
|
||||
case MANUFACTURER_MEIZU://魅族
|
||||
Meizu(activity);
|
||||
break;
|
||||
case MANUFACTURER_XIAOMI://小米
|
||||
Xiaomi(activity);
|
||||
break;
|
||||
case MANUFACTURER_SONY://索尼
|
||||
Sony(activity);
|
||||
break;
|
||||
case MANUFACTURER_OPPO://oppo
|
||||
OPPO(activity);
|
||||
break;
|
||||
case MANUFACTURER_LG://lg
|
||||
LG(activity);
|
||||
break;
|
||||
case MANUFACTURER_LETV://乐视
|
||||
Letv(activity);
|
||||
break;
|
||||
default://其他
|
||||
try {//防止应用详情页也找不到,捕获异常后跳转到设置,这里跳转最好是两级,太多用户也会觉得麻烦,还不如不跳
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
} catch (Exception e) {
|
||||
SystemConfig(activity);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 华为跳转权限设置页
|
||||
* @param activity
|
||||
*/
|
||||
public static void Huawei(Activity activity) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra("packageName", activity.getPackageName());
|
||||
ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");
|
||||
intent.setComponent(comp);
|
||||
activity.startActivityForResult(intent, ACTIVITY_RESULT_APP_SETTINGS);
|
||||
isAppSettingOpen = false;
|
||||
} catch (Exception e) {
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 魅族跳转权限设置页,测试时,点击无反应,具体原因不明
|
||||
* @param activity
|
||||
*/
|
||||
public static void Meizu(Activity activity) {
|
||||
try {
|
||||
Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
intent.putExtra("packageName", activity.getPackageName());
|
||||
activity.startActivity(intent);
|
||||
isAppSettingOpen = false;
|
||||
} catch (Exception e) {
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 小米,功能正常
|
||||
* @param activity
|
||||
*/
|
||||
public static void Xiaomi(Activity activity) {
|
||||
try { //MIUI 8 9
|
||||
Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR");
|
||||
localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.PermissionsEditorActivity");
|
||||
localIntent.putExtra("extra_pkgname", activity.getPackageName());
|
||||
activity.startActivityForResult(localIntent, ACTIVITY_RESULT_APP_SETTINGS);
|
||||
isAppSettingOpen = false;
|
||||
//activity.startActivity(localIntent);
|
||||
} catch (Exception e) {
|
||||
try { //MIUI 5/6/7
|
||||
Intent localIntent = new Intent("miui.intent.action.APP_PERM_EDITOR");
|
||||
localIntent.setClassName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity");
|
||||
localIntent.putExtra("extra_pkgname", activity.getPackageName());
|
||||
activity.startActivityForResult(localIntent, ACTIVITY_RESULT_APP_SETTINGS);
|
||||
isAppSettingOpen = false;
|
||||
//activity.startActivity(localIntent);
|
||||
} catch (Exception e1) { //否则跳转到应用详情
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
//这里有个问题,进入活动后需要再跳一级活动,就检测不到返回结果
|
||||
//activity.startActivity(getAppDetailSettingIntent());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 索尼,6.0以上的手机非常少,基本没看见
|
||||
* @param activity
|
||||
*/
|
||||
public static void Sony(Activity activity) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra("packageName", activity.getPackageName());
|
||||
ComponentName comp = new ComponentName("com.sonymobile.cta", "com.sonymobile.cta.SomcCTAMainActivity");
|
||||
intent.setComponent(comp);
|
||||
activity.startActivity(intent);
|
||||
isAppSettingOpen = false;
|
||||
} catch (Exception e) {
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OPPO
|
||||
* @param activity
|
||||
*/
|
||||
public static void OPPO(Activity activity) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra("packageName", activity.getPackageName());
|
||||
ComponentName comp = new ComponentName("com.color.safecenter", "com.color.safecenter.permission.PermissionManagerActivity");
|
||||
intent.setComponent(comp);
|
||||
activity.startActivity(intent);
|
||||
isAppSettingOpen = false;
|
||||
} catch (Exception e) {
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LG经过测试,正常使用
|
||||
* @param activity
|
||||
*/
|
||||
public static void LG(Activity activity) {
|
||||
try {
|
||||
Intent intent = new Intent("android.intent.action.MAIN");
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra("packageName", activity.getPackageName());
|
||||
ComponentName comp = new ComponentName("com.android.settings", "com.android.settings.Settings$AccessLockSummaryActivity");
|
||||
intent.setComponent(comp);
|
||||
activity.startActivity(intent);
|
||||
isAppSettingOpen = false;
|
||||
} catch (Exception e) {
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐视6.0以上很少,基本都可以忽略了,现在乐视手机不多
|
||||
* @param activity
|
||||
*/
|
||||
public static void Letv(Activity activity) {
|
||||
try {
|
||||
Intent intent = new Intent();
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra("packageName", activity.getPackageName());
|
||||
ComponentName comp = new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.PermissionAndApps");
|
||||
intent.setComponent(comp);
|
||||
activity.startActivity(intent);
|
||||
isAppSettingOpen = false;
|
||||
} catch (Exception e) {
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 只能打开到自带安全软件
|
||||
* @param activity
|
||||
*/
|
||||
public static void _360(Activity activity) {
|
||||
try {
|
||||
Intent intent = new Intent("android.intent.action.MAIN");
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra("packageName", activity.getPackageName());
|
||||
ComponentName comp = new ComponentName("com.qihoo360.mobilesafe", "com.qihoo360.mobilesafe.ui.index.AppEnterActivity");
|
||||
intent.setComponent(comp);
|
||||
activity.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
openAppDetailSetting(activity);
|
||||
//activity.startActivityForResult(getAppDetailSettingIntent(activity), PERMISSION_SETTING_FOR_RESULT);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 系统设置界面
|
||||
* @param activity
|
||||
*/
|
||||
public static void SystemConfig(Activity activity) {
|
||||
Intent intent = new Intent(Settings.ACTION_SETTINGS);
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
/**
|
||||
* 获取应用详情页面
|
||||
* @return
|
||||
*/
|
||||
private static Intent getAppDetailSettingIntent(Activity activity) {
|
||||
Intent localIntent = new Intent();
|
||||
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
//if (Build.VERSION.SDK_INT >= 9) {
|
||||
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
|
||||
localIntent.setData(Uri.fromParts("package", activity.getPackageName(), null));
|
||||
/*} else if (Build.VERSION.SDK_INT <= 8) {
|
||||
localIntent.setAction(Intent.ACTION_VIEW);
|
||||
localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
|
||||
localIntent.putExtra("com.android.settings.ApplicationPkgName", activity.getPackageName());
|
||||
}*/
|
||||
return localIntent;
|
||||
}
|
||||
|
||||
public static void openAppDetailSetting(Activity activity) {
|
||||
activity.startActivityForResult(getAppDetailSettingIntent(activity), ACTIVITY_RESULT_APP_SETTINGS);
|
||||
isAppSettingOpen = true;
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,14 @@ package cc.winboll.studio.contacts.utils;
|
||||
* @Describe 联系人工具集
|
||||
*/
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.ContactsContract;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -120,4 +124,92 @@ public class ContactUtils {
|
||||
}
|
||||
return sbSpaceNumber.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 跳转至系统添加联系人界面的工具函数
|
||||
* @param context 上下文(如 PhoneCallService、Activity、Fragment 均可,需传入有效上下文)
|
||||
* @param phoneNumber 可选参数:预填的联系人电话(传 null 则跳转空表单)
|
||||
*/
|
||||
public static void jumpToAddContact(Context mContext, String phoneNumber) {
|
||||
Intent intent = new Intent(Intent.ACTION_INSERT);
|
||||
intent.setType("vnd.android.cursor.dir/person");
|
||||
intent.putExtra(android.provider.ContactsContract.Intents.Insert.PHONE, phoneNumber);
|
||||
mContext.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转至系统编辑联系人界面(适配小米等定制机型)
|
||||
* @param context 上下文(Activity/Service/Fragment)
|
||||
* @param phoneNumber 待编辑联系人的电话号码(用于匹配已有联系人,必传)
|
||||
* @param contactId 可选:已有联系人的ID(通过 ContactsContract 获取,传null则自动匹配号码)
|
||||
*/
|
||||
public static void jumpToEditContact(Context context, String phoneNumber, Long contactId) {
|
||||
Intent intent = new Intent(Intent.ACTION_EDIT);
|
||||
// 关键:小米等机型需明确设置数据类型为“单个联系人”,避免参数丢失
|
||||
intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
|
||||
|
||||
// 场景A:已知联系人ID(精准定位,优先用此方式,参数传递最稳定)
|
||||
if (contactId != null && contactId > 0) {
|
||||
// 构建联系人的Uri(格式:content://contacts/people/[contactId],系统标准格式)
|
||||
Uri contactUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
|
||||
intent.setData(contactUri);
|
||||
//ToastUtils.show("1");
|
||||
} else if (phoneNumber != null && !phoneNumber.isEmpty()) {
|
||||
// 方式1:小米等机型兼容的“通过号码定位联系人”参数(部分系统认此参数)
|
||||
//intent.putExtra(ContactsContract.Intents.Insert.PHONE_NUMBER, phoneNumber);
|
||||
// 方式2:补充系统标准的“数据Uri”,强化匹配(避免参数被定制系统忽略)
|
||||
Uri phoneUri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
|
||||
intent.setData(phoneUri);
|
||||
} else {
|
||||
LogUtils.d(TAG, "编辑联系人失败:电话号码和联系人ID均为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 可选:预填最新号码(覆盖原有号码,若用户修改了号码,编辑时自动更新)
|
||||
if (phoneNumber != null && !phoneNumber.isEmpty()) {
|
||||
intent.putExtra(ContactsContract.CommonDataKinds.Phone.NUMBER, phoneNumber);
|
||||
intent.putExtra(ContactsContract.CommonDataKinds.Phone.TYPE, ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
|
||||
}
|
||||
|
||||
// 启动活动(加防护,避免无联系人应用崩溃)
|
||||
// 小米机型在Service/非Activity中调用,需加NEW_TASK标志,否则可能无法启动
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过电话号码查询联系人ID(适配小米机型,解决编辑时匹配不稳定问题)
|
||||
* @param context 上下文
|
||||
* @param phoneNumber 待查询的电话号码
|
||||
* @return 联系人ID(无匹配时返回-1)
|
||||
*/
|
||||
public static Long getContactIdByPhone(Context context, String phoneNumber) {
|
||||
if (phoneNumber == null || phoneNumber.isEmpty()) {
|
||||
return -1L;
|
||||
}
|
||||
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
// 1. 构建电话查询Uri(系统标准:通过号码过滤联系人数据)
|
||||
Uri queryUri = Uri.withAppendedPath(ContactsContract.CommonDataKinds.Phone.CONTENT_FILTER_URI, Uri.encode(phoneNumber));
|
||||
// 2. 只查询“联系人ID”字段(高效,避免冗余数据)
|
||||
String[] projection = {ContactsContract.CommonDataKinds.Phone.CONTACT_ID};
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
cursor = cr.query(queryUri, projection, null, null, null);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
// 3. 读取联系人ID(返回Long类型,避免int溢出)
|
||||
return cursor.getLong(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.CONTACT_ID));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "查询联系人ID失败。" + e);
|
||||
} finally {
|
||||
if (cursor != null) {
|
||||
cursor.close(); // 关闭游标,避免内存泄漏
|
||||
}
|
||||
}
|
||||
return -1L; // 无匹配联系人
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,9 +13,8 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.RemoteViews;
|
||||
import cc.winboll.studio.contacts.R;
|
||||
import cc.winboll.studio.contacts.threads.MainServiceThread;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.utils.ToastUtils;
|
||||
|
||||
public class APPStatusWidget extends AppWidgetProvider {
|
||||
|
||||
|
||||
@@ -5,5 +5,8 @@
|
||||
<item
|
||||
android:id="@+id/item_calllog_phonenumber_copy"
|
||||
android:title="Copy"/>
|
||||
<item
|
||||
android:id="@+id/item_calllog_phonenumber_add_contact"
|
||||
android:title="Add Contact"/>
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -5,5 +5,8 @@
|
||||
<item
|
||||
android:id="@+id/item_contact_phonenumber_copy"
|
||||
android:title="Copy"/>
|
||||
<item
|
||||
android:id="@+id/item_calllog_phonenumber_edit_contact"
|
||||
android:title="Edit Contact"/>
|
||||
|
||||
</menu>
|
||||
|
||||
@@ -4,29 +4,32 @@ apply from: '../.winboll/winboll_lib_build.gradle'
|
||||
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 {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
// 米盟 SDK
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libmimo_1011.so"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
api 'cc.winboll.studio:libapputils:15.10.2'
|
||||
api 'cc.winboll.studio:libappbase:15.10.9'
|
||||
|
||||
// 吐司类库
|
||||
api 'com.github.getActivity:ToastUtils:10.5'
|
||||
|
||||
// 权限请求框架:https://github.com/getActivity/XXPermissions
|
||||
api 'com.github.getActivity:XXPermissions:18.63'
|
||||
// 下拉控件
|
||||
@@ -52,4 +55,17 @@ dependencies {
|
||||
//api 'androidx.vectordrawable:vectordrawable:1.1.0'
|
||||
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
|
||||
// 米盟
|
||||
api 'com.miui.zeus:mimo-ad-sdk:5.3.+'//请使用最新版sdk
|
||||
//注意:以下5个库必须要引入
|
||||
//implementation 'androidx.appcompat:appcompat:1.4.1'
|
||||
api 'androidx.recyclerview:recyclerview:1.0.0'
|
||||
api 'com.google.code.gson:gson:2.8.5'
|
||||
api 'com.github.bumptech.glide:glide:4.9.0'
|
||||
//annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'
|
||||
|
||||
api 'cc.winboll.studio:libappbase:15.11.0'
|
||||
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Sep 29 13:04:07 HKT 2025
|
||||
stageCount=3
|
||||
#Wed Nov 26 15:54:26 GMT 2025
|
||||
stageCount=7
|
||||
libraryProject=libaes
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.2
|
||||
buildCount=0
|
||||
baseBetaVersion=15.10.3
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.6
|
||||
buildCount=32
|
||||
baseBetaVersion=15.11.7
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="cc.winboll.studio.libaes">
|
||||
|
||||
<application>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
|
||||
tools:ignore="QueryAllPackagesPermission" />
|
||||
|
||||
<!-- 通过GPS得到精确位置 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<!-- 通过网络得到粗略位置 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
|
||||
<application
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
|
||||
<activity
|
||||
android:name="cc.winboll.studio.libaes.unittests.SecondaryLibraryActivity"
|
||||
@@ -33,6 +49,18 @@
|
||||
android:name="cc.winboll.studio.libaes.winboll.WinBoLLMail"
|
||||
android:exported="true"/>
|
||||
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_provider" />
|
||||
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -31,6 +31,9 @@ import cc.winboll.studio.libappbase.LogUtils;
|
||||
import com.baoyz.widget.PullRefreshLayout;
|
||||
import java.util.ArrayList;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libaes.views.ADsBannerView;
|
||||
import cc.winboll.studio.libappbase.LogActivity;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
public abstract class DrawerFragmentActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
|
||||
|
||||
@@ -71,17 +74,22 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 修复:释放广告资源,避免内存泄漏
|
||||
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
|
||||
if (adsBannerView != null) {
|
||||
adsBannerView.releaseAdResources();
|
||||
}
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public Intent getIntent() {
|
||||
// TODO: Implement this method
|
||||
return super.getIntent();
|
||||
}
|
||||
public Intent getIntent() {
|
||||
// TODO: Implement this method
|
||||
return super.getIntent();
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return this.mContext;
|
||||
}*/
|
||||
public Context getContext() {
|
||||
return this.mContext;
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public MenuInflater getMenuInflater() {
|
||||
@@ -90,9 +98,9 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
|
||||
}
|
||||
|
||||
/*public void setSubtitle(CharSequence context) {
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setSubtitle(context);
|
||||
}*/
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setSubtitle(context);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public void recreate() {
|
||||
@@ -100,9 +108,9 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public boolean moveTaskToBack(boolean nonRoot) {
|
||||
return super.moveTaskToBack(nonRoot);
|
||||
}*/
|
||||
public boolean moveTaskToBack(boolean nonRoot) {
|
||||
return super.moveTaskToBack(nonRoot);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public void startActivity(Intent intent) {
|
||||
@@ -115,24 +123,24 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
|
||||
}
|
||||
|
||||
/*@Override
|
||||
public FragmentManager getSupportFragmentManager() {
|
||||
return super.getSupportFragmentManager();
|
||||
}
|
||||
public FragmentManager getSupportFragmentManager() {
|
||||
return super.getSupportFragmentManager();
|
||||
}
|
||||
|
||||
public void setSubtitle(int resId) {
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setSubtitle(resId);
|
||||
}
|
||||
public void setSubtitle(int resId) {
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setSubtitle(resId);
|
||||
}
|
||||
|
||||
public void setTitle(CharSequence context) {
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setTitle(context);
|
||||
}
|
||||
public void setTitle(CharSequence context) {
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setTitle(context);
|
||||
}
|
||||
|
||||
public void setTitle(int resId) {
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setTitle(resId);
|
||||
}*/
|
||||
public void setTitle(int resId) {
|
||||
// TODO: Implement this method
|
||||
getSupportActionBar().setTitle(resId);
|
||||
}*/
|
||||
|
||||
@Override
|
||||
public SharedPreferences getSharedPreferences(String name, int mode) {
|
||||
@@ -175,6 +183,9 @@ 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()) {
|
||||
ToastUtils.show("Test");
|
||||
LogActivity.startLogActivity(this);
|
||||
} else if (R.id.item_about == item.getItemId()) {
|
||||
LogUtils.d(TAG, "onAbout");
|
||||
} else if (android.R.id.home == item.getItemId()) {
|
||||
@@ -189,6 +200,11 @@ public abstract class DrawerFragmentActivity extends AppCompatActivity implement
|
||||
if (checkThemeStyleChange()) {
|
||||
recreate();
|
||||
}
|
||||
|
||||
ADsBannerView adsBannerView = findViewById(R.id.adsbanner);
|
||||
if (adsBannerView != null) {
|
||||
adsBannerView.resumeADs(DrawerFragmentActivity.this);
|
||||
}
|
||||
}
|
||||
|
||||
void initRootView() {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package cc.winboll.studio.libaes.enums;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/26 17:49
|
||||
* @Describe 广告控制模式枚举
|
||||
*/
|
||||
public enum ADsMode {
|
||||
STANDALONE("单机模式"), // 单机模式(默认)
|
||||
MIMO_SDK("米盟广告SDK支持模式"); // 米盟广告SDK模式
|
||||
|
||||
private final String modeName;
|
||||
|
||||
ADsMode(String modeName) {
|
||||
this.modeName = modeName;
|
||||
}
|
||||
|
||||
public String getModeName() {
|
||||
return modeName;
|
||||
}
|
||||
|
||||
// 根据保存的字符串值解析枚举(SP读取时使用)
|
||||
public static ADsMode fromValue(String value) {
|
||||
if (value == null) return STANDALONE;
|
||||
try {
|
||||
return ADsMode.valueOf(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return STANDALONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import androidx.fragment.app.Fragment;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.views.AButton;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
public class TestAButtonFragment extends Fragment {
|
||||
|
||||
|
||||
@@ -12,14 +12,13 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
import cc.winboll.studio.libaes.ImagePagerAdapter;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.views.AOHPCTCSeekBar;
|
||||
import cc.winboll.studio.libappbase.LogView;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package cc.winboll.studio.libaes.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.DisplayMetrics;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/18 15:23
|
||||
* @Describe 米盟 MimoUtils
|
||||
*/
|
||||
public final class MimoUtils {
|
||||
public static final String TAG = "Utils";
|
||||
|
||||
public static int dpToPx(Context context, float dp) {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (dp * displayMetrics.density + 0.5f);
|
||||
}
|
||||
|
||||
public static int pxToDp(Context context, float px) {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (px / displayMetrics.density + 0.5f);
|
||||
}
|
||||
|
||||
public static int pxToSp(Context context, float pxValue) {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (pxValue / displayMetrics.scaledDensity + 0.5f);
|
||||
}
|
||||
|
||||
public static int spToPx(Context context, float spValue) {
|
||||
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
|
||||
return (int) (spValue * displayMetrics.scaledDensity + 0.5f);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package cc.winboll.studio.libaes.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/13 06:50
|
||||
* @Describe 应用变量保存工具
|
||||
*/
|
||||
|
||||
public class PrefUtils {
|
||||
|
||||
public static final String TAG = "PrefUtils";
|
||||
|
||||
//
|
||||
// 保存字符串到SharedPreferences的函数
|
||||
//
|
||||
public static void saveString(Context context, String key, String value) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.putString(key, value);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
//
|
||||
// 从SharedPreferences读取字符串的函数
|
||||
//
|
||||
public static String getString(Context context, String key, String defaultValue) {
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences("myPrefs", Context.MODE_PRIVATE);
|
||||
return sharedPreferences.getString(key, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,498 @@
|
||||
package cc.winboll.studio.libaes.views;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.utils.MimoUtils;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import com.miui.zeus.mimo.sdk.ADParams;
|
||||
import com.miui.zeus.mimo.sdk.BannerAd;
|
||||
import com.miui.zeus.mimo.sdk.MimoCustomController;
|
||||
import com.miui.zeus.mimo.sdk.MimoLocation;
|
||||
import com.miui.zeus.mimo.sdk.MimoSdk;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import cc.winboll.studio.libaes.enums.ADsMode;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/18 14:41
|
||||
* @Describe WinBoLL 横幅广告类
|
||||
*/
|
||||
public class ADsBannerView extends LinearLayout {
|
||||
|
||||
public static final String TAG = "ADsBannerView";
|
||||
|
||||
|
||||
private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
|
||||
private String BANNER_POS_ID_WINBOLL_BETA = "d129ee5a263911f981a6dc7a9802e3e7";
|
||||
private String BANNER_POS_ID_WINBOLL = "4ec30efdb32271765b9a4efac902828b";
|
||||
|
||||
/*
|
||||
private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
|
||||
private String BANNER_POS_ID_WINBOLL_BETA = "802e356f1726f9ff39c69308bfd6f06a";
|
||||
private String BANNER_POS_ID_WINBOLL = "802e356f1726f9ff39c69308bfd6f06a";
|
||||
*/
|
||||
|
||||
Context mContext;
|
||||
View mMianView;
|
||||
SharedPreferences mSharedPreferences;
|
||||
ViewGroup mContainer;
|
||||
BannerAd mBannerAd;
|
||||
List<BannerAd> mAllBanners = new ArrayList<>();
|
||||
// 新增:主线程Handler,确保广告操作在主线程执行
|
||||
private Handler mMainHandler;
|
||||
|
||||
public ADsBannerView(Context context) {
|
||||
super(context);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public ADsBannerView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public ADsBannerView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public ADsBannerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
void initView(Context context) {
|
||||
this.mContext = context;
|
||||
|
||||
initMimoSdk(this.mContext);
|
||||
|
||||
// 初始化主线程Handler(关键:确保广告操作在主线程执行)
|
||||
mMainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
this.mMianView = inflate(this.mContext, R.layout.view_adsbanner, null);
|
||||
mContainer = this.mMianView.findViewById(R.id.ads_container);
|
||||
addView(this.mMianView);
|
||||
}
|
||||
|
||||
public void resumeADs(final Activity activity) {
|
||||
// 没有设置米盟广告支持就退出
|
||||
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
|
||||
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
|
||||
if (ADsControlView.getAdsModeFromStatic(this.mContext) == ADsMode.MIMO_SDK) {
|
||||
LogUtils.i(TAG, "已设置播放米盟广告,正在播放...");
|
||||
mMainHandler.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 再次校验生命周期,避免延迟执行时Activity已销毁
|
||||
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
|
||||
fetchAd(activity);
|
||||
}
|
||||
}
|
||||
}, 1000); // 延迟1秒请求广告,提升页面加载体验
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放广告资源(关键:避免内存泄漏和空Context调用)
|
||||
*/
|
||||
public void releaseAdResources() {
|
||||
// 没有设置米盟广告支持就退出
|
||||
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "releaseAdResources()");
|
||||
|
||||
// 移除Handler回调
|
||||
if (mMainHandler != null) {
|
||||
mMainHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
|
||||
// 销毁所有广告实例
|
||||
if (mAllBanners != null && !mAllBanners.isEmpty()) {
|
||||
for (BannerAd ad : mAllBanners) {
|
||||
if (ad != null) {
|
||||
ad.destroy();
|
||||
}
|
||||
}
|
||||
mAllBanners.clear();
|
||||
}
|
||||
// 置空当前广告引用
|
||||
mBannerAd = null;
|
||||
// 移除广告容器中的视图
|
||||
if (mContainer != null) {
|
||||
mContainer.removeAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示广告(核心修复:传递安全的Context + 生命周期校验)
|
||||
*/
|
||||
private void showAd(final Activity activity) {
|
||||
// 没有设置米盟广告支持就退出
|
||||
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "showAd()");
|
||||
// 1. 生命周期校验:避免Activity已销毁时操作UI
|
||||
if (activity == null || activity.isFinishing() || activity.isDestroyed()) {
|
||||
LogUtils.e(TAG, "showAd: Activity is finishing or destroyed");
|
||||
return;
|
||||
}
|
||||
// 2. 非空校验:广告实例和容器
|
||||
if (mBannerAd == null || mContainer == null) {
|
||||
LogUtils.e(TAG, "showAd: BannerAd or Container is null");
|
||||
return;
|
||||
}
|
||||
// 3. 创建广告容器(使用ApplicationContext避免内存泄漏)
|
||||
final FrameLayout container = new FrameLayout(activity.getApplicationContext());
|
||||
container.setPadding(0, 0, 0, MimoUtils.dpToPx(activity, 10));
|
||||
mContainer.addView(container, new FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT
|
||||
));
|
||||
|
||||
// if (mIsBiddingWin) {
|
||||
// mBannerAd.setPrice(getPrice());
|
||||
// }
|
||||
// 4. 显示广告:传递ApplicationContext,避免Activity Context失效
|
||||
mBannerAd.showAd(activity, container, new BannerAd.BannerInteractionListener() {
|
||||
@Override
|
||||
public void onAdClick() {
|
||||
LogUtils.d(TAG, "onAdClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdShow() {
|
||||
LogUtils.d(TAG, "onAdShow");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdDismiss() {
|
||||
LogUtils.d(TAG, "onAdDismiss");
|
||||
// 修复:移除容器时校验Activity状态
|
||||
if (activity != null && !activity.isFinishing() && !activity.isDestroyed() && mContainer != null) {
|
||||
mContainer.removeView(container);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderSuccess() {
|
||||
LogUtils.d(TAG, "onRenderSuccess");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRenderFail(int code, String msg) {
|
||||
LogUtils.e(TAG, "onRenderFail errorCode " + code + " errorMsg " + msg);
|
||||
// 修复:渲染失败时移除容器
|
||||
if (activity != null && !activity.isFinishing() && !activity.isDestroyed() && mContainer != null) {
|
||||
mContainer.removeView(container);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求广告(核心修复:Context安全校验 + 异常捕获 + 资源管理)
|
||||
*/
|
||||
private void fetchAd(final Activity activity) {
|
||||
// 没有设置米盟广告支持就退出
|
||||
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
|
||||
return;
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "fetchAd()");
|
||||
// 1. 双重校验:Activity未销毁 + Context非空
|
||||
if (activity == null || activity.isFinishing() || activity.isDestroyed() || activity.getApplicationContext() == null) {
|
||||
LogUtils.e(TAG, "fetchAd: Invalid Context or Activity state");
|
||||
return;
|
||||
}
|
||||
// 2. 释放之前的广告资源,避免内存泄漏
|
||||
if (mBannerAd != null) {
|
||||
mBannerAd.destroy();
|
||||
}
|
||||
// 3. 初始化广告(使用ApplicationContext,避免Activity Context失效)
|
||||
try {
|
||||
mBannerAd = new BannerAd();
|
||||
mAllBanners.add(mBannerAd);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "fetchAd: Init BannerAd failed", e);
|
||||
return;
|
||||
}
|
||||
// 4. 设置下载监听
|
||||
mBannerAd.setDownLoadListener(new BannerAd.BannerDownloadListener() {
|
||||
@Override
|
||||
public void onDownloadStarted() {
|
||||
LogUtils.d(TAG, "onDownloadStarted");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadPaused() {
|
||||
LogUtils.d(TAG, "onDownloadPaused");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadFailed(int errorCode) {
|
||||
String msg = "onDownloadFailed, errorCode = " + errorCode;
|
||||
LogUtils.d(TAG, msg);
|
||||
//ToastUtils.show(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadFinished() {
|
||||
LogUtils.d(TAG, "onDownloadFinished");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadProgressUpdated(int progress) {
|
||||
LogUtils.d(TAG, "onDownloadProgressUpdated " + progress + "%");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallFailed(int errorCode) {
|
||||
LogUtils.d(TAG, "onInstallFailed, errorCode = " + errorCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallStart() {
|
||||
LogUtils.d(TAG, "onInstallStart");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInstallSuccess() {
|
||||
LogUtils.d(TAG, "onInstallSuccess");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadCancel() {
|
||||
LogUtils.d(TAG, "onDownloadCancel");
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 构建广告参数并请求
|
||||
String currentAD_ID = getAD_ID();
|
||||
LogUtils.d(TAG, String.format("currentAD_ID = %s", currentAD_ID));
|
||||
ADParams params = new ADParams.Builder().setUpId(currentAD_ID).build();
|
||||
mBannerAd.loadAd(params, new BannerAd.BannerLoadListener() {
|
||||
@Override
|
||||
public void onBannerAdLoadSuccess() {
|
||||
LogUtils.d(TAG, "onBannerAdLoadSuccess()");
|
||||
// 修复:广告加载成功后校验Activity状态
|
||||
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
|
||||
showAd(activity);
|
||||
//ToastUtils.show("showAd()");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdLoadFailed(int errorCode, String errorMsg) {
|
||||
String msg = "onAdLoadFailed: errorCode = " + errorCode + ", errorMsg = " + errorMsg;
|
||||
LogUtils.d(TAG, msg);
|
||||
removeAllBanners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void removeAllBanners() {
|
||||
// 没有设置米盟广告支持就退出
|
||||
if (ADsControlView.getAdsModeFromStatic(this.mContext) != ADsMode.MIMO_SDK) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:加载失败时移除当前广告实例
|
||||
if (mAllBanners.contains(mBannerAd)) {
|
||||
mAllBanners.remove(mBannerAd);
|
||||
}
|
||||
mBannerAd.destroy();
|
||||
mBannerAd = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据当前秒数获取广告ID(原逻辑保留)
|
||||
*/
|
||||
private String getAD_ID() {
|
||||
long currentSecond = System.currentTimeMillis() / 1000;
|
||||
return (currentSecond % 2 == 0) ? BANNER_POS_ID :
|
||||
(GlobalApplication.isDebugging() ? BANNER_POS_ID_WINBOLL_BETA : BANNER_POS_ID_WINBOLL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取广告价格(原逻辑保留,添加空指针校验)
|
||||
*/
|
||||
// private long getPrice() {
|
||||
// if (mBannerAd == null) {
|
||||
// return 0;
|
||||
// }
|
||||
// Map<String, Object> map = mBannerAd.getMediaExtraInfo();
|
||||
// if (map == null || map.isEmpty() || !map.containsKey("price")) {
|
||||
// LogUtils.w(TAG, "getPrice: media extra info is null or no price key");
|
||||
// return 0;
|
||||
// }
|
||||
// Object priceObj = map.get("price");
|
||||
// if (priceObj instanceof Long) {
|
||||
// return (Long) priceObj;
|
||||
// } else if (priceObj instanceof Integer) {
|
||||
// return ((Integer) priceObj).longValue();
|
||||
// } else {
|
||||
// LogUtils.e(TAG, "getPrice: price type is invalid");
|
||||
// return 0;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 显示隐私协议弹窗(原逻辑保留,优化Context使用)
|
||||
*/
|
||||
// private void showPrivacy() {
|
||||
// // 校验Activity状态,避免弹窗泄露
|
||||
// if (getActivity() == null || getActivity().isFinishing() || getActivity().isDestroyed()) {
|
||||
// return;
|
||||
// }
|
||||
// ADsMode adsMode = ADsControlView.getAdsModeFromStatic(this.mContext);
|
||||
// if (adsMode == ADsMode.STANDALONE) {
|
||||
// ADsControlView.updateAdsModeByStatic(this.mContext, ADsMode.STANDALONE);
|
||||
// LogUtils.i(TAG, "单机模式,广告已处于不可用状态...");
|
||||
// Toast.makeText(getActivity().getApplicationContext(), "单机模式,广告已处于不可用状态...", Toast.LENGTH_SHORT).show();
|
||||
// return;
|
||||
// } else if (adsMode == ADsMode.MIMO_SDK) {
|
||||
// ADsControlView.updateAdsModeByStatic(this.mContext, ADsMode.MIMO_SDK);
|
||||
// LogUtils.i(TAG, "米盟广告SDK支持模式,现在初始化SDK...");
|
||||
// initMimoSdk();
|
||||
// return;
|
||||
// }
|
||||
// else {
|
||||
// LogUtils.i(TAG, "开始弹出隐私协议...");
|
||||
// AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
// builder.setTitle("用户须知");
|
||||
// builder.setMessage("小米广告SDK隐私政策: https://dev.mi.com/distribute/doc/details?pId=1688, 请复制到浏览器查看");
|
||||
// builder.setIcon(R.drawable.ic_launcher);
|
||||
// builder.setCancelable(false); // 点击对话框以外的区域不消失
|
||||
// builder.setPositiveButton("同意", new DialogInterface.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(DialogInterface dialog, int which) {
|
||||
// getSharedPreferences().edit()
|
||||
// .putString(PRIVACY_VALUE, String.valueOf(1))
|
||||
// .apply();
|
||||
// initMimoSdk();
|
||||
// dialog.dismiss();
|
||||
// }
|
||||
// });
|
||||
// builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
|
||||
// @Override
|
||||
// public void onClick(DialogInterface dialog, int which) {
|
||||
// getSharedPreferences().edit()
|
||||
// .putString(PRIVACY_VALUE, String.valueOf(0))
|
||||
// .apply();
|
||||
// dialog.dismiss();
|
||||
// }
|
||||
// });
|
||||
// AlertDialog dialog = builder.create();
|
||||
//
|
||||
// // 配置弹窗位置(底部全屏)
|
||||
// Window window = dialog.getWindow();
|
||||
// if (window != null) {
|
||||
// window.setGravity(Gravity.BOTTOM);
|
||||
// WindowManager m = getActivity().getWindowManager();
|
||||
// Display d = m.getDefaultDisplay();
|
||||
// WindowManager.LayoutParams p = window.getAttributes();
|
||||
// p.width = d.getWidth();
|
||||
// window.setAttributes(p);
|
||||
// }
|
||||
// dialog.show();
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 初始化米盟SDK(核心修复:传递ApplicationContext + 异常捕获)
|
||||
*/
|
||||
private void initMimoSdk(Context context) {
|
||||
// 1. 安全获取ApplicationContext,避免Activity Context失效
|
||||
Context appContext = context.getApplicationContext();
|
||||
if (appContext == null) {
|
||||
LogUtils.e(TAG, "initMimoSdk: ApplicationContext is null");
|
||||
return;
|
||||
}
|
||||
// 2. 初始化SDK,捕获异常避免崩溃
|
||||
try {
|
||||
MimoSdk.init(appContext, new MimoCustomController() {
|
||||
@Override
|
||||
public boolean isCanUseLocation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MimoLocation getMimoLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanUseWifiState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean alist() {
|
||||
return true;
|
||||
}
|
||||
}, new MimoSdk.InitCallback() {
|
||||
@Override
|
||||
public void success() {
|
||||
LogUtils.d(TAG, "MimoSdk init success");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(int code, String msg) {
|
||||
LogUtils.e(TAG, "MimoSdk init fail, code=" + code + ",msg=" + msg);
|
||||
}
|
||||
});
|
||||
MimoSdk.setDebugOn(true);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "initMimoSdk: init failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取SharedPreferences实例(原逻辑保留,添加空指针校验)
|
||||
*/
|
||||
// SharedPreferences getSharedPreferences() {
|
||||
//// if (mSharedPreferences == null) {
|
||||
//// // 修复:使用ApplicationContext获取SharedPreferences,避免Activity Context泄露
|
||||
//// Context appContext = getActivity().getApplicationContext();
|
||||
//// if (appContext != null) {
|
||||
//// mSharedPreferences = appContext.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
|
||||
//// } else {
|
||||
//// LogUtils.e(TAG, "getSharedPreferences: ApplicationContext is null");
|
||||
//// // 降级方案:若ApplicationContext为空,使用Activity Context(仅作兼容)
|
||||
//// mSharedPreferences = getActivity().getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
|
||||
//// }
|
||||
//// }
|
||||
// return mSharedPreferences;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,484 @@
|
||||
package cc.winboll.studio.libaes.views;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Display;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.RadioGroup;
|
||||
import android.widget.Toast;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import cc.winboll.studio.libaes.R;
|
||||
import cc.winboll.studio.libaes.enums.ADsMode;
|
||||
import cc.winboll.studio.libaes.views.ADsControlView;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import com.miui.zeus.mimo.sdk.MimoCustomController;
|
||||
import com.miui.zeus.mimo.sdk.MimoLocation;
|
||||
import com.miui.zeus.mimo.sdk.MimoSdk;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/26 17:51
|
||||
* @Describe 广告模式控制控件(Java 7 兼容)
|
||||
* 支持:SP持久化、外部静态方法更新、Handler视图同步、外部静态方法读取
|
||||
*/
|
||||
public class ADsControlView extends LinearLayout {
|
||||
public static final String TAG = "ADsControlView";
|
||||
|
||||
|
||||
// SP存储配置
|
||||
private static final String SP_NAME = "ads_control_config";
|
||||
private static final String KEY_SELECTED_MODE = "selected_ads_mode";
|
||||
// 单机模式与米盟模式标志位
|
||||
ADsMode mADsMode;
|
||||
private static final String PRIVACY_VALUE = "privacy_value";
|
||||
// 隐私协议签约结果 0: 拒绝,1:赞同 2: 未签约
|
||||
String privacyAgreeValue;
|
||||
|
||||
// Handler消息标识
|
||||
private static final int MSG_UPDATE_MODE = 1001;
|
||||
|
||||
// 控件引用
|
||||
private RadioGroup rgAdsMode;
|
||||
private RadioButton rbStandalone;
|
||||
private RadioButton rbMimoSdk;
|
||||
|
||||
// 外部监听、SP实例、Handler实例
|
||||
private OnAdsModeSelectedListener listener;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private InternalHandler mHandler;
|
||||
private Context mContext;
|
||||
|
||||
// 静态列表:存储所有已创建的控件实例(Java 7 标准集合)
|
||||
private static final java.util.List<ADsControlView> sControlViews = new java.util.ArrayList<ADsControlView>();
|
||||
|
||||
// 构造方法(Java 7 兼容)
|
||||
public ADsControlView(Context context) {
|
||||
super(context);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public ADsControlView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public ADsControlView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public void setPrivacyAgreeValue(String privacyAgreeValue) {
|
||||
this.privacyAgreeValue = privacyAgreeValue;
|
||||
}
|
||||
|
||||
public String getPrivacyAgreeValue() {
|
||||
String privacyAgreeValue = sharedPreferences.getString(PRIVACY_VALUE, "0");
|
||||
return privacyAgreeValue;
|
||||
}
|
||||
|
||||
public void setADsMode(ADsMode mADsMode) {
|
||||
this.mADsMode = mADsMode;
|
||||
sharedPreferences.edit().putString(KEY_SELECTED_MODE, this.mADsMode.name()).apply();
|
||||
}
|
||||
|
||||
public ADsMode getADsMode() {
|
||||
String savedModeStr = sharedPreferences.getString(KEY_SELECTED_MODE, ADsMode.STANDALONE.name());
|
||||
mADsMode = ADsMode.fromValue(savedModeStr);
|
||||
return mADsMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图、SP、Handler
|
||||
*/
|
||||
private void initView(final Context context) {
|
||||
this.mContext = context;
|
||||
// 加载布局
|
||||
LayoutInflater.from(context).inflate(R.layout.view_adscontrol, this, true);
|
||||
|
||||
// 初始化SP
|
||||
sharedPreferences = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
|
||||
// 绑定控件
|
||||
rgAdsMode = (RadioGroup) findViewById(R.id.rg_ads_mode);
|
||||
rbStandalone = (RadioButton) findViewById(R.id.rb_standalone);
|
||||
rbMimoSdk = (RadioButton) findViewById(R.id.rb_mimo_sdk);
|
||||
|
||||
// 初始化Handler(主线程Looper)
|
||||
mHandler = new InternalHandler(Looper.getMainLooper());
|
||||
|
||||
// 注册控件实例到静态列表(线程安全)
|
||||
registerControlView(this);
|
||||
|
||||
// 从SP读取初始模式并设置
|
||||
ToastUtils.show(String.format("savedMode : %s", getADsMode().name()));
|
||||
setSelectedMode(getADsMode());
|
||||
|
||||
// 单选组选择事件监听
|
||||
rgAdsMode.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(RadioGroup group, int checkedId) {
|
||||
if (checkedId == R.id.rb_standalone) {
|
||||
setADsMode(ADsMode.STANDALONE);
|
||||
} else if (checkedId == R.id.rb_mimo_sdk) {
|
||||
showPrivacy(context, new OnPrivacyChangeListener(){
|
||||
@Override
|
||||
public void onAgreePrivacy() {
|
||||
setADsMode(ADsMode.MIMO_SDK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisagreePrivacy() {
|
||||
setADsMode(ADsMode.STANDALONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 【静态】显示隐私协议弹窗(供外部调用,带Context参数)
|
||||
* @param context 上下文(需传入Activity Context,用于弹窗显示)
|
||||
*/
|
||||
public static void showPrivacy(Context context, OnPrivacyChangeListener onPrivacyChangeListener) {
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "showPrivacy: Context is null, cannot show privacy dialog");
|
||||
return;
|
||||
}
|
||||
// 校验是否为Activity Context(弹窗必须依附Activity)
|
||||
Activity activity = null;
|
||||
try {
|
||||
activity = (Activity) context;
|
||||
} catch (ClassCastException e) {
|
||||
LogUtils.e(TAG, "showPrivacy: Context is not Activity Context", e);
|
||||
Toast.makeText(context.getApplicationContext(), "请传入Activity上下文以显示隐私协议", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
// 校验Activity状态
|
||||
if (activity.isFinishing() || activity.isDestroyed()) {
|
||||
LogUtils.e(TAG, "showPrivacy: Activity is finishing or destroyed");
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取隐私协议状态并处理逻辑
|
||||
SbhhharedPreferences sp = getPrivacySharedPreferences(context);
|
||||
String privacyAgreeValue = sp.getString(PRIVACY_VALUE, null);
|
||||
handlePrivacyLogic(activity, privacyAgreeValue, onPrivacyChangeListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 【静态】清理SP中存储的隐私协议状态(PRIVACY_VALUE)
|
||||
* 函数名:cleanprivacystatus(按要求命名)
|
||||
* @param context 上下文(建议使用ApplicationContext)
|
||||
*/
|
||||
public static void cleanPrivacyStatus(Context context) {
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "cleanPrivacyStatus: Context is null, cannot clean privacy status");
|
||||
return;
|
||||
}
|
||||
// 清空PRIVACY_VALUE值(移除该键,恢复初始未选择状态)
|
||||
SharedPreferences sp = getPrivacySharedPreferences(context);
|
||||
sp.edit().remove(PRIVACY_VALUE).apply();
|
||||
LogUtils.i(TAG, "cleanPrivacyStatus: Privacy status cleaned successfully");
|
||||
ToastUtils.show("cleanPrivacyStatus: Privacy status cleaned successfully");
|
||||
// 清理后同步更新广告模式为单机模式(避免隐私状态为空时仍加载广告)
|
||||
//ADsControlView.updateAdsModeByStatic(context, ADsMode.STANDALONE);
|
||||
}
|
||||
|
||||
// 【配套静态工具方法】获取隐私协议SP实例(供上述两个静态方法调用,需一并添加)
|
||||
private static SharedPreferences getPrivacySharedPreferences(Context context) {
|
||||
// 使用ApplicationContext获取SP,避免内存泄漏
|
||||
Context appContext = context.getApplicationContext();
|
||||
if (appContext != null) {
|
||||
return appContext.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
|
||||
}
|
||||
// 降级方案:若ApplicationContext为空,使用传入的Context
|
||||
return context.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
// 【配套静态工具方法】隐私协议逻辑处理(供上述两个静态方法调用,需一并添加)
|
||||
private static void handlePrivacyLogic(final Activity activity, String privacyAgreeValue, final OnPrivacyChangeListener onPrivacyChangeListener) {
|
||||
if (TextUtils.equals(privacyAgreeValue, String.valueOf(0))) {
|
||||
ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.STANDALONE);
|
||||
LogUtils.i(TAG, "已拒绝隐私协议,广告已处于不可用状态...");
|
||||
Toast.makeText(activity.getApplicationContext(), "已拒绝隐私协议,广告已处于不可用状态", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
} else if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
|
||||
ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.MIMO_SDK);
|
||||
LogUtils.i(TAG, "已同意隐私协议,开始初始化米盟SDK...");
|
||||
initMimoSdkStatic(activity.getApplicationContext());
|
||||
return;
|
||||
} else {
|
||||
LogUtils.i(TAG, "开始弹出隐私协议...");
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||
builder.setTitle("用户须知");
|
||||
builder.setMessage("小米广告SDK隐私政策: https://dev.mi.com/distribute/doc/details?pId=1688, 请复制到浏览器查看");
|
||||
builder.setIcon(R.drawable.ic_launcher);
|
||||
builder.setCancelable(false); // 点击对话框以外的区域不消失
|
||||
builder.setPositiveButton("同意", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
SharedPreferences sp = getPrivacySharedPreferences(activity);
|
||||
sp.edit().putString(PRIVACY_VALUE, String.valueOf(1)).apply();
|
||||
initMimoSdkStatic(activity.getApplicationContext());
|
||||
dialog.dismiss();
|
||||
onPrivacyChangeListener.onAgreePrivacy();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
SharedPreferences sp = getPrivacySharedPreferences(activity);
|
||||
sp.edit().putString(PRIVACY_VALUE, String.valueOf(0)).apply();
|
||||
ADsControlView.updateAdsModeByStatic(activity.getApplicationContext(), ADsMode.STANDALONE);
|
||||
dialog.dismiss();
|
||||
onPrivacyChangeListener.onDisagreePrivacy();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
// 配置弹窗位置(底部全屏)
|
||||
Window window = dialog.getWindow();
|
||||
if (window != null) {
|
||||
window.setGravity(Gravity.BOTTOM);
|
||||
WindowManager m = activity.getWindowManager();
|
||||
Display d = m.getDefaultDisplay();
|
||||
WindowManager.LayoutParams p = window.getAttributes();
|
||||
p.width = d.getWidth();
|
||||
window.setAttributes(p);
|
||||
}
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
// 【配套静态工具方法】静态初始化米盟SDK(供上述静态方法调用,需一并添加)
|
||||
private static void initMimoSdkStatic(Context appContext) {
|
||||
if (appContext == null) {
|
||||
LogUtils.e(TAG, "initMimoSdkStatic: ApplicationContext is null");
|
||||
return;
|
||||
}
|
||||
// 初始化SDK,捕获异常避免崩溃
|
||||
try {
|
||||
MimoSdk.init(appContext, new MimoCustomController() {
|
||||
@Override
|
||||
public boolean isCanUseLocation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MimoLocation getMimoLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanUseWifiState() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean alist() {
|
||||
return true;
|
||||
}
|
||||
}, new MimoSdk.InitCallback() {
|
||||
@Override
|
||||
public void success() {
|
||||
LogUtils.d(TAG, "MimoSdk init success (static)");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fail(int code, String msg) {
|
||||
LogUtils.e(TAG, "MimoSdk init fail (static), code=" + code + ",msg=" + msg);
|
||||
}
|
||||
});
|
||||
MimoSdk.setDebugOn(true);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "initMimoSdkStatic: init failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:外部调用更新SP中的模式,并发送消息通知控件更新
|
||||
* @param context 上下文(建议使用ApplicationContext)
|
||||
* @param mode 要设置的广告模式
|
||||
*/
|
||||
public static void updateAdsModeByStatic(Context context, ADsMode mode) {
|
||||
if (context == null || mode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 更新SP数据
|
||||
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
sp.edit().putString(KEY_SELECTED_MODE, mode.name()).apply();
|
||||
|
||||
// 2. 发送Handler消息,通知所有控件更新
|
||||
InternalHandler.sendUpdateModeMessage(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增静态方法:外部调用读取SP中存储的广告模式(Java 7 兼容)
|
||||
* @param context 上下文(建议使用ApplicationContext)
|
||||
* @return 存储的AdsMode,默认返回单机模式(STANDALONE)
|
||||
*/
|
||||
public static ADsMode getAdsModeFromStatic(Context context) {
|
||||
// 空指针校验:上下文为空时返回默认模式
|
||||
if (context == null) {
|
||||
return ADsMode.STANDALONE;
|
||||
}
|
||||
|
||||
// 读取SP数据
|
||||
SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
|
||||
String savedModeStr = sp.getString(KEY_SELECTED_MODE, ADsMode.STANDALONE.name());
|
||||
|
||||
// 解析枚举并返回(兼容SP中数据异常的情况)
|
||||
return ADsMode.fromValue(savedModeStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册控件实例到静态列表(线程安全)
|
||||
*/
|
||||
private static void registerControlView(ADsControlView view) {
|
||||
synchronized (sControlViews) {
|
||||
if (!sControlViews.contains(view)) {
|
||||
sControlViews.add(view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除控件实例(线程安全)
|
||||
*/
|
||||
private static void unregisterControlView(ADsControlView view) {
|
||||
synchronized (sControlViews) {
|
||||
sControlViews.remove(view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置选中模式(内部使用,更新UI)
|
||||
*/
|
||||
private void setSelectedMode(final ADsMode mode) {
|
||||
final ADsMode mode2;
|
||||
if (mode == null) {
|
||||
mode2 = ADsMode.STANDALONE;
|
||||
} else {
|
||||
mode2 = mode;
|
||||
}
|
||||
// 确保UI操作在主线程
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
if (mode2 == ADsMode.STANDALONE) {
|
||||
rbStandalone.setChecked(true);
|
||||
} else if (mode2 == ADsMode.MIMO_SDK) {
|
||||
rbMimoSdk.setChecked(true);
|
||||
}
|
||||
} else {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setSelectedMode(mode2);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前选中模式
|
||||
*/
|
||||
public ADsMode getSelectedMode() {
|
||||
int checkedId = rgAdsMode.getCheckedRadioButtonId();
|
||||
return checkedId == R.id.rb_mimo_sdk ? ADsMode.MIMO_SDK : ADsMode.STANDALONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置外部监听
|
||||
*/
|
||||
public void setOnAdsModeSelectedListener(OnAdsModeSelectedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部Handler类(Java 7 静态内部类,无隐藏API依赖)
|
||||
*/
|
||||
private static class InternalHandler extends Handler {
|
||||
static volatile InternalHandler _InternalHandler;
|
||||
public InternalHandler(Looper looper) {
|
||||
super(looper);
|
||||
_InternalHandler = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法:发送模式更新消息
|
||||
*/
|
||||
public static void sendUpdateModeMessage(ADsMode mode) {
|
||||
if (mode == null || _InternalHandler == null) {
|
||||
return;
|
||||
}
|
||||
Message msg = _InternalHandler.obtainMessage();
|
||||
msg.what = MSG_UPDATE_MODE;
|
||||
msg.obj = mode;
|
||||
_InternalHandler.sendMessage(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
super.handleMessage(msg);
|
||||
if (msg.what == MSG_UPDATE_MODE) {
|
||||
ADsMode mode = (ADsMode) msg.obj;
|
||||
if (mode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:替换isDetachedFromWindow()为isAttachedToWindow()(API 1兼容)
|
||||
// 逻辑调整:view.isAttachedToWindow() → 控件已附加到窗口(活跃状态)
|
||||
synchronized (sControlViews) {
|
||||
for (ADsControlView view : sControlViews) {
|
||||
// 三重校验:非空 + 可见 + 已附加到窗口(避免操作销毁/未初始化的控件)
|
||||
if (view != null && view.isShown() && view.isAttachedToWindow()) {
|
||||
view.setSelectedMode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生命周期方法:控件销毁时解除注册,避免内存泄漏
|
||||
*/
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
// 移除Handler回调
|
||||
if (mHandler != null) {
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
// 从静态列表中移除当前控件
|
||||
unregisterControlView(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部监听接口
|
||||
*/
|
||||
public interface OnAdsModeSelectedListener {
|
||||
void onModeSelected(ADsMode selectedMode);
|
||||
}
|
||||
|
||||
public interface OnPrivacyChangeListener {
|
||||
void onAgreePrivacy();
|
||||
void onDisagreePrivacy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import android.content.res.TypedArray;
|
||||
import android.net.Uri;
|
||||
import android.os.Message;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
@@ -20,11 +19,11 @@ 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.PrefUtils;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libapputils.utils.PrefUtils;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import java.io.IOException;
|
||||
import mehdi.sakout.aboutpage.AboutPage;
|
||||
import mehdi.sakout.aboutpage.Element;
|
||||
@@ -108,7 +107,7 @@ public class AboutView extends LinearLayout {
|
||||
mszAppDescription = mAPPInfo.getAppDescription();
|
||||
mnAppIcon = mAPPInfo.getAppIcon();
|
||||
|
||||
mszWinBoLLServerHost = GlobalApplication.isDebuging() ? "https://yun-preivew.winboll.cc": "https://yun.winboll.cc";
|
||||
mszWinBoLLServerHost = GlobalApplication.isDebugging() ? "https://yun-preivew.winboll.cc": "https://yun.winboll.cc";
|
||||
|
||||
try {
|
||||
mszAppVersionName = _mContext.getPackageManager().getPackageInfo(_mContext.getPackageName(), 0).versionName;
|
||||
@@ -194,8 +193,9 @@ public class AboutView extends LinearLayout {
|
||||
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 {
|
||||
@@ -215,8 +215,8 @@ public class AboutView extends LinearLayout {
|
||||
//.addGroup("Connect with us")
|
||||
.addEmail("ZhanGSKen<zhangsken@qq.com>")
|
||||
.addWebsite(mszHomePage)
|
||||
.addItem(elementGitWeb)
|
||||
.addItem(elementAppUpdate);
|
||||
.addItem(elementGitWeb);
|
||||
//.addItem(elementAppUpdate);
|
||||
//.addFacebook("the.medy")
|
||||
//.addTwitter("medyo80")
|
||||
//.addYoutube("UCdPQtdWIsg7_pi4mrRu46vA")
|
||||
@@ -225,11 +225,11 @@ public class AboutView extends LinearLayout {
|
||||
//.addInstagram("medyo80")
|
||||
//.create();
|
||||
|
||||
if (mAPPInfo.isAddDebugTools()) {
|
||||
/*if (mAPPInfo.isAddDebugTools()) {
|
||||
// 定义应用调试按钮
|
||||
//
|
||||
Element elementAppMode;
|
||||
if (GlobalApplication.isDebuging()) {
|
||||
if (GlobalApplication.isDebugging()) {
|
||||
elementAppMode = new Element(_mContext.getString(R.string.app_normal), R.drawable.ic_winboll);
|
||||
elementAppMode.setOnClickListener(mAppNormalOnClickListener);
|
||||
} else {
|
||||
@@ -237,7 +237,7 @@ public class AboutView extends LinearLayout {
|
||||
elementAppMode.setOnClickListener(mAppDebugOnClickListener);
|
||||
}
|
||||
aboutPage.addItem(elementAppMode);
|
||||
}
|
||||
}*/
|
||||
|
||||
return aboutPage.create();
|
||||
}
|
||||
@@ -263,8 +263,8 @@ public class AboutView extends LinearLayout {
|
||||
if (intent != null) {
|
||||
//intent.setAction(cc.winboll.studio.libapputils.intent.action.DEBUGVIEW);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
GlobalApplication.setIsDebuging(true);
|
||||
GlobalApplication.saveDebugStatus(_mContext);
|
||||
GlobalApplication.setIsDebugging(true);
|
||||
GlobalApplication.saveDebugStatus((GlobalApplication)_mContext.getApplicationContext());
|
||||
|
||||
WinBoLLActivityManager.getInstance().finishAll();
|
||||
context.startActivity(intent);
|
||||
@@ -275,8 +275,8 @@ public class AboutView extends LinearLayout {
|
||||
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
|
||||
if (intent != null) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
GlobalApplication.setIsDebuging(false);
|
||||
GlobalApplication.saveDebugStatus(_mContext);
|
||||
GlobalApplication.setIsDebugging(false);
|
||||
GlobalApplication.saveDebugStatus((GlobalApplication)_mContext.getApplicationContext());
|
||||
|
||||
WinBoLLActivityManager.getInstance().finishAll();
|
||||
context.startActivity(intent);
|
||||
@@ -301,7 +301,7 @@ public class AboutView extends LinearLayout {
|
||||
String szUrl = mszWinBoLLServerHost + "/studio/details.php?app=" + mszAppAPKFolderName;
|
||||
// 构建包含认证信息的请求
|
||||
String credential = "";
|
||||
if (GlobalApplication.isDebuging()) {
|
||||
if (GlobalApplication.isDebugging()) {
|
||||
credential = Credentials.basic(metDevUserName.getText().toString(), metDevUserPassword.getText().toString());
|
||||
PrefUtils.saveString(_mContext, "metDevUserName", metDevUserName.getText().toString());
|
||||
PrefUtils.saveString(_mContext, "metDevUserPassword", metDevUserPassword.getText().toString());
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/activitydrawerfragmentASupportToolbar1"/>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
@@ -19,7 +19,8 @@
|
||||
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1.0"
|
||||
android:id="@+id/activitydrawerfragmentDrawerLayout1">
|
||||
|
||||
<FrameLayout
|
||||
@@ -52,7 +53,13 @@
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<cc.winboll.studio.libaes.views.ADsBannerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/adsbanner"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
15
libaes/src/main/res/layout/view_adsbanner.xml
Normal file
15
libaes/src/main/res/layout/view_adsbanner.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/ads_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ads_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
46
libaes/src/main/res/layout/view_adscontrol.xml
Normal file
46
libaes/src/main/res/layout/view_adscontrol.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp"
|
||||
android:background="@android:color/white">
|
||||
|
||||
<!-- 标题提示 -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="运行模式选择"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@android:color/black"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<!-- 单选组 -->
|
||||
<RadioGroup
|
||||
android:id="@+id/rg_ads_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- 单机模式单选框 -->
|
||||
<RadioButton
|
||||
android:id="@+id/rb_standalone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="单机模式"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/darker_gray"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<!-- 米盟广告SDK模式单选框 -->
|
||||
<RadioButton
|
||||
android:id="@+id/rb_mimo_sdk"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="米盟广告SDK支持模式"
|
||||
android:textSize="14sp"
|
||||
android:textColor="@android:color/darker_gray" />
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
||||
35
libaes/src/main/res/menu/toolbar_studio_debug.xml
Normal file
35
libaes/src/main/res/menu/toolbar_studio_debug.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<!--<item android:title="@string/app_theme">
|
||||
<menu>-->
|
||||
<!-- 定义一组单选按钮 -->
|
||||
<!-- checkableBehavior的可选值由三个:single设置为单选,all为多选,none为普通选项 -->
|
||||
<!-- <group android:checkableBehavior="single">
|
||||
<item android:id="@+id/app_defaulttheme" android:title="@string/app_defaulttheme"/>
|
||||
<item android:id="@+id/app_skytheme" android:title="@string/app_skytheme"/>
|
||||
<item android:id="@+id/app_goldentheme" android:title="@string/app_goldentheme"/>
|
||||
</group>
|
||||
</menu>
|
||||
</item>-->
|
||||
|
||||
<item android:title="DebugTools">
|
||||
<menu>
|
||||
<item
|
||||
android:id="@+id/item_testcrashreport"
|
||||
android:title="Test Application Crash Report"/>
|
||||
<item
|
||||
android:id="@+id/item_unittest"
|
||||
android:title="UnitTest"/>
|
||||
<item
|
||||
android:id="@+id/item_log"
|
||||
android:title="APPLOG"/>
|
||||
<item
|
||||
android:id="@+id/item_info"
|
||||
android:title="Info"/>
|
||||
<item
|
||||
android:id="@+id/item_exitdebug"
|
||||
android:title="ExitDebug"/>
|
||||
</menu>
|
||||
</item>
|
||||
</menu>
|
||||
@@ -12,5 +12,10 @@
|
||||
<string name="text_GoldenTheme">GoldenTheme</string>
|
||||
<string name="text_MemorTheme">MemorTheme</string>
|
||||
<string name="text_TaoTheme">TaoTheme</string>
|
||||
|
||||
|
||||
<string name="app_normal">Click here is switch to Normal APP</string>
|
||||
<string name="app_debug">Click here is switch to APP DEBUG</string>
|
||||
<string name="gitea_home">GITEA HOME</string>
|
||||
<string name="app_update">APP UPDATE</string>
|
||||
|
||||
</resources>
|
||||
|
||||
5
libaes/src/main/res/xml/file_provider.xml
Normal file
5
libaes/src/main/res/xml/file_provider.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<external-path path="mimoDownload" name="files_root" />
|
||||
<external-path path="." name="external_storage_root" />
|
||||
</paths>
|
||||
16
libaes/src/main/res/xml/network_security_config.xml
Normal file
16
libaes/src/main/res/xml/network_security_config.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<network-security-config>
|
||||
<!-- 允许 winboll.cc 及其子域名的明文流量(HTTP) -->
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="true">winboll.cc</domain>
|
||||
</domain-config>
|
||||
<!-- 米盟 SDK 配置 -->
|
||||
<base-config cleartextTrafficPermitted="true" />
|
||||
<debug-overrides>
|
||||
<trust-anchors>
|
||||
<!-- Trust user added CAs while debuggable only -->
|
||||
<certificates src="system" />
|
||||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</debug-overrides>
|
||||
</network-security-config>
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Nov 13 05:16:16 HKT 2025
|
||||
stageCount=1
|
||||
#Fri Nov 21 11:41:04 HKT 2025
|
||||
stageCount=2
|
||||
libraryProject=libappbase
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.0
|
||||
publishVersion=15.11.1
|
||||
buildCount=0
|
||||
baseBetaVersion=15.11.1
|
||||
baseBetaVersion=15.11.2
|
||||
|
||||
@@ -21,7 +21,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
api 'cc.winboll.studio:libappbase:15.9.5'
|
||||
api 'cc.winboll.studio:libappbase:15.10.9'
|
||||
|
||||
// 二维码类库
|
||||
api 'com.google.zxing:core:3.4.1'
|
||||
@@ -32,6 +32,8 @@ dependencies {
|
||||
|
||||
// Html 解析
|
||||
api 'org.jsoup:jsoup:1.13.1'
|
||||
|
||||
api 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
// SSH
|
||||
//api 'com.jcraft:jsch:0.1.55'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Mon Sep 01 07:56:11 HKT 2025
|
||||
stageCount=7
|
||||
#Mon Sep 29 01:15:55 HKT 2025
|
||||
stageCount=3
|
||||
libraryProject=libapputils
|
||||
baseVersion=15.8
|
||||
publishVersion=15.8.6
|
||||
baseVersion=15.10
|
||||
publishVersion=15.10.2
|
||||
buildCount=0
|
||||
baseBetaVersion=15.8.7
|
||||
baseBetaVersion=15.10.3
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package cc.winboll.studio.libapputils.utils;
|
||||
|
||||
/**
|
||||
* @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;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,16 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.support.v4.content.FileProvider;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -22,7 +28,6 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
@@ -97,36 +102,6 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 把字符串写入文件,指定 UTF-8 编码
|
||||
//
|
||||
public static void writeStringToFile(String szFilePath, String szContent) throws IOException {
|
||||
File file = new File(szFilePath);
|
||||
if (!file.getParentFile().exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
}
|
||||
FileOutputStream outputStream = new FileOutputStream(file);
|
||||
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
|
||||
writer.write(szContent);
|
||||
writer.close();
|
||||
}
|
||||
|
||||
//
|
||||
// 读取文件到字符串,指定 UTF-8 编码
|
||||
//
|
||||
public static String readStringFromFile(String szFilePath) throws IOException {
|
||||
File file = new File(szFilePath);
|
||||
FileInputStream inputStream = new FileInputStream(file);
|
||||
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||
StringBuilder content = new StringBuilder();
|
||||
int character;
|
||||
while ((character = reader.read()) != -1) {
|
||||
content.append((char) character);
|
||||
}
|
||||
reader.close();
|
||||
return content.toString();
|
||||
}
|
||||
|
||||
public static boolean copyFile(File srcFile, File dstFile) {
|
||||
if (!srcFile.exists()) {
|
||||
LogUtils.d(TAG, "The original file does not exist.");
|
||||
@@ -154,4 +129,113 @@ public class FileUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取文件为字节数组(Java 7 语法)
|
||||
*/
|
||||
public static byte[] readByteArrayFromFile(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String readStringFromFile(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,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Sat Sep 06 01:57:20 HKT 2025
|
||||
stageCount=9
|
||||
#Sun Sep 28 12:37:31 HKT 2025
|
||||
stageCount=10
|
||||
libraryProject=
|
||||
baseVersion=15.3
|
||||
publishVersion=15.3.8
|
||||
publishVersion=15.3.9
|
||||
buildCount=0
|
||||
baseBetaVersion=15.3.9
|
||||
baseBetaVersion=15.3.10
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Jul 24 10:16:27 HKT 2025
|
||||
stageCount=2
|
||||
#Sun Sep 28 13:24:09 HKT 2025
|
||||
stageCount=3
|
||||
libraryProject=
|
||||
baseVersion=15.1
|
||||
publishVersion=15.1.1
|
||||
publishVersion=15.1.2
|
||||
buildCount=0
|
||||
baseBetaVersion=15.1.2
|
||||
baseBetaVersion=15.1.3
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#### 介绍
|
||||
安卓位置应用,有关于地理位置的相关应用。
|
||||
PS:使用感言~~~『記低用唔到』。
|
||||
|
||||
#### 软件架构
|
||||
适配安卓应用 [AIDE Pro] 的 Gradle 编译结构。
|
||||
|
||||
@@ -18,18 +18,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.positions"
|
||||
minSdkVersion 24
|
||||
minSdkVersion 23
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
// versionName 更新后需要手动设置
|
||||
// .winboll/winbollBuildProps.properties 文件的 stageCount=0
|
||||
// Gradle编译环境下合起来的 versionName 就是 "${versionName}.0"
|
||||
versionName "15.0"
|
||||
versionName "15.11"
|
||||
if(true) {
|
||||
versionName = genVersionName("${versionName}")
|
||||
}
|
||||
@@ -44,7 +47,8 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// https://mvnrepository.com/artifact/com.jzxiang.pickerview/TimePickerDialog
|
||||
api 'com.jzxiang.pickerview:TimePickerDialog:1.0.1'
|
||||
|
||||
// 谷歌定位服务核心依赖(FusedLocationProviderClient所在库)
|
||||
api 'com.google.android.gms:play-services-location:21.0.1'
|
||||
@@ -58,8 +62,6 @@ dependencies {
|
||||
api 'com.journeyapps:zxing-android-embedded:3.6.0'
|
||||
// 应用介绍页类库
|
||||
api 'io.github.medyo:android-about-page:2.0.0'
|
||||
// 吐司类库
|
||||
api 'com.github.getActivity:ToastUtils:10.5'
|
||||
// 网络连接类库
|
||||
api 'com.squareup.okhttp3:okhttp:4.4.1'
|
||||
// AndroidX 类库
|
||||
@@ -70,7 +72,7 @@ dependencies {
|
||||
//api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
|
||||
//api 'androidx.fragment:fragment:1.1.0'
|
||||
|
||||
api 'cc.winboll.studio:libaes:15.10.2'
|
||||
api 'cc.winboll.studio:libapputils:15.10.2'
|
||||
api 'cc.winboll.studio:libappbase:15.10.9'
|
||||
api 'cc.winboll.studio:libaes:15.11.0'
|
||||
api 'cc.winboll.studio:libappbase:15.11.0'
|
||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#Created by .winboll/winboll_app_build.gradle
|
||||
#Thu Oct 02 13:16:14 GMT 2025
|
||||
stageCount=8
|
||||
#Sat Nov 15 08:45:48 GMT 2025
|
||||
stageCount=2
|
||||
libraryProject=
|
||||
baseVersion=15.0
|
||||
publishVersion=15.0.7
|
||||
buildCount=29
|
||||
baseBetaVersion=15.0.8
|
||||
baseVersion=15.11
|
||||
publishVersion=15.11.1
|
||||
buildCount=21
|
||||
baseBetaVersion=15.11.2
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">寻龙记#</string>
|
||||
<string name="app_name">悟空笔记#</string>
|
||||
<string name="appplus_name">时空任务#</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Positions +</string>
|
||||
<string name="app_name">Positions</string>
|
||||
<string name="appplus_name">PositionsPlus+</string>
|
||||
|
||||
</resources>
|
||||
|
||||
19
positions/src/beta/res/xml/shortcutsmain.xml
Normal file
19
positions/src/beta/res/xml/shortcutsmain.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 切换启动入口的快捷菜单 -->
|
||||
<shortcut
|
||||
android:shortcutId="open_appplus"
|
||||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:shortcutShortLabel="@string/open_appplus"
|
||||
android:shortcutLongLabel="@string/open_appplus"
|
||||
android:shortcutDisabledMessage="@string/appplus_open_disabled">
|
||||
<intent
|
||||
android:action="cc.winboll.studio.positions.App.ACTION_OPEN_APPPLUS"
|
||||
android:targetPackage="cc.winboll.studio.positions.beta"
|
||||
android:targetClass="cc.winboll.studio.positions.activities.ShortcutActionActivity"
|
||||
android:data="open_appplus" />
|
||||
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
19
positions/src/beta/res/xml/shortcutsplus.xml
Normal file
19
positions/src/beta/res/xml/shortcutsplus.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 切换启动入口的快捷菜单 -->
|
||||
<shortcut
|
||||
android:shortcutId="close_appplus"
|
||||
android:enabled="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:shortcutShortLabel="@string/close_appplus"
|
||||
android:shortcutLongLabel="@string/close_appplus"
|
||||
android:shortcutDisabledMessage="@string/appplus_close_disabled">
|
||||
<intent
|
||||
android:action="cc.winboll.studio.positions.App.ACTION_CLOSE_APPPLUS"
|
||||
android:targetPackage="cc.winboll.studio.positions.beta"
|
||||
android:targetClass="cc.winboll.studio.positions.activities.ShortcutActionActivity"
|
||||
android:data="close_appplus" />
|
||||
|
||||
<categories android:name="android.shortcut.conversation" />
|
||||
</shortcut>
|
||||
</shortcuts>
|
||||
@@ -3,20 +3,32 @@
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="cc.winboll.studio.positions">
|
||||
|
||||
<!-- 前台服务权限(可选,提升后台定位稳定性,避免服务被回收) -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<!-- 只能在前台获取精确的位置信息 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
|
||||
<!-- 只有在前台运行时才能获取大致位置信息 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
|
||||
|
||||
<!-- 在后台使用位置信息 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
|
||||
<!-- 运行前台服务 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
|
||||
<!-- 运行“location”类型的前台服务 -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION"/>
|
||||
|
||||
<!-- 拥有完全的网络访问权限 -->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<!-- 在后台使用位置信息 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
|
||||
<!-- 安装快捷方式 -->
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
|
||||
|
||||
<!-- 读取您共享存储空间中的内容 -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
||||
<!-- 修改或删除您共享存储空间中的内容 -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.location.gps"
|
||||
@@ -32,7 +44,36 @@
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name">
|
||||
android:label="@string/app_name"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
|
||||
<action android:name="android.intent.action.EDIT"/>
|
||||
|
||||
<data android:mimeType="application/json"/>
|
||||
|
||||
<data android:mimeType="text/x-json"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name=".activities.CrashActivity"/>
|
||||
|
||||
<activity-alias
|
||||
android:name=".MainActivityWukong"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:enabled="true">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
@@ -42,25 +83,80 @@
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcutsmain"/>
|
||||
|
||||
</activity-alias>
|
||||
|
||||
<activity-alias
|
||||
android:name=".MainActivityLaojun"
|
||||
android:targetActivity=".MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/appplus_name"
|
||||
android:icon="@drawable/ic_positions_plus"
|
||||
android:enabled="false">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcutsplus"/>
|
||||
|
||||
</activity-alias>
|
||||
|
||||
<meta-data
|
||||
android:name="android.max_aspect"
|
||||
android:value="4.0"/>
|
||||
|
||||
<activity android:name=".GlobalApplication$CrashActivity"/>
|
||||
|
||||
<activity android:name="cc.winboll.studio.positions.activities.LocationActivity"/>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.version"
|
||||
android:value="@integer/google_play_services_version"/>
|
||||
|
||||
<service android:name=".services.MainService"/>
|
||||
<service
|
||||
android:name=".services.MainService"
|
||||
android:exported="false"/>
|
||||
|
||||
<service android:name=".services.AssistantService"/>
|
||||
<service
|
||||
android:name=".services.AssistantService"
|
||||
android:exported="false"/>
|
||||
|
||||
<service
|
||||
android:name=".services.DistanceRefreshService"
|
||||
android:exported="false"/>
|
||||
|
||||
<receiver android:name="cc.winboll.studio.positions.receivers.MotionStatusReceiver">
|
||||
|
||||
<intent-filter>
|
||||
|
||||
<action android:name="cc.winboll.studio.positions.receivers.MotionStatusReceiver"/>
|
||||
|
||||
</intent-filter>
|
||||
|
||||
</receiver>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"/>
|
||||
|
||||
</provider>
|
||||
|
||||
<activity android:name="cc.winboll.studio.positions.activities.ShortcutActionActivity"/>
|
||||
|
||||
<service android:name=".services.DistanceRefreshService"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
||||
@@ -14,7 +14,6 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.ViewGroup;
|
||||
@@ -22,9 +21,10 @@ import android.widget.HorizontalScrollView;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
import cc.winboll.studio.libappbase.GlobalApplication;
|
||||
import com.hjq.toast.ToastUtils;
|
||||
import com.hjq.toast.style.WhiteToastStyle;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.positions.utils.MyActivityLifecycleCallbacks;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
@@ -41,28 +41,39 @@ import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
|
||||
|
||||
public class App extends GlobalApplication {
|
||||
|
||||
public static volatile AppLevel _mAppLevel = AppLevel.WUKONG;
|
||||
|
||||
public static final String COMPONENT_WUKONG = "cc.winboll.studio.positions.MainActivityWukong";
|
||||
public static final String COMPONENT_LAOJUN = "cc.winboll.studio.positions.MainActivityLaojun";
|
||||
public static final String ACTION_OPEN_APPPLUS = "cc.winboll.studio.positions.App.ACTION_OPEN_APPPLUS";
|
||||
public static final String ACTION_CLOSE_APPPLUS = "cc.winboll.studio.positions.App.ACTION_CLOSE_APPPLUS";
|
||||
|
||||
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
|
||||
|
||||
MyActivityLifecycleCallbacks mMyActivityLifecycleCallbacks;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
setIsDebuging(BuildConfig.DEBUG);
|
||||
|
||||
|
||||
setIsDebugging(BuildConfig.DEBUG);
|
||||
|
||||
WinBoLLActivityManager.init(this);
|
||||
|
||||
|
||||
// 初始化 Toast 框架
|
||||
ToastUtils.init(this);
|
||||
// 设置 Toast 布局样式
|
||||
//ToastUtils.setView(R.layout.view_toast);
|
||||
ToastUtils.setStyle(new WhiteToastStyle());
|
||||
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||
|
||||
//ToastUtils.setStyle(new WhiteToastStyle());
|
||||
//ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
|
||||
|
||||
//CrashHandler.getInstance().registerGlobal(this);
|
||||
//CrashHandler.getInstance().registerPart(this);
|
||||
mMyActivityLifecycleCallbacks = new MyActivityLifecycleCallbacks();
|
||||
registerActivityLifecycleCallbacks(mMyActivityLifecycleCallbacks);
|
||||
}
|
||||
|
||||
public static void write(InputStream input, OutputStream output) throws IOException {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package cc.winboll.studio.positions;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/10 07:23
|
||||
* @Describe 应用级别类型枚举
|
||||
*/
|
||||
public enum AppLevel {
|
||||
WUKONG("wukong", "悟空级别"),
|
||||
LAOJUN("laojun", "老君级别");
|
||||
|
||||
public static final String TAG = "AppLevel";
|
||||
|
||||
// 枚举属性
|
||||
private final String code; // 编码(如 "wukong")
|
||||
private final String desc; // 描述
|
||||
|
||||
// 构造方法(Java 7 需显式定义)
|
||||
AppLevel(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
// Getter 方法(获取枚举属性)
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
// 可选:根据 code 获取枚举项(便于业务使用)
|
||||
public static AppLevel getByCode(String code) {
|
||||
for (AppLevel level : values()) {
|
||||
if (level.code.equals(code)) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return null; // 或抛出异常,根据业务需求调整
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CompoundButton;
|
||||
@@ -17,10 +18,13 @@ import androidx.core.content.ContextCompat;
|
||||
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.ToastUtils;
|
||||
import cc.winboll.studio.positions.activities.LocationActivity;
|
||||
import cc.winboll.studio.positions.activities.WinBoLLActivity;
|
||||
import cc.winboll.studio.positions.services.MainService;
|
||||
import cc.winboll.studio.positions.utils.APPPlusUtils;
|
||||
import cc.winboll.studio.positions.utils.AppConfigsUtil;
|
||||
import cc.winboll.studio.positions.utils.JsonShareHandler;
|
||||
import cc.winboll.studio.positions.utils.ServiceUtil;
|
||||
|
||||
/**
|
||||
* 主页面:仅负责
|
||||
@@ -30,17 +34,18 @@ import cc.winboll.studio.positions.utils.AppConfigsUtil;
|
||||
*/
|
||||
public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
public static final String TAG = "MainActivity";
|
||||
// 权限请求码(建议定义为类常量,避免魔法值)
|
||||
|
||||
// 权限请求码(建议定义为类常量,避免魔法值)
|
||||
private static final int REQUEST_LOCATION_PERMISSIONS = 1001;
|
||||
private static final int REQUEST_BACKGROUND_LOCATION_PERMISSION = 1002;
|
||||
|
||||
|
||||
// UI 控件:服务控制开关、顶部工具栏
|
||||
private Switch mServiceSwitch;
|
||||
private Button mManagePositionsButton;
|
||||
private Toolbar mToolbar;
|
||||
// 服务相关:服务实例、绑定状态标记
|
||||
//private DistanceRefreshService mDistanceService;
|
||||
private boolean isServiceBound = false;
|
||||
//private boolean isServiceBound = false;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -82,6 +87,10 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main); // 关联主页面布局
|
||||
|
||||
// 处理启动时的分享 Intent
|
||||
handleShareIntent(getIntent());
|
||||
|
||||
|
||||
// 1. 初始化顶部 Toolbar(保留原逻辑,设置页面标题)
|
||||
initToolbar();
|
||||
// 2. 初始化其他控件
|
||||
@@ -94,6 +103,31 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
//bindDistanceService();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
// 处理后续接收的分享 Intent(如应用已在后台)
|
||||
handleShareIntent(intent);
|
||||
}
|
||||
|
||||
private void handleShareIntent(Intent intent) {
|
||||
if (intent != null && Intent.ACTION_SEND.equals(intent.getAction())) {
|
||||
// 调用工具类,弹出确认对话框
|
||||
JsonShareHandler.handleSharedJsonWithConfirm(this, intent, new JsonShareHandler.ConfirmCallback() {
|
||||
@Override
|
||||
public void onConfirm(boolean isConfirm) {
|
||||
// 回调处理:isConfirm 为 true 表示接收并保存,false 表示取消
|
||||
if (!isConfirm) {
|
||||
Log.d("MainActivity", "用户取消接收文件");
|
||||
// 可添加取消后的逻辑(如关闭页面)
|
||||
// finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
@@ -113,9 +147,9 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
mToolbar = (Toolbar) findViewById(R.id.toolbar); // Java 7 显式 findViewById + 强转
|
||||
setSupportActionBar(mToolbar);
|
||||
// 给ActionBar设置标题(先判断非空,避免空指针异常)
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setTitle(getString(R.string.app_name));
|
||||
}
|
||||
AppLevel appLevel = AppConfigsUtil.getInstance(getApplicationContext()).getAppLevel(true);
|
||||
getSupportActionBar().setTitle(getString(R.string.app_name));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,18 +175,11 @@ public class MainActivity extends WinBoLLActivity implements IWinBoLLActivity {
|
||||
// 权限就绪:执行服务启停逻辑
|
||||
if (isChecked) {
|
||||
LogUtils.d(TAG, "设置启动服务");
|
||||
AppConfigsUtil.getInstance(MainActivity.this).setIsEnableMainService(true);
|
||||
// 启动服务(startService确保服务独立运行,不受Activity绑定影响)
|
||||
startService(new Intent(MainActivity.this, MainService.class));
|
||||
ServiceUtil.startAutoService(MainActivity.this);
|
||||
} else {
|
||||
LogUtils.d(TAG, "设置关闭服务");
|
||||
AppConfigsUtil.getInstance(MainActivity.this).setIsEnableMainService(false);
|
||||
// 停止服务前先解绑,避免服务被Activity持有
|
||||
// if (isServiceBound) {
|
||||
// unbindService(mServiceConn);
|
||||
// isServiceBound = false;
|
||||
// }
|
||||
stopService(new Intent(MainActivity.this, MainService.class));
|
||||
|
||||
ServiceUtil.stopAutoService(MainActivity.this);
|
||||
}
|
||||
|
||||
mManagePositionsButton.setEnabled(isChecked);
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package cc.winboll.studio.positions;
|
||||
import android.os.Bundle;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/13 15:21
|
||||
* @Describe MainActivityLaojun
|
||||
*/
|
||||
public class MainActivityLaojun extends MainActivity {
|
||||
|
||||
public static final String TAG = "MainActivityLaojun";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
ToastUtils.show("道法自然");
|
||||
LogUtils.d(TAG, "玩法归臻");
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package cc.winboll.studio.positions;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/15 15:14
|
||||
* @Describe 应用入口级别类型枚举
|
||||
*/
|
||||
public enum PointLevel {
|
||||
DORAEMON("doraemon", "叮铛级别"),
|
||||
WUKONG("wukong", "悟空级别"),
|
||||
LAOJUN("laojun", "老君级别");
|
||||
|
||||
public static final String TAG = "PointLevel";
|
||||
|
||||
// 枚举属性
|
||||
private final String code; // 编码(如 "wukong")
|
||||
private final String desc; // 描述
|
||||
|
||||
// 构造方法(Java 7 需显式定义)
|
||||
PointLevel(String code, String desc) {
|
||||
this.code = code;
|
||||
this.desc = desc;
|
||||
}
|
||||
|
||||
// Getter 方法(获取枚举属性)
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDesc() {
|
||||
return desc;
|
||||
}
|
||||
|
||||
// 可选:根据 code 获取枚举项(便于业务使用)
|
||||
public static PointLevel getByCode(String code) {
|
||||
for (PointLevel level : values()) {
|
||||
if (level.code.equals(code)) {
|
||||
return level;
|
||||
}
|
||||
}
|
||||
return null; // 或抛出异常,根据业务需求调整
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,27 @@ package cc.winboll.studio.positions.activities;
|
||||
* @Date 2025/09/29 18:22
|
||||
* @Describe 位置列表页面(适配MainService GPS接口+规范服务交互+完善生命周期)
|
||||
*/
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.positions.R;
|
||||
import cc.winboll.studio.positions.adapters.PositionAdapter;
|
||||
import cc.winboll.studio.positions.models.PositionModel;
|
||||
import cc.winboll.studio.positions.models.PositionTaskModel;
|
||||
import cc.winboll.studio.positions.services.MainService;
|
||||
import cc.winboll.studio.positions.R;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Java 7 语法适配:
|
||||
@@ -30,37 +34,65 @@ import java.util.ArrayList;
|
||||
* 3. 所有位置/任务操作通过 MainService 接口执行
|
||||
*/
|
||||
public class LocationActivity extends Activity {
|
||||
private static final String TAG = "LocationActivity";
|
||||
public static final String TAG = "LocationActivity";
|
||||
|
||||
private RecyclerView mRvPosition;
|
||||
private PositionAdapter mPositionAdapter;
|
||||
private ArrayList<PositionModel> mLocalPosCache; // 本地位置缓存(与MainService同步)
|
||||
|
||||
// MainService 引用+绑定状态
|
||||
|
||||
// MainService 引用+绑定状态(AtomicBoolean 确保多线程状态可见性)
|
||||
private MainService mMainService;
|
||||
private boolean isServiceBound = false;
|
||||
private final AtomicBoolean isServiceBound = new AtomicBoolean(false);
|
||||
// 标记 Adapter 是否已初始化(避免重复初始化/销毁后初始化)
|
||||
private final AtomicBoolean isAdapterInited = new AtomicBoolean(false);
|
||||
|
||||
// 服务连接(Java 7 匿名内部类实现)
|
||||
// ---------------------- 新增:GPS监听核心变量 ----------------------
|
||||
private MainService.GpsUpdateListener mGpsUpdateListener; // GPS监听实例
|
||||
private PositionModel mCurrentGpsPos; // 缓存当前GPS位置(供页面使用)
|
||||
// 本地位置缓存(解决服务数据未同步时Adapter空数据问题)
|
||||
private final ArrayList<PositionModel> mLocalPosCache = new ArrayList<PositionModel>();
|
||||
|
||||
|
||||
// 服务连接(Java 7 匿名内部类实现,强化状态同步+数据预加载)
|
||||
private ServiceConnection mServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
// 假设 MainService 用 LocalBinder 暴露实例(Java 7 强转)
|
||||
MainService.LocalBinder binder = (MainService.LocalBinder) service;
|
||||
mMainService = binder.getService();
|
||||
isServiceBound = true;
|
||||
// 1. 安全获取服务实例(避免强转失败+服务未就绪)
|
||||
if (!(service instanceof MainService.LocalBinder)) {
|
||||
LogUtils.e(TAG, "服务绑定失败:Binder类型不匹配(非MainService.LocalBinder)");
|
||||
isServiceBound.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
LogUtils.d(TAG, "MainService绑定成功,开始同步数据");
|
||||
// 从MainService同步初始数据(位置+任务)
|
||||
syncDataFromMainService();
|
||||
// 初始化Adapter(传入MainService实例,确保任务数据从服务获取)
|
||||
initPositionAdapter();
|
||||
try {
|
||||
MainService.LocalBinder binder = (MainService.LocalBinder) service;
|
||||
mMainService = binder.getService();
|
||||
// 2. 标记服务绑定成功(原子操作,确保多线程可见)
|
||||
isServiceBound.set(true);
|
||||
LogUtils.d(TAG, "MainService绑定成功,开始同步数据+初始化Adapter");
|
||||
|
||||
// 3. 同步服务数据到本地缓存(核心:先同步数据,再初始化Adapter)
|
||||
syncDataFromMainService();
|
||||
// 4. 注册GPS监听(确保监听在Adapter前初始化,数据不丢失)
|
||||
registerGpsListener();
|
||||
// 5. 初始化Adapter(传入本地缓存+服务实例,数据非空)
|
||||
initPositionAdapter();
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "服务绑定后初始化失败:" + e.getMessage());
|
||||
isServiceBound.set(false);
|
||||
mMainService = null;
|
||||
showToast("服务初始化失败,无法加载数据");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
LogUtils.w(TAG, "MainService断开连接,清空引用");
|
||||
LogUtils.w(TAG, "MainService断开连接,清空引用+标记状态");
|
||||
// 1. 清空服务引用+标记绑定状态
|
||||
mMainService = null;
|
||||
isServiceBound = false;
|
||||
isServiceBound.set(false);
|
||||
// 2. 标记Adapter未初始化(下次绑定需重新初始化)
|
||||
isAdapterInited.set(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -69,159 +101,361 @@ public class LocationActivity extends Activity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_location);
|
||||
|
||||
// 初始化视图+本地缓存
|
||||
// 1. 初始化视图(优先执行,避免Adapter初始化时视图为空)
|
||||
initView();
|
||||
mLocalPosCache = new ArrayList<PositionModel>();
|
||||
|
||||
// 绑定MainService(确保Activity启动时就拿到服务实例)
|
||||
// 2. 初始化GPS监听(提前创建,避免绑定服务后空指针)
|
||||
initGpsUpdateListener();
|
||||
// 3. 绑定MainService(最后执行,确保视图/监听已就绪)
|
||||
bindMainService();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化视图(RecyclerView)
|
||||
* 初始化视图(RecyclerView)- 确保视图先于Adapter初始化
|
||||
*/
|
||||
private void initView() {
|
||||
mRvPosition = (RecyclerView) findViewById(R.id.rv_position_list);
|
||||
// Java 7 显式设置布局管理器(LinearLayoutManager)
|
||||
// 1. 显式设置布局管理器(避免Adapter设置时无布局管理器崩溃)
|
||||
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
|
||||
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
mRvPosition.setLayoutManager(layoutManager);
|
||||
// 2. 初始化本地缓存(避免首次加载时缓存为空)
|
||||
mLocalPosCache.clear();
|
||||
LogUtils.d(TAG, "视图初始化完成(布局管理器+本地缓存已就绪)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定MainService(Java 7 显式Intent)
|
||||
* 绑定MainService(Java 7 显式Intent,强化绑定安全性)
|
||||
*/
|
||||
private void bindMainService() {
|
||||
// 1. 避免重复绑定(快速重建Activity时防止多绑定)
|
||||
if (isServiceBound.get()) {
|
||||
LogUtils.w(TAG, "无需重复绑定:MainService已绑定");
|
||||
return;
|
||||
}
|
||||
|
||||
Intent serviceIntent = new Intent(this, MainService.class);
|
||||
// 绑定服务(BIND_AUTO_CREATE:服务不存在时自动创建)
|
||||
bindService(serviceIntent, mServiceConnection, BIND_AUTO_CREATE);
|
||||
LogUtils.d(TAG, "发起MainService绑定请求");
|
||||
// 2. 绑定服务(BIND_AUTO_CREATE:服务不存在时自动创建,增加绑定成功率)
|
||||
boolean bindSuccess = bindService(serviceIntent, mServiceConnection, BIND_AUTO_CREATE);
|
||||
if (!bindSuccess) {
|
||||
LogUtils.e(TAG, "发起MainService绑定请求失败(服务未找到/系统限制)");
|
||||
showToast("服务绑定失败,无法加载位置数据");
|
||||
} else {
|
||||
LogUtils.d(TAG, "MainService绑定请求已发起");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从MainService同步数据(位置+任务)
|
||||
* 从MainService同步数据到本地缓存(核心解决Adapter空数据问题)
|
||||
* 作用:1. 服务数据优先同步到本地,Adapter基于本地缓存初始化
|
||||
* 2. 避免服务数据更新时直接操作Adapter,通过缓存中转
|
||||
*/
|
||||
private void syncDataFromMainService() {
|
||||
if (!isServiceBound || mMainService == null) {
|
||||
LogUtils.w(TAG, "同步数据失败:MainService未绑定");
|
||||
showToast("服务未就绪,无法加载数据");
|
||||
// 1. 安全校验(服务未绑定/服务空,用本地缓存兜底)
|
||||
if (!isServiceBound.get() || mMainService == null) {
|
||||
LogUtils.w(TAG, "同步数据:服务未就绪,使用本地缓存(当前缓存量=" + mLocalPosCache.size() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
// 同步位置数据(从服务获取最新列表)
|
||||
ArrayList<PositionModel> servicePosList = mMainService.getPositionList();
|
||||
if (servicePosList != null && !servicePosList.isEmpty()) {
|
||||
mLocalPosCache.clear();
|
||||
mLocalPosCache.addAll(servicePosList);
|
||||
LogUtils.d(TAG, "从MainService同步位置数据完成:数量=" + mLocalPosCache.size());
|
||||
}
|
||||
try {
|
||||
// 2. 从服务获取最新位置数据(同步操作,确保数据拿到后再返回)
|
||||
ArrayList<PositionModel> servicePosList = mMainService.getPositionList();
|
||||
// 3. 同步到本地缓存(清空旧数据+添加新数据,避免重复)
|
||||
synchronized (mLocalPosCache) { // 加锁避免多线程操作缓存冲突
|
||||
mLocalPosCache.clear();
|
||||
if (servicePosList != null && !servicePosList.isEmpty()) {
|
||||
mLocalPosCache.addAll(servicePosList);
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "数据同步完成:服务位置数=" + (servicePosList == null ? 0 : servicePosList.size())
|
||||
+ ",本地缓存数=" + mLocalPosCache.size());
|
||||
|
||||
// 同步任务数据(无需本地缓存,Adapter直接从服务获取)
|
||||
ArrayList<PositionTaskModel> serviceTaskList = mMainService.getAllTasks();
|
||||
LogUtils.d(TAG, "从MainService同步任务数据完成:数量=" + serviceTaskList.size());
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "同步服务数据失败:" + e.getMessage());
|
||||
// 异常时保留本地缓存,避免Adapter无数据
|
||||
LogUtils.w(TAG, "同步失败,使用本地缓存兜底(缓存量=" + mLocalPosCache.size() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化PositionAdapter(核心:传入MainService实例)
|
||||
* 初始化PositionAdapter(核心优化:基于本地缓存初始化,避免空数据)
|
||||
*/
|
||||
private void initPositionAdapter() {
|
||||
if (mMainService == null) {
|
||||
LogUtils.e(TAG, "初始化Adapter失败:MainService为空");
|
||||
// 1. 多重安全校验(避免销毁后初始化/重复初始化/依赖未就绪)
|
||||
if (isAdapterInited.get() || !isServiceBound.get() || mMainService == null || mRvPosition == null) {
|
||||
LogUtils.w(TAG, "Adapter初始化跳过:"
|
||||
+ "已初始化=" + isAdapterInited.get()
|
||||
+ ",服务绑定=" + isServiceBound.get()
|
||||
+ ",视图就绪=" + (mRvPosition != null));
|
||||
return;
|
||||
}
|
||||
|
||||
// Java 7 显式初始化Adapter,传入上下文+本地位置缓存+MainService实例
|
||||
mPositionAdapter = new PositionAdapter(this, mLocalPosCache, mMainService);
|
||||
try {
|
||||
// 2. 基于本地缓存初始化Adapter(缓存已同步服务数据,非空)
|
||||
mPositionAdapter = new PositionAdapter(this, mLocalPosCache, mMainService);
|
||||
|
||||
// 设置Adapter回调(处理位置删除/保存,最终同步到MainService)
|
||||
mPositionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
|
||||
@Override
|
||||
public void onDeleteClick(int position) {
|
||||
// 删除逻辑:先删本地缓存,再调用MainService接口删服务数据
|
||||
if (position < 0 || position >= mLocalPosCache.size()) {
|
||||
LogUtils.w(TAG, "删除位置失败:无效索引=" + position);
|
||||
return;
|
||||
}
|
||||
PositionModel deletePos = mLocalPosCache.get(position);
|
||||
if (deletePos != null && !deletePos.getPositionId().isEmpty()) {
|
||||
// 1. 调用MainService接口删除服务端数据
|
||||
mMainService.removePosition(deletePos.getPositionId());
|
||||
// 2. 删除本地缓存数据
|
||||
mLocalPosCache.remove(position);
|
||||
// 3. 通知Adapter刷新
|
||||
mPositionAdapter.notifyItemRemoved(position);
|
||||
showToast("删除位置成功:" + deletePos.getMemo());
|
||||
LogUtils.d(TAG, "删除位置完成:ID=" + deletePos.getPositionId() + "(已同步MainService)");
|
||||
}
|
||||
}
|
||||
});
|
||||
// 3. 设置删除回调(删除时同步服务+本地缓存+Adapter)
|
||||
mPositionAdapter.setOnDeleteClickListener(new PositionAdapter.OnDeleteClickListener() {
|
||||
@Override
|
||||
public void onDeleteClick(int position) {
|
||||
// 安全校验(索引有效+服务绑定+缓存非空)
|
||||
if (position < 0 || position >= mLocalPosCache.size() || !isServiceBound.get() || mMainService == null) {
|
||||
LogUtils.w(TAG, "删除位置失败:索引无效/服务未就绪(索引=" + position + ",缓存量=" + mLocalPosCache.size() + ")");
|
||||
return;
|
||||
}
|
||||
|
||||
mPositionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
|
||||
@Override
|
||||
public void onSavePositionClick(int position, PositionModel updatedPos) {
|
||||
// 保存逻辑:先更本地缓存,再调用MainService接口更新服务数据
|
||||
if (!isServiceBound || mMainService == null) {
|
||||
LogUtils.w(TAG, "保存位置失败:MainService未绑定");
|
||||
showToast("服务未就绪,保存失败");
|
||||
return;
|
||||
}
|
||||
if (position < 0 || position >= mLocalPosCache.size()) {
|
||||
LogUtils.w(TAG, "保存位置失败:无效索引=" + position);
|
||||
return;
|
||||
}
|
||||
PositionModel deletePos = mLocalPosCache.get(position);
|
||||
if (deletePos != null && !deletePos.getPositionId().isEmpty()) {
|
||||
// 步骤1:调用服务删除(确保服务数据一致性)
|
||||
mMainService.removePosition(deletePos.getPositionId());
|
||||
// 步骤2:删除本地缓存(确保缓存与服务同步)
|
||||
synchronized (mLocalPosCache) {
|
||||
mLocalPosCache.remove(position);
|
||||
}
|
||||
// 步骤3:通知Adapter刷新(基于缓存操作,避免空数据)
|
||||
mPositionAdapter.notifyItemRemoved(position);
|
||||
showToast("删除位置成功:" + deletePos.getMemo());
|
||||
LogUtils.d(TAG, "删除位置完成:ID=" + deletePos.getPositionId() + "(服务+缓存已同步)");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 1. 调用MainService接口更新服务端数据
|
||||
mMainService.updatePosition(updatedPos);
|
||||
// 2. 更新本地缓存数据
|
||||
mLocalPosCache.set(position, updatedPos);
|
||||
// 3. 通知Adapter刷新(可选,Adapter已本地同步)
|
||||
mPositionAdapter.notifyItemChanged(position);
|
||||
showToast("保存位置成功:" + updatedPos.getMemo());
|
||||
LogUtils.d(TAG, "保存位置完成:ID=" + updatedPos.getPositionId() + "(已同步MainService)");
|
||||
}
|
||||
});
|
||||
// 4. 设置保存回调(保存时同步服务+本地缓存+Adapter)
|
||||
mPositionAdapter.setOnSavePositionClickListener(new PositionAdapter.OnSavePositionClickListener() {
|
||||
@Override
|
||||
public void onSavePositionClick(int position, PositionModel updatedPos) {
|
||||
// 安全校验(索引有效+服务绑定+数据非空)
|
||||
if (!isServiceBound.get() || mMainService == null
|
||||
|| position < 0 || position >= mLocalPosCache.size() || updatedPos == null) {
|
||||
LogUtils.w(TAG, "保存位置失败:服务未就绪/索引无效/数据空");
|
||||
showToast("服务未就绪,保存失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置Adapter到RecyclerView
|
||||
mRvPosition.setAdapter(mPositionAdapter);
|
||||
LogUtils.d(TAG, "PositionAdapter初始化完成(已绑定MainService)");
|
||||
// 步骤1:调用服务更新(确保服务数据一致性)
|
||||
mMainService.updatePosition(updatedPos);
|
||||
// 步骤2:更新本地缓存(确保缓存与服务同步)
|
||||
synchronized (mLocalPosCache) {
|
||||
mLocalPosCache.set(position, updatedPos);
|
||||
}
|
||||
// 步骤3:通知Adapter刷新(基于缓存操作,避免空数据)
|
||||
mPositionAdapter.notifyItemChanged(position);
|
||||
showToast("保存位置成功:" + updatedPos.getMemo());
|
||||
LogUtils.d(TAG, "保存位置完成:ID=" + updatedPos.getPositionId() + "(服务+缓存已同步)");
|
||||
}
|
||||
});
|
||||
|
||||
// 5. 设置Adapter到RecyclerView(最后一步,确保Adapter已配置完成)
|
||||
mRvPosition.setAdapter(mPositionAdapter);
|
||||
// 6. 标记Adapter已初始化(避免重复初始化)
|
||||
isAdapterInited.set(true);
|
||||
LogUtils.d(TAG, "PositionAdapter初始化完成(基于本地缓存,数据量=" + mLocalPosCache.size() + ")");
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "Adapter初始化失败:" + e.getMessage());
|
||||
isAdapterInited.set(false);
|
||||
mPositionAdapter = null;
|
||||
showToast("位置列表初始化失败,请重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示Toast(Java 7 显式Toast.makeText)
|
||||
* 显示Toast(Java 7 显式Toast.makeText,避免空Context)
|
||||
*/
|
||||
private void showToast(String content) {
|
||||
if (isFinishing() || isDestroyed()) { // 避免Activity销毁后弹Toast崩溃
|
||||
LogUtils.w(TAG, "Activity已销毁,跳过Toast:" + content);
|
||||
return;
|
||||
}
|
||||
Toast.makeText(this, content, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
// ---------------------- 页面交互(新增位置逻辑保留,适配GPS数据) ----------------------
|
||||
/**
|
||||
* 新增位置(调用服务addPosition(),可选:用当前GPS位置初始化新位置)
|
||||
*/
|
||||
public void addNewPosition(View view) {
|
||||
// 1. 隐藏软键盘(避免软键盘遮挡操作)
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null && getCurrentFocus() != null) {
|
||||
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
|
||||
}
|
||||
|
||||
// 2. 安全校验(服务未绑定,不允许新增)
|
||||
if (!isServiceBound.get() || mMainService == null) {
|
||||
LogUtils.w(TAG, "新增位置失败:MainService未绑定");
|
||||
showToast("服务未就绪,无法新增位置");
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 创建新位置模型(优化:优先用当前GPS位置初始化,无则用默认值)
|
||||
PositionModel newPos = new PositionModel();
|
||||
newPos.setPositionId(PositionModel.genPositionId()); // 生成唯一ID(需PositionModel实现)
|
||||
if (mCurrentGpsPos != null) {
|
||||
newPos.setLongitude(mCurrentGpsPos.getLongitude());
|
||||
newPos.setLatitude(mCurrentGpsPos.getLatitude());
|
||||
newPos.setMemo("当前GPS位置(可编辑)");
|
||||
} else {
|
||||
newPos.setLongitude(116.404267); // 北京经度(默认值)
|
||||
newPos.setLatitude(39.915119); // 北京纬度(默认值)
|
||||
newPos.setMemo("默认位置(可编辑备注)");
|
||||
}
|
||||
newPos.setIsSimpleView(true); // 默认简单视图
|
||||
newPos.setIsEnableRealPositionDistance(true); // 启用距离计算(依赖GPS)
|
||||
|
||||
// 4. 调用服务新增+同步本地缓存(确保缓存与服务一致)
|
||||
mMainService.addPosition(newPos);
|
||||
synchronized (mLocalPosCache) {
|
||||
mLocalPosCache.add(newPos);
|
||||
}
|
||||
LogUtils.d(TAG, "通过服务新增位置:ID=" + newPos.getPositionId() + ",纬度=" + newPos.getLatitude() + "(缓存已同步)");
|
||||
|
||||
// 5. 刷新Adapter(基于缓存操作,确保数据立即显示)
|
||||
if (isAdapterInited.get() && mPositionAdapter != null) {
|
||||
mPositionAdapter.notifyItemInserted(mLocalPosCache.size() - 1);
|
||||
}
|
||||
showToast("新增位置成功(已启用GPS距离计算)");
|
||||
}
|
||||
|
||||
// ---------------------- 新增:GPS监听初始化+注册/反注册(核心适配逻辑) ----------------------
|
||||
/**
|
||||
* 初始化GPS监听:实现MainService.GpsUpdateListener,接收实时GPS数据
|
||||
*/
|
||||
private void initGpsUpdateListener() {
|
||||
LogUtils.d(TAG, "initGpsUpdateListener()");
|
||||
mGpsUpdateListener = new MainService.GpsUpdateListener() {
|
||||
@Override
|
||||
public void onGpsPositionUpdated(PositionModel currentGpsPos) {
|
||||
if (currentGpsPos == null || isFinishing() || isDestroyed()) {
|
||||
LogUtils.w(TAG, "GPS位置更新:数据为空或Activity已销毁");
|
||||
return;
|
||||
}
|
||||
// 缓存当前GPS位置(供页面其他逻辑使用)
|
||||
mCurrentGpsPos = currentGpsPos;
|
||||
LogUtils.d(TAG, String.format("收到GPS更新:纬度=%.4f,经度=%.4f"
|
||||
, currentGpsPos.getLatitude(), currentGpsPos.getLongitude()));
|
||||
// 安全更新UI(避免Activity销毁后操作视图崩溃)
|
||||
((TextView)findViewById(R.id.tv_latitude)).setText(String.format("当前纬度:%f", currentGpsPos.getLatitude()));
|
||||
((TextView)findViewById(R.id.tv_longitude)).setText(String.format("当前经度:%f", currentGpsPos.getLongitude()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGpsStatusChanged(String status) {
|
||||
if (status == null || isFinishing() || isDestroyed()) return;
|
||||
LogUtils.d(TAG, "GPS状态变化:" + status);
|
||||
if (status.contains("未开启") || status.contains("权限") || status.contains("失败")) {
|
||||
ToastUtils.show("GPS提示:" + status);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册GPS监听:调用MainService的PUBLIC方法,绑定监听
|
||||
*/
|
||||
private void registerGpsListener() {
|
||||
// 安全校验(避免Activity销毁/服务未绑定/监听为空时注册)
|
||||
if (isFinishing() || isDestroyed() || !isServiceBound.get() || mMainService == null || mGpsUpdateListener == null) {
|
||||
LogUtils.w(TAG, "GPS监听注册跳过:Activity状态异常/依赖未就绪");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mMainService.registerGpsUpdateListener(mGpsUpdateListener);
|
||||
LogUtils.d(TAG, "GPS监听已注册");
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "GPS监听注册失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 反注册GPS监听:调用MainService的PUBLIC方法,解绑监听(核心防内存泄漏+数据异常)
|
||||
*/
|
||||
private void unregisterGpsListener() {
|
||||
// 避免Activity销毁后调用服务方法(防止空指针/服务已解绑)
|
||||
if (mMainService == null || mGpsUpdateListener == null) {
|
||||
LogUtils.w(TAG, "GPS监听反注册跳过:服务/监听未初始化");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mMainService.unregisterGpsUpdateListener(mGpsUpdateListener);
|
||||
LogUtils.d(TAG, "GPS监听已反注册");
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "GPS监听反注册失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面可见时同步数据(解决快速切回时数据未更新问题)
|
||||
* 场景:快速关闭再打开Activity,服务已绑定但数据未重新同步
|
||||
*/
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
// 1. 服务已绑定但Adapter未初始化:重新同步数据+初始化Adapter
|
||||
if (isServiceBound.get() && mMainService != null && !isAdapterInited.get()) {
|
||||
LogUtils.d(TAG, "onResume:服务已绑定但Adapter未初始化,重新同步数据");
|
||||
syncDataFromMainService();
|
||||
initPositionAdapter();
|
||||
}
|
||||
// 2. 服务已绑定且Adapter已初始化:刷新数据(确保与服务同步)
|
||||
else if (isServiceBound.get() && mMainService != null && isAdapterInited.get() && mPositionAdapter != null) {
|
||||
syncDataFromMainService();
|
||||
mPositionAdapter.notifyDataSetChanged();
|
||||
LogUtils.d(TAG, "onResume:刷新位置数据(与服务同步)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面不可见时暂停操作(避免后台操作导致数据异常)
|
||||
*/
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
// 避免后台时仍执行UI刷新(如GPS更新触发的视图操作)
|
||||
LogUtils.d(TAG, "onPause:页面不可见,暂停UI相关操作");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
LogUtils.d(TAG, "onDestroy:开始释放资源");
|
||||
|
||||
// 1. 释放Adapter资源(反注册服务监听,避免内存泄漏)
|
||||
// 1. 反注册GPS监听(优先执行,避免服务持有Activity引用导致内存泄漏)
|
||||
unregisterGpsListener();
|
||||
|
||||
// 2. 释放Adapter资源(反注册可能的监听,避免内存泄漏)
|
||||
if (mPositionAdapter != null) {
|
||||
mPositionAdapter.release();
|
||||
mPositionAdapter = null; // 清空引用,帮助GC回收
|
||||
LogUtils.d(TAG, "Adapter资源已释放");
|
||||
}
|
||||
|
||||
// 2. 解绑MainService(避免Activity销毁后服务仍被持有)
|
||||
if (isServiceBound) {
|
||||
unbindService(mServiceConnection);
|
||||
LogUtils.d(TAG, "MainService解绑完成");
|
||||
// 3. 解绑MainService(最后执行,确保其他资源已释放)
|
||||
if (isServiceBound.get()) {
|
||||
try {
|
||||
unbindService(mServiceConnection);
|
||||
LogUtils.d(TAG, "MainService解绑完成");
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 捕获“服务未绑定”异常(快速开关时可能出现,避免崩溃)
|
||||
LogUtils.d(TAG, "解绑MainService失败:服务未绑定(可能已提前解绑)");
|
||||
}
|
||||
// 重置绑定状态+服务引用
|
||||
isServiceBound.set(false);
|
||||
mMainService = null;
|
||||
}
|
||||
|
||||
// 4. 清空本地缓存+GPS引用(帮助GC回收)
|
||||
synchronized (mLocalPosCache) {
|
||||
mLocalPosCache.clear();
|
||||
}
|
||||
mCurrentGpsPos = null;
|
||||
mGpsUpdateListener = null;
|
||||
isAdapterInited.set(false);
|
||||
LogUtils.d(TAG, "所有资源释放完成(onDestroy执行结束)");
|
||||
}
|
||||
|
||||
public static class LocalBinder extends android.os.Binder {
|
||||
// 持有 MainService 实例引用
|
||||
private MainService mService;
|
||||
|
||||
// 构造时传入服务实例
|
||||
public LocalBinder(MainService service) {
|
||||
this.mService = service;
|
||||
}
|
||||
|
||||
// 对外提供获取服务实例的方法(供Activity调用)
|
||||
public MainService getService() {
|
||||
return mService;
|
||||
}
|
||||
}
|
||||
// ---------------------- 移除重复定义:LocalBinder 统一在 MainService 中定义 ----------------------
|
||||
// 说明:原LocationActivity中的LocalBinder是重复定义(MainService已实现),会导致类型强转失败
|
||||
// 此处删除该类,确保Activity绑定服务时强转的是MainService中的LocalBinder
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package cc.winboll.studio.positions.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.PersistableBundle;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.positions.R;
|
||||
import cc.winboll.studio.positions.utils.APPPlusUtils;
|
||||
import cc.winboll.studio.positions.utils.AppConfigsUtil;
|
||||
import cc.winboll.studio.positions.AppLevel;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/15 13:45
|
||||
* @Describe 应用快捷方式活动类
|
||||
*/
|
||||
public class ShortcutActionActivity extends Activity {
|
||||
|
||||
public static final String TAG = "ShortcutActionActivity";
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// 处理应用级别的切换请求
|
||||
handleSwitchRequest();
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// @Override
|
||||
// public void onPostCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
|
||||
// super.onPostCreate(savedInstanceState, persistentState);
|
||||
// finish();
|
||||
// }
|
||||
|
||||
// @Override
|
||||
// protected void onStart() {
|
||||
// super.onStart();
|
||||
// }
|
||||
|
||||
/**
|
||||
* 处理应用图标快捷菜单的请求
|
||||
*/
|
||||
private void handleSwitchRequest() {
|
||||
Intent intent = getIntent();
|
||||
if (intent != null && "open_appplus".equals(intent.getDataString())) {
|
||||
ToastUtils.show("已添加" + getString(R.string.app_name) + "附加组件");
|
||||
AppConfigsUtil.getInstance(getApplicationContext()).setAppLevel(AppLevel.LAOJUN);
|
||||
APPPlusUtils.openAPPPlus(this);
|
||||
//moveTaskToBack(true);
|
||||
}
|
||||
if (intent != null && "close_appplus".equals(intent.getDataString())) {
|
||||
ToastUtils.show("已移除" + getString(R.string.app_name) + "附加组件");
|
||||
AppConfigsUtil.getInstance(getApplicationContext()).setAppLevel(AppLevel.WUKONG);
|
||||
APPPlusUtils.closeAPPPlus(this);
|
||||
//moveTaskToBack(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,19 @@ 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.ToastUtils;
|
||||
import cc.winboll.studio.positions.App;
|
||||
import cc.winboll.studio.positions.PointLevel;
|
||||
import cc.winboll.studio.positions.R;
|
||||
import cc.winboll.studio.positions.utils.ActivityAliasUtils;
|
||||
import cc.winboll.studio.positions.utils.AppConfigsUtil;
|
||||
|
||||
public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivity {
|
||||
|
||||
public static final String TAG = "WinBoLLActivity";
|
||||
|
||||
public static volatile PointLevel _mPointLevel = PointLevel.WUKONG;
|
||||
|
||||
@Override
|
||||
public Activity getActivity() {
|
||||
return this;
|
||||
@@ -30,18 +38,67 @@ public class WinBoLLActivity extends AppCompatActivity implements IWinBoLLActivi
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//ToastUtils.show("onResume");
|
||||
|
||||
// ActivityAliasUtils 工具使用示例
|
||||
//
|
||||
// // 获取真实的目标组件名(即使通过 alias 启动,也能拿到 OriginalActivity)
|
||||
// String realTargetName = ActivityAliasUtils.getRealTargetNameFromIntent(this);
|
||||
// LogUtils.d("AliasActivity", "真实组件名:" + realTargetName);
|
||||
// 获取真实的目标组件名(即使通过 alias 启动,也能拿到 OriginalActivity)
|
||||
// String realTargetName = ActivityAliasUtils.getRealTargetNameFromIntent(this);
|
||||
// LogUtils.d(TAG, "真实组件名:" + realTargetName);
|
||||
// ToastUtils.show(realTargetName);
|
||||
// // 判断某个组件是否为 alias
|
||||
// String componentName = "com.winboll.app.AliasActivity";
|
||||
// boolean isAlias = ActivityAliasUtils.isActivityAlias(getApplicationContext(), componentName);
|
||||
// LogUtils.d("判断结果", componentName + " 是否为 alias:" + isAlias); // true
|
||||
// // 获取启动当前 Activity 的组件名(兼容 alias 场景)
|
||||
// String launchComponent = ActivityAliasUtils.getLaunchComponentName(this);
|
||||
// LogUtils.d("MainActivity", "启动组件名:" + launchComponent);
|
||||
|
||||
/*
|
||||
* 应用入口逻辑模块
|
||||
*/
|
||||
//
|
||||
// 检查当前活动的启动组件名,设置应用入口级别。
|
||||
String launchComponent = ActivityAliasUtils.getLaunchComponentName(this);
|
||||
LogUtils.d("MainActivity", "启动组件名:" + launchComponent);
|
||||
ToastUtils.show(launchComponent);
|
||||
// 当前应用处于活动暂停的状态时,就检查应用的入口组件名称,设置应用入口级别。
|
||||
if (WinBoLLActivity._mPointLevel == PointLevel.DORAEMON) {
|
||||
if (launchComponent.equals(App.COMPONENT_WUKONG)) {
|
||||
getSupportActionBar().setTitle(getString(R.string.appplus_name));
|
||||
ToastUtils.show("WUKONG");
|
||||
_mPointLevel = PointLevel.WUKONG;
|
||||
} else if (launchComponent.equals(App.COMPONENT_LAOJUN)) {
|
||||
getSupportActionBar().setTitle(getString(R.string.app_name));
|
||||
ToastUtils.show("LAOJUN");
|
||||
_mPointLevel = PointLevel.LAOJUN;
|
||||
} else {
|
||||
// 如果是其他应用组件入口,就关闭活动
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 应用级别设置模块
|
||||
*/
|
||||
// 读取并配置应用级别
|
||||
App._mAppLevel = AppConfigsUtil.getInstance(getApplicationContext()).getAppLevel(true);
|
||||
|
||||
LogUtils.d(TAG, String.format("onResume %s", getTag()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
/*if (item.getItemId() == R.id.item_log) {
|
||||
WinBoLLActivityManager.getInstance().startLogActivity(this);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.item_home) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
}*/
|
||||
WinBoLLActivityManager.getInstance().startLogActivity(this);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.item_home) {
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
return true;
|
||||
}*/
|
||||
// 在switch语句中处理每个ID,并在处理完后返回true,未处理的情况返回false。
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,12 +9,14 @@ package cc.winboll.studio.positions.models;
|
||||
import android.util.JsonWriter;
|
||||
import android.util.JsonReader;
|
||||
import java.io.IOException;
|
||||
import cc.winboll.studio.positions.AppLevel;
|
||||
|
||||
public class AppConfigsModel extends BaseBean {
|
||||
|
||||
public static final String TAG = "AppConfigsModel";
|
||||
|
||||
boolean isEnableMainService;
|
||||
AppLevel appLevel;
|
||||
|
||||
public AppConfigsModel(boolean isEnableMainService) {
|
||||
this.isEnableMainService = isEnableMainService;
|
||||
@@ -24,6 +26,14 @@ public class AppConfigsModel extends BaseBean {
|
||||
this.isEnableMainService = false;
|
||||
}
|
||||
|
||||
public void setAppLevel(AppLevel appLevel) {
|
||||
this.appLevel = appLevel;
|
||||
}
|
||||
|
||||
public AppLevel getAppLevel() {
|
||||
return appLevel;
|
||||
}
|
||||
|
||||
public void setIsEnableMainService(boolean isEnableMainService) {
|
||||
this.isEnableMainService = isEnableMainService;
|
||||
}
|
||||
@@ -42,6 +52,7 @@ public class AppConfigsModel extends BaseBean {
|
||||
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
|
||||
super.writeThisToJsonWriter(jsonWriter);
|
||||
jsonWriter.name("isEnableDistanceRefreshService").value(isEnableMainService());
|
||||
jsonWriter.name("appLevel").value(getAppLevel().ordinal());
|
||||
}
|
||||
|
||||
// JSON反序列化(加载位置数据,校验字段)
|
||||
@@ -52,6 +63,8 @@ public class AppConfigsModel extends BaseBean {
|
||||
} else {
|
||||
if (name.equals("isEnableDistanceRefreshService")) {
|
||||
setIsEnableMainService(jsonReader.nextBoolean());
|
||||
} else if (name.equals("appLevel")) {
|
||||
setAppLevel((AppLevel.values()[jsonReader.nextInt()]));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -26,19 +26,22 @@ public class PositionTaskModel extends BaseBean {
|
||||
boolean isLessThan;
|
||||
// 任务条件距离(单位:米)
|
||||
int discussDistance;
|
||||
// 任务开始启用时间
|
||||
long startTime;
|
||||
// 任务是否已触发
|
||||
boolean isBingo = false;
|
||||
// 是否启用任务
|
||||
boolean isEnable;
|
||||
|
||||
// 带参构造(强制传入positionId,确保任务与位置绑定)
|
||||
public PositionTaskModel(String taskId, String positionId, String taskDescription, boolean isGreaterThan, int discussDistance, boolean isEnable) {
|
||||
public PositionTaskModel(String taskId, String positionId, String taskDescription, boolean isGreaterThan, int discussDistance, long startTime, boolean isEnable) {
|
||||
this.taskId = (taskId == null || taskId.trim().isEmpty()) ? genTaskId() : taskId; // 空ID自动生成
|
||||
this.positionId = positionId; // 强制绑定位置ID
|
||||
this.taskDescription = (taskDescription == null || taskDescription.trim().isEmpty()) ? "新任务" : taskDescription;
|
||||
this.isGreaterThan = isGreaterThan;
|
||||
this.isLessThan = !isGreaterThan; // 确保互斥
|
||||
this.discussDistance = Math.max(discussDistance, 1); // 距离最小1米,避免无效值
|
||||
this.startTime = startTime;
|
||||
this.isEnable = isEnable;
|
||||
}
|
||||
|
||||
@@ -50,9 +53,18 @@ public class PositionTaskModel extends BaseBean {
|
||||
this.isGreaterThan = true;
|
||||
this.isLessThan = false; // 初始互斥
|
||||
this.discussDistance = 100; // 默认100米
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.isEnable = true;
|
||||
}
|
||||
|
||||
public void setStartTime(long startTime) {
|
||||
this.startTime = startTime;
|
||||
}
|
||||
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
public void setIsBingo(boolean isBingo) {
|
||||
this.isBingo = isBingo;
|
||||
}
|
||||
@@ -144,6 +156,7 @@ public class PositionTaskModel extends BaseBean {
|
||||
jsonWriter.name("isGreaterThan").value(isGreaterThan());
|
||||
jsonWriter.name("isLessThan").value(isLessThan());
|
||||
jsonWriter.name("discussDistance").value(getDiscussDistance());
|
||||
jsonWriter.name("startTime").value(getStartTime());
|
||||
jsonWriter.name("isEnable").value(isEnable());
|
||||
}
|
||||
|
||||
@@ -165,6 +178,8 @@ public class PositionTaskModel extends BaseBean {
|
||||
setIsLessThan(jsonReader.nextBoolean());
|
||||
} else if (name.equals("discussDistance")) {
|
||||
setDiscussDistance(jsonReader.nextInt());
|
||||
} else if (name.equals("startTime")) {
|
||||
setStartTime(jsonReader.nextLong());
|
||||
} else if (name.equals("isEnable")) {
|
||||
setIsEnable(jsonReader.nextBoolean());
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
package cc.winboll.studio.positions.receivers;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/10/28 19:07
|
||||
* @Describe MotionStatusReceiver
|
||||
*/
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.Sensor;
|
||||
import android.hardware.SensorEvent;
|
||||
import android.hardware.SensorEventListener;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.libappbase.ToastUtils;
|
||||
import cc.winboll.studio.positions.services.MainService;
|
||||
import cc.winboll.studio.positions.utils.ServiceUtil;
|
||||
|
||||
/**
|
||||
* 运动状态监听Receiver
|
||||
* 功能:1.持续监听传感器(不关闭) 2.每5秒计算运动状态 3.按状态切换GPS模式(实时/30秒定时)
|
||||
*/
|
||||
public class MotionStatusReceiver extends BroadcastReceiver implements SensorEventListener {
|
||||
public static final String TAG = "MotionStatusReceiver";
|
||||
|
||||
// 广播Action
|
||||
public static final String ACTION_MOTION_STATUS_RECEIVER = "cc.winboll.studio.positions.receivers.MotionStatusReceiver";
|
||||
public static final String EXTRA_SENSORS_ENABLE = "EXTRA_SENSORS_ENABLE";
|
||||
// 传感器启动状态标志位
|
||||
boolean mIsSensorsEnable = false;
|
||||
// 运动状态常量
|
||||
private static final int MOTION_STATUS_STATIC = 0; // 静止/低运动
|
||||
private static final int MOTION_STATUS_WALKING = 1; // 行走/高速运动
|
||||
// 配置参数(按需求调整)
|
||||
private static final float ACCELEROMETER_THRESHOLD = 0.8f; // 加速度阈值
|
||||
private static final float GYROSCOPE_THRESHOLD = 0.5f; // 陀螺仪阈值
|
||||
private static final long STATUS_CALC_INTERVAL = 5000; // 运动状态计算间隔(5秒)
|
||||
private static final long GPS_STATIC_INTERVAL = 30; // 静止时GPS间隔(30秒)
|
||||
// 核心对象
|
||||
private volatile SensorManager mSensorManager;
|
||||
private Sensor mAccelerometer;
|
||||
private Sensor mGyroscope;
|
||||
private volatile boolean mIsSensorListening = false; // 传感器是否持续监听
|
||||
private int mCurrentMotionStatus = MOTION_STATUS_STATIC; // 当前运动状态
|
||||
private Handler mMainHandler; // 主线程Handler(用于定时计算)
|
||||
private Context mBroadcastContext; // 广播上下文
|
||||
// 传感器数据缓存(用于5秒内数据汇总,避免单次波动误判)
|
||||
private float mAccelMax = 0f; // 5秒内加速度最大值
|
||||
private float mGyroMax = 0f; // 5秒内陀螺仪最大值
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
|
||||
LogUtils.d(TAG, "===== 接收器启动:onReceive() 开始执行 =====");
|
||||
this.mBroadcastContext = context;
|
||||
mMainHandler = new Handler(Looper.getMainLooper());
|
||||
|
||||
if (TextUtils.equals(intent.getAction(), ACTION_MOTION_STATUS_RECEIVER)) {
|
||||
boolean isSettingEnable = intent.getBooleanExtra(EXTRA_SENSORS_ENABLE, false);
|
||||
if (mIsSensorsEnable == false && isSettingEnable == true) {
|
||||
mIsSensorsEnable = true;
|
||||
// 1. 初始化传感器(必执行)
|
||||
initSensors();
|
||||
|
||||
|
||||
if (mAccelerometer == null || mGyroscope == null) {
|
||||
LogUtils.e(TAG, "设备缺少加速度/陀螺仪,无法持续监听");
|
||||
cleanResources(false); // 传感器不可用才清理
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 校验参数
|
||||
if (context == null || intent == null) {
|
||||
LogUtils.d(TAG, "onReceive():无效参数,终止处理");
|
||||
cleanResources(false);
|
||||
return;
|
||||
}
|
||||
LogUtils.d(TAG, "onReceive():接收到广播Action=" + intent.getAction());
|
||||
|
||||
// 3. 启动持续传感器监听(核心:不关闭,重复调用无影响)
|
||||
startSensorListening();
|
||||
|
||||
// 4. 启动5秒定时计算运动状态(核心:持续触发状态判断)
|
||||
startStatusCalcTimer();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 5. 处理外部广播触发(可选,保留外部控制能力)
|
||||
// if (TextUtils.equals(intent.getAction(), ACTION_MOTION_STATUS_RECEIVER)) {
|
||||
// int motionStatus = intent.getIntExtra(EXTRA_MOTION_STATUS, MOTION_STATUS_STATIC);
|
||||
// String statusDesc = motionStatus == MOTION_STATUS_WALKING ? "高速运动" : "静止/低运动";
|
||||
// LogUtils.d(TAG, "外部广播触发,强制设置运动状态:" + statusDesc);
|
||||
// mCurrentMotionStatus = motionStatus;
|
||||
// handleMotionStatus(mCurrentMotionStatus); // 立即执行GPS切换
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化传感器(持续监听,复用实例)
|
||||
*/
|
||||
private void initSensors() {
|
||||
LogUtils.d(TAG, "initSensors():初始化传感器");
|
||||
if (mSensorManager != null || mBroadcastContext == null) return;
|
||||
|
||||
mSensorManager = (SensorManager) mBroadcastContext.getSystemService(Context.SENSOR_SERVICE);
|
||||
if (mSensorManager == null) {
|
||||
LogUtils.e(TAG, "设备不支持传感器服务");
|
||||
return;
|
||||
}
|
||||
// 获取传感器实例(持续复用,不销毁)
|
||||
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
|
||||
mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
|
||||
|
||||
LogUtils.d(TAG, "传感器初始化结果:加速度=" + (mAccelerometer != null) + ",陀螺仪=" + (mGyroscope != null));
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动传感器持续监听(核心:不关闭,注册一次一直生效)
|
||||
*/
|
||||
private void startSensorListening() {
|
||||
if (mSensorManager == null || mAccelerometer == null || mGyroscope == null) return;
|
||||
|
||||
if (!mIsSensorListening) {
|
||||
// 注册传感器监听(持续生效,直到服务销毁才注销)
|
||||
mSensorManager.registerListener(
|
||||
this,
|
||||
mAccelerometer,
|
||||
SensorManager.SENSOR_DELAY_NORMAL, // 正常延迟,平衡性能与精度
|
||||
mMainHandler
|
||||
);
|
||||
mSensorManager.registerListener(
|
||||
this,
|
||||
mGyroscope,
|
||||
SensorManager.SENSOR_DELAY_NORMAL,
|
||||
mMainHandler
|
||||
);
|
||||
mIsSensorListening = true;
|
||||
LogUtils.d(TAG, "startSensorListening():传感器持续监听已启动(不关闭)");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动5秒定时计算运动状态(核心:周期性汇总传感器数据)
|
||||
*/
|
||||
private void startStatusCalcTimer() {
|
||||
if (mMainHandler == null) return;
|
||||
|
||||
// 移除旧任务(避免重复注册)
|
||||
mMainHandler.removeCallbacks(mStatusCalcRunnable);
|
||||
// 启动定时任务(每5秒执行一次)
|
||||
mMainHandler.postDelayed(mStatusCalcRunnable, STATUS_CALC_INTERVAL);
|
||||
LogUtils.d(TAG, "startStatusCalcTimer():5秒运动状态计算定时器已启动");
|
||||
}
|
||||
|
||||
/**
|
||||
* 运动状态计算任务(5秒执行一次)
|
||||
*/
|
||||
private final Runnable mStatusCalcRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// 1. 基于5秒内缓存的最大传感器数据判断状态
|
||||
boolean isHighMotion = (mAccelMax > ACCELEROMETER_THRESHOLD) && (mGyroMax > GYROSCOPE_THRESHOLD);
|
||||
int newMotionStatus = isHighMotion ? MOTION_STATUS_WALKING : MOTION_STATUS_STATIC;
|
||||
|
||||
// 2. 状态变化时才处理(避免频繁切换GPS)
|
||||
if (newMotionStatus != mCurrentMotionStatus) {
|
||||
mCurrentMotionStatus = newMotionStatus;
|
||||
String statusDesc = isHighMotion ? "高速运动" : "静止/低运动";
|
||||
LogUtils.d(TAG, "运动状态更新(5秒计算):" + statusDesc
|
||||
+ "(加速度最大值=" + mAccelMax + ",陀螺仪最大值=" + mGyroMax + ")");
|
||||
handleMotionStatus(newMotionStatus); // 切换GPS模式
|
||||
} else {
|
||||
LogUtils.d(TAG, "运动状态无变化(5秒计算):" + (isHighMotion ? "高速运动" : "静止/低运动"));
|
||||
}
|
||||
|
||||
// 3. 重置传感器数据缓存,准备下一个5秒周期
|
||||
mAccelMax = 0f;
|
||||
mGyroMax = 0f;
|
||||
|
||||
// 4. 循环执行定时任务(核心:持续计算)
|
||||
mMainHandler.postDelayed(this, STATUS_CALC_INTERVAL);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 传感器数据变化回调(核心:实时缓存最大数据)
|
||||
*/
|
||||
@Override
|
||||
public void onSensorChanged(SensorEvent event) {
|
||||
if (event == null) return;
|
||||
|
||||
// 实时缓存5秒内的最大传感器数据(避免单次波动误判)
|
||||
switch (event.sensor.getType()) {
|
||||
case Sensor.TYPE_ACCELEROMETER:
|
||||
float accelTotal = Math.abs(event.values[0]) + Math.abs(event.values[1]) + Math.abs(event.values[2]);
|
||||
if (accelTotal > mAccelMax) mAccelMax = accelTotal; // 缓存最大值
|
||||
LogUtils.d(TAG, "加速度传感器实时数据:合值=" + accelTotal + "(当前5秒最大值=" + mAccelMax + ")");
|
||||
break;
|
||||
case Sensor.TYPE_GYROSCOPE:
|
||||
float gyroTotal = Math.abs(event.values[0]) + Math.abs(event.values[1]) + Math.abs(event.values[2]);
|
||||
if (gyroTotal > mGyroMax) mGyroMax = gyroTotal; // 缓存最大值
|
||||
LogUtils.d(TAG, "陀螺仪实时数据:合值=" + gyroTotal + "(当前5秒最大值=" + mGyroMax + ")");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运动状态(核心:按状态切换GPS模式)
|
||||
*/
|
||||
private void handleMotionStatus(int motionStatus) {
|
||||
LogUtils.d(TAG, "handleMotionStatus():开始处理运动状态,切换GPS模式");
|
||||
if (mBroadcastContext == null) {
|
||||
LogUtils.w(TAG, "上下文为空,无法处理GPS");
|
||||
return;
|
||||
}
|
||||
|
||||
MainService mainService = getMainService();
|
||||
if (mainService == null) {
|
||||
LogUtils.e(TAG, "MainService未启动,GPS控制失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (motionStatus == MOTION_STATUS_WALKING) {
|
||||
// 高速运动:启动GPS实时更新(2秒/1米)
|
||||
handleHighMotionGPS(mainService);
|
||||
} else {
|
||||
// 静止/低运动:启动GPS30秒定时更新
|
||||
handleStaticGPS(mainService);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 高速运动GPS处理:实时更新
|
||||
*/
|
||||
private void handleHighMotionGPS(MainService mainService) {
|
||||
// 动态权限判断(Android 6.0+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||
mBroadcastContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
sendPermissionRequestBroadcast();
|
||||
return;
|
||||
}
|
||||
|
||||
// 启动实时GPS(已启动则不重复操作)
|
||||
if (!mainService.isGpsListening()) {
|
||||
mainService.startGpsLocation(); // 实时更新(2秒/1米)
|
||||
mainService.stopGpsStaticTimer(); // 停止定时GPS
|
||||
LogUtils.d(TAG, "高速运动:已启动GPS实时更新");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 静止/低运动GPS处理:30秒定时更新
|
||||
*/
|
||||
private void handleStaticGPS(MainService mainService) {
|
||||
// 停止实时GPS(已停止则不重复操作)
|
||||
if (mainService.isGpsListening()) {
|
||||
mainService.stopGpsLocation(); // 停止实时更新
|
||||
LogUtils.d(TAG, "静止/低运动:已停止GPS实时更新");
|
||||
}
|
||||
|
||||
// 启动30秒定时GPS(已启动则不重复操作)
|
||||
mainService.startGpsStaticTimer(GPS_STATIC_INTERVAL); // 30秒一次
|
||||
LogUtils.d(TAG, "静止/低运动:已启动GPS30秒定时更新");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取MainService实例(复用逻辑)
|
||||
*/
|
||||
private MainService getMainService() {
|
||||
if (mBroadcastContext == null) return null;
|
||||
|
||||
// 优先获取单例
|
||||
MainService singleton = MainService.getInstance(mBroadcastContext);
|
||||
if (singleton != null && singleton.isServiceRunning()) {
|
||||
return singleton;
|
||||
}
|
||||
|
||||
// 启动服务并重试
|
||||
if (!ServiceUtil.isServiceAlive(mBroadcastContext, MainService.class.getName())) {
|
||||
mBroadcastContext.startService(new Intent(mBroadcastContext, MainService.class));
|
||||
try {
|
||||
Thread.sleep(500); // 等待服务启动
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
return MainService.getInstance(mBroadcastContext);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送GPS权限申请广播(Receiver无法直接申请)
|
||||
*/
|
||||
private void sendPermissionRequestBroadcast() {
|
||||
Intent permissionIntent = new Intent("cc.winboll.studio.positions.ACTION_REQUEST_GPS_PERMISSION");
|
||||
permissionIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
|
||||
mBroadcastContext.sendBroadcast(permissionIntent);
|
||||
LogUtils.d(TAG, "GPS权限缺失,已发送申请广播");
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源清理(核心:传感器不关闭,仅清理Handler和上下文)
|
||||
* @param isForceStopSensor 是否强制停止传感器(仅服务销毁时传true)
|
||||
*/
|
||||
private void cleanResources(boolean isForceStopSensor) {
|
||||
// 1. 停止定时计算任务
|
||||
if (mMainHandler != null) {
|
||||
mMainHandler.removeCallbacksAndMessages(null);
|
||||
mMainHandler = null;
|
||||
LogUtils.d(TAG, "cleanResources():已停止运动状态计算定时器");
|
||||
}
|
||||
|
||||
// 2. 强制停止传感器(仅当外部触发销毁时执行,正常情况不关闭)
|
||||
if (isForceStopSensor && mSensorManager != null && mIsSensorListening) {
|
||||
mSensorManager.unregisterListener(this);
|
||||
mIsSensorListening = false;
|
||||
LogUtils.d(TAG, "cleanResources():已强制停止传感器监听");
|
||||
}
|
||||
|
||||
// 3. 置空上下文(避免内存泄漏)
|
||||
mBroadcastContext = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 传感器精度变化回调(日志监控)
|
||||
*/
|
||||
@Override
|
||||
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||
String sensorType = sensor.getType() == Sensor.TYPE_ACCELEROMETER ? "加速度" : "陀螺仪";
|
||||
String accuracyDesc = getAccuracyDesc(accuracy);
|
||||
LogUtils.d(TAG, sensorType + "传感器精度变化:" + accuracyDesc);
|
||||
}
|
||||
|
||||
/**
|
||||
* 传感器精度描述转换
|
||||
*/
|
||||
private String getAccuracyDesc(int accuracy) {
|
||||
switch (accuracy) {
|
||||
case SensorManager.SENSOR_STATUS_ACCURACY_HIGH: return "高";
|
||||
case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM: return "中";
|
||||
case SensorManager.SENSOR_STATUS_ACCURACY_LOW: return "低";
|
||||
case SensorManager.SENSOR_STATUS_UNRELIABLE: return "不可靠";
|
||||
default: return "未知";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 补充:Receiver销毁时强制清理(需在MainService注销时调用)
|
||||
*/
|
||||
public void forceCleanResources() {
|
||||
cleanResources(true); // 强制停止传感器
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import cc.winboll.studio.positions.utils.ServiceUtil;
|
||||
public class AssistantService extends Service {
|
||||
|
||||
public final static String TAG = "AssistantService";
|
||||
|
||||
public static final String EXTRA_IS_SETTING_TO_ENABLE = "EXTRA_IS_SETTING_TO_ENABLE";
|
||||
|
||||
MyServiceConnection mMyServiceConnection;
|
||||
volatile boolean mIsServiceRunning;
|
||||
AppConfigsUtil mAppConfigsUtil;
|
||||
@@ -37,13 +38,18 @@ public class AssistantService extends Service {
|
||||
}
|
||||
// 设置运行参数
|
||||
mIsServiceRunning = false;
|
||||
run();
|
||||
if (mAppConfigsUtil.isEnableMainService(true)) {
|
||||
run();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
run();
|
||||
return START_STICKY;
|
||||
if (mAppConfigsUtil.isEnableMainService(true)) {
|
||||
run();
|
||||
}
|
||||
|
||||
return mAppConfigsUtil.isEnableMainService(true) ? Service.START_STICKY : super.onStartCommand(intent, flags, startId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,194 @@
|
||||
package cc.winboll.studio.positions.utils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/10 09:51
|
||||
* @Describe 应用图标切换工具类(启用组件时创建对应快捷方式)
|
||||
*/
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.widget.Toast;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
import cc.winboll.studio.positions.App;
|
||||
import cc.winboll.studio.positions.MainActivity;
|
||||
|
||||
public class APPPlusUtils {
|
||||
public static final String TAG = "APPPlusUtils";
|
||||
|
||||
// 快捷方式配置(名称+图标,需与实际资源匹配)
|
||||
// private static final String PLUS_SHORTCUT_NAME = "位置服务-Laojun";
|
||||
// private static final int PLUS_SHORTCUT_ICON = R.mipmap.ic_launcher; // Laojun 图标资源
|
||||
|
||||
/**
|
||||
* 添加Plus组件与图标
|
||||
*/
|
||||
public static boolean openAPPPlus(Context context) {
|
||||
if (context == null) {
|
||||
LogUtils.d(TAG, "切换失败:上下文为空");
|
||||
Toast.makeText(context, "图标切换失败", Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ComponentName plusComponentLaojun = new ComponentName(context, App.COMPONENT_LAOJUN);
|
||||
//ComponentName plusComponentWuKong = new ComponentName(context, MainActivity.COMPONENT_WUKONG);
|
||||
|
||||
try {
|
||||
//disableComponent(pm, plusComponentWuKong);
|
||||
enableComponent(pm, plusComponentLaojun);
|
||||
|
||||
// 2. 创建 Laojun 组件对应的快捷方式(自动去重)
|
||||
// boolean shortcutCreated = createComponentShortcut(context, plusComponent, PLUS_SHORTCUT_NAME, PLUS_SHORTCUT_ICON);
|
||||
//
|
||||
// // 3. 通知桌面刷新图标
|
||||
// context.sendBroadcast(new Intent(Intent.ACTION_PACKAGE_CHANGED)
|
||||
// .setData(android.net.Uri.parse("package:" + context.getPackageName())));
|
||||
//
|
||||
// // 4. 反馈结果
|
||||
// String logMsg = shortcutCreated ? "启用 Laojun + 快捷方式创建成功" : "启用 Laojun 成功,快捷方式创建失败";
|
||||
// String toastMsg = shortcutCreated ? "图标切换为 Laojun,已创建快捷方式" : "图标切换为 Laojun,快捷方式创建失败";
|
||||
// LogUtils.d(TAG, logMsg);
|
||||
// Toast.makeText(context, toastMsg, Toast.LENGTH_SHORT).show();
|
||||
//
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "Laojun 图标切换失败:" + e.getMessage());
|
||||
// 失败兜底:启用 Wukong 组件
|
||||
//enableComponent(pm, wukongComponent);
|
||||
Toast.makeText(context, "图标切换失败" + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 移除Plus组件
|
||||
*/
|
||||
public static boolean closeAPPPlus(Context context) {
|
||||
if (context == null) {
|
||||
LogUtils.d(TAG, "切换失败:上下文为空");
|
||||
Toast.makeText(context, "图标切换失败", Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ComponentName plusComponentLaojun = new ComponentName(context, App.COMPONENT_LAOJUN);
|
||||
//ComponentName plusComponentWuKong = new ComponentName(context, MainActivity.COMPONENT_WUKONG);
|
||||
|
||||
disableComponent(pm, plusComponentLaojun);
|
||||
//enableComponent(pm, plusComponentWuKong);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指定组件的桌面快捷方式(自动去重,兼容 Android 8.0+)
|
||||
* @param component 目标组件(如 LAOJUN_ACTIVITY)
|
||||
* @param name 快捷方式名称
|
||||
* @param iconRes 快捷方式图标资源ID
|
||||
* @return 是否创建成功
|
||||
*/
|
||||
private static boolean createComponentShortcut(Context context, ComponentName component, String name, int iconRes) {
|
||||
if (context == null || component == null || name == null || iconRes == 0) {
|
||||
LogUtils.d(TAG, "快捷方式创建失败:参数为空");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Android 8.0+(API 26+):使用 ShortcutManager(系统推荐)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
android.content.pm.ShortcutManager shortcutManager = context.getSystemService(android.content.pm.ShortcutManager.class);
|
||||
if (shortcutManager == null || !shortcutManager.isRequestPinShortcutSupported()) {
|
||||
LogUtils.d(TAG, "系统不支持创建快捷方式");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已存在该组件的快捷方式(去重)
|
||||
for (android.content.pm.ShortcutInfo info : shortcutManager.getPinnedShortcuts()) {
|
||||
if (component.getClassName().equals(info.getIntent().getComponent().getClassName())) {
|
||||
LogUtils.d(TAG, "快捷方式已存在:" + component.getClassName());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 构建启动目标组件的意图
|
||||
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.setComponent(component)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
// 构建快捷方式信息
|
||||
android.content.pm.ShortcutInfo shortcutInfo = new android.content.pm.ShortcutInfo.Builder(context, component.getClassName())
|
||||
.setShortLabel(name)
|
||||
.setLongLabel(name)
|
||||
.setIcon(android.graphics.drawable.Icon.createWithResource(context, iconRes))
|
||||
.setIntent(launchIntent)
|
||||
.build();
|
||||
|
||||
// 请求创建快捷方式(需用户确认)
|
||||
shortcutManager.requestPinShortcut(shortcutInfo, null);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "Android O+ 快捷方式创建失败:" + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Android 8.0 以下:使用广播(兼容旧机型)
|
||||
try {
|
||||
// 构建启动目标组件的意图
|
||||
Intent launchIntent = new Intent(Intent.ACTION_MAIN)
|
||||
.setComponent(component)
|
||||
.addCategory(Intent.CATEGORY_LAUNCHER)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
// 构建创建快捷方式的广播意图
|
||||
Intent installIntent = new Intent("com.android.launcher.action.INSTALL_SHORTCUT");
|
||||
installIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, launchIntent);
|
||||
installIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name);
|
||||
installIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
|
||||
Intent.ShortcutIconResource.fromContext(context, iconRes));
|
||||
installIntent.putExtra("duplicate", false); // 禁止重复创建
|
||||
|
||||
context.sendBroadcast(installIntent);
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.d(TAG, "Android O- 快捷方式创建失败:" + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用组件(带状态检查,避免重复操作)
|
||||
*/
|
||||
private static void enableComponent(PackageManager pm, ComponentName component) {
|
||||
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
|
||||
pm.setComponentEnabledSetting(
|
||||
component,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
|
||||
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用组件(带状态检查,避免重复操作)
|
||||
*/
|
||||
private static void disableComponent(PackageManager pm, ComponentName component) {
|
||||
if (pm.getComponentEnabledSetting(component) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
|
||||
pm.setComponentEnabledSetting(
|
||||
component,
|
||||
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
|
||||
PackageManager.DONT_KILL_APP | PackageManager.SYNCHRONOUS
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package cc.winboll.studio.positions.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.text.TextUtils;
|
||||
import cc.winboll.studio.libappbase.LogUtils;
|
||||
|
||||
/**
|
||||
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
|
||||
* @Date 2025/11/15 15:23
|
||||
* @Describe Activity Alias 工具类(兼容 Android 所有版本,Java 7 语法)
|
||||
* 用于获取 activity-alias 对应的原始 Activity 组件名、判断 alias 类型、获取启动组件名等
|
||||
*/
|
||||
public class ActivityAliasUtils {
|
||||
private static final String TAG = "ActivityAliasUtils";
|
||||
|
||||
/**
|
||||
* 获取 activity-alias 指向的原始 Activity 组件名
|
||||
*
|
||||
* @param context 上下文(建议用 ApplicationContext)
|
||||
* @param aliasName activity-alias 的组件名(完整路径,如 ".AliasActivity" 或 "com.winboll.app.AliasActivity")
|
||||
* @return 原始 Activity 的完整组件名(如 "com.winboll.app.OriginalActivity"),失败返回 null
|
||||
*/
|
||||
public static String getTargetActivityName(Context context, String aliasName) {
|
||||
// 校验参数
|
||||
if (context == null || TextUtils.isEmpty(aliasName)) {
|
||||
LogUtils.e(TAG, "getTargetActivityName: context is null or aliasName is empty");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 补全组件名(若传入的是短名,自动拼接包名)
|
||||
String fullAliasName = aliasName.startsWith(".")
|
||||
? context.getPackageName() + aliasName
|
||||
: aliasName;
|
||||
|
||||
try {
|
||||
// 1. 获取 PackageManager
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
|
||||
// 2. 解析 activity-alias 的 ActivityInfo(flag 必须设为 PackageManager.GET_META_DATA,否则可能获取不到 targetActivity)
|
||||
ActivityInfo aliasActivityInfo = packageManager.getActivityInfo(
|
||||
new android.content.ComponentName(context.getPackageName(), fullAliasName),
|
||||
PackageManager.GET_META_DATA
|
||||
);
|
||||
|
||||
// 3. 获取 targetActivity(原始 Activity 组件名)
|
||||
String targetActivity = aliasActivityInfo.targetActivity;
|
||||
if (TextUtils.isEmpty(targetActivity)) {
|
||||
LogUtils.e(TAG, "getTargetActivityName: targetActivity is empty for alias " + fullAliasName);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 补全原始 Activity 的完整包名(若 targetActivity 是短名)
|
||||
String fullTargetName = targetActivity.startsWith(".")
|
||||
? context.getPackageName() + targetActivity
|
||||
: targetActivity;
|
||||
|
||||
LogUtils.d(TAG, "getTargetActivityName: alias=" + fullAliasName + ", target=" + fullTargetName);
|
||||
return fullTargetName;
|
||||
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
LogUtils.e(TAG, "getTargetActivityName: alias not found - " + fullAliasName, e);
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "getTargetActivityName: unknown error", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断某个组件名是否为 activity-alias(而非原始 Activity)
|
||||
*
|
||||
* @param context 上下文
|
||||
* @param componentName 待判断的组件名(完整路径)
|
||||
* @return true:是 activity-alias;false:不是或判断失败
|
||||
*/
|
||||
public static boolean isActivityAlias(Context context, String componentName) {
|
||||
// 调用 getTargetActivityName,若返回非空,则说明是 alias
|
||||
return !TextUtils.isEmpty(getTargetActivityName(context, componentName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从启动的 Intent 中获取实际的目标组件名(处理 alias 场景)
|
||||
* 适用于 Activity 中获取自身真实组件名(原始 Activity 名)
|
||||
*
|
||||
* @param context 当前 Activity 上下文
|
||||
* @return 真实的目标组件名(原始 Activity 名,若为 alias 启动则返回原始 Activity,否则返回自身)
|
||||
*/
|
||||
public static String getRealTargetNameFromIntent(Context context) {
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "getRealTargetNameFromIntent: context is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取当前 Activity 的组件名(可能是 alias)
|
||||
String currentComponentName = context.getClass().getName();
|
||||
// 检查是否为 alias,若是则返回 target,否则返回自身
|
||||
String targetName = getTargetActivityName(context, currentComponentName);
|
||||
return TextUtils.isEmpty(targetName) ? currentComponentName : targetName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前活动上下文(Activity)的启动组件名(即启动时使用的组件名,可能是 alias 或原始 Activity)
|
||||
* 场景:若通过 alias 启动 Activity,返回 alias 名;若直接启动原始 Activity,返回原始 Activity 名
|
||||
*
|
||||
* @param context 当前 Activity 上下文(必须是 Activity 实例,不能是 ApplicationContext)
|
||||
* @return 启动组件的完整名,失败返回 null
|
||||
*/
|
||||
public static String getLaunchComponentName(Context context) {
|
||||
// 1. 校验上下文类型(必须是 Activity,否则无法获取启动 Intent)
|
||||
if (context == null) {
|
||||
LogUtils.e(TAG, "getLaunchComponentName: context is null");
|
||||
return null;
|
||||
}
|
||||
if (!(context instanceof android.app.Activity)) {
|
||||
LogUtils.e(TAG, "getLaunchComponentName: context must be Activity instance, current is " + context.getClass().getName());
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 获取启动当前 Activity 的 Intent
|
||||
android.app.Activity activity = (android.app.Activity) context;
|
||||
Intent launchIntent = activity.getIntent();
|
||||
if (launchIntent == null) {
|
||||
LogUtils.e(TAG, "getLaunchComponentName: launch Intent is null");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 从 Intent 中获取启动组件名(ComponentName)
|
||||
android.content.ComponentName componentName = launchIntent.getComponent();
|
||||
if (componentName == null) {
|
||||
LogUtils.e(TAG, "getLaunchComponentName: ComponentName is null in launch Intent");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 获取组件的完整类名(即启动时使用的组件名)
|
||||
String launchComponentName = componentName.getClassName();
|
||||
LogUtils.d(TAG, "getLaunchComponentName: current launch component is " + launchComponentName);
|
||||
return launchComponentName;
|
||||
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "getLaunchComponentName: failed to get launch component name", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user