前言
项目开发过程中,总会遇到一些问题,
- 需要手动改动一个开关的值,重新编译运行
- 需要手动改动接口地址,重新编译运行
- 自己想看一些数据,但它们不应该在正常功能中
…
以上,都可以增加一个调试面板来解决。
在测试环境中,增加一个用于调试的 Activity,内部可以有各种配置、展示等等,开发人员,想咋弄咋弄…
基于以上想法,打开调试面板的入口,就可以,
- 类似打开『开发者模式』,在某个地方,连续点击n次,触发
- 弄个悬浮窗,触发
…
本文中,采用了悬浮窗的实现(不是那种全局唯一的,只是在每个activity可见后,动态添加一个可拖动的view)。关于悬浮窗的具体实现,本文中不作讨论
在每个Activity中都创建一个悬浮窗
如,有一个名为 GodFloatView
带有上帝视角的悬浮窗;有一个名为 DashboardActivity
调试面板的Activity。
基于 Application#registerActivityLifecycleCallbacks()
实现如下,
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private val floatViewMap = arrayMapOf<Int, GodFloatView>()
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
debugMode(activity)
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
override fun onActivityDestroyed(activity: Activity) {
floatViewMap.remove(activity.javaClass.hashCode())
}
// 开发调试模式
private fun debugMode(activity: Activity) {
if (!BuildConfig.DEBUG) return
if (activity.javaClass.getAnnotation(DebugMode::class.java) != null) return
if (floatViewMap[activity.javaClass.hashCode()] == null) {
val floatView = GodFloatView(activity)
floatViewMap[activity.javaClass.hashCode()] = floatView
floatView.addToWindow(activity.window)
floatView.setClick {
activity.startActivity(Intent(activity, DashboardActivity::class.java))
}
}
}
})
基于 Map<Int, GodFloatView> 管理所有 activity中的 GodFloatView。key 是activity的hashCode值,以保证唯一性。当 map[key]为空时,则创建 悬浮窗,并存到map中
基于运行时注解,以避免在调试相关的Activity中,添加悬浮窗
看上面的代码,
private fun debugMode(activity: Activity) {
...
if (activity.javaClass.getAnnotation(DebugMode::class.java) != null) return
...
}
代码的意义就是,如果一个类 应用了 @DebugMode 注解,则 return
注解定义:
/**
* desc:
* author: stone
* email: aa86799@163.com
* time: 2023/3/11 13:30
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class DebugMode
将该注解,应用于调试相关的所有Activity中,就可以避免添加悬浮窗,防止重复打开调试面板
后记
Gradle配置隔离(2023-05-11 更新)
首先,在 module > src 下创建 debug 目录;在debug 目录下创建 java 和 res 目录。
再将测试源码移动到 debug > java;对应资源文件 移动到 debug > res。
所在 module > build.gradle
, 增加如下配置
android {
sourceSets {
main {
java.srcDirs = ["src/main/java"]
res.srcDirs = ["src/main/res"]
manifest.srcFile 'src/main/AndroidManifest.xml'
}
debug {
java.srcDirs = ["src/debug/java"]
res.srcDirs = ["src/debug/res"]
manifest.srcFile 'src/debug/AndroidManifest.xml'
}
}
}
以上配置后,在打 debug 包时, debug 和 main中的配置会合并。打 release 包时仅依赖于 main
遇到的问题
在打 release 包时会报错,因为 DebugMode 、DashboardActivity 找不到,它们现在都在 src/debug/java 下了。
解决方法:
- 获取 DebugMode 的Class 实例: Class.forName(a.b.DebugMode);
- 打开 DashboardActivity ,通过 Intent#setClassName 或 setComponent 来设置,
Intent()
.setClassName(packageName, "a.b.DashboardActivity")
// .setComponent(ComponentName(packageName, "a.b.DashboardActivity"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.apply {
startActivity(this)
}