1、需求背景
在一些企业级 App(如CRM、金融、内部办公系统)中,"防截屏/防泄露"的需求非常常见,其中全局安全水印是成本最低、落地最稳定的一种方案。

2、整体设计思想
整个架构不依赖任何业务代码接入,而是基于:
Application → ActivityLifecycleCallbacks → DecorView 注入,其本质是利用系统生命周期事件作为"触发器",实现 UI 能力的自动注入与回收
3、核心价值
✔ 无侵入,不污染业务代码
✔ 全局覆盖(DecorView 注入)
✔ 可动态控制开关(enable/disable)
✔ 高性能Canvas绘制,极低性能开销
✔ 防重复注入(tag 控制)
✔ 动态配置水印样式
4、SDK使用
4.1 导包
源码(有需求可自行拓展):https://github.com/ericbyliang/WatermarkDemo
Kotlin
implementation("io.github.ericbyliang:watermark:1.0.0")
4.2 初始化
Kotlin
WatermarkSDK.init(application)
4.3 启用水印(通常在登录成功后,比如首页)
Kotlin
WatermarkSDK.enable(activity, WatermarkConfig(textProvider = { "林俊杰 20000002" }))
4.4 关闭水印(如登录页 / 退出登录)
Kotlin
WatermarkSDK.disable(activity)
**✨**使用就是这么简单,无代码侵入
5、源码解析
5.1 SDK入口(WatermarkSDK)
设计要点
- init:只注册生命周期监听
- enable:绑定配置 + 开启注入
- disable:移除 View + 关闭状态
Kotlin
import android.app.Activity
import android.app.Application
import android.text.TextUtils
/**
* 水印SDK入口
* - 初始化水印能力
* - 注册全局Activity监听
*/
object WatermarkSDK {
/**
* 当前水印配置
*/
lateinit var config: WatermarkConfig
private set
/**
* 当前activity生命周期
*/
lateinit var lifecycle: WatermarkLifecycle
private set
/**
* 初始化水印功能
*
* @param application Application
*/
fun init(
application: Application
) {
this.lifecycle = WatermarkLifecycle()
application.registerActivityLifecycleCallbacks(lifecycle)
}
/**
* ✔ 启用水印
*/
fun enable(act: Activity, config: WatermarkConfig) {
this.config = config
if (!TextUtils.isEmpty(this.config.textProvider().trim())) {
this.lifecycle.setEnabled(true)
WatermarkInjector.attach(act)
}
}
/**
* ✔ 关闭水印
*/
fun disable(act: Activity) {
WatermarkInjector.detach(act)
this.lifecycle.setEnabled(false)
}
}
5.2 生命周期控制(WatermarkLifecycle)
作用
- 控制水印生命周期
- 自动跟随 Activity 创建/销毁
- 避免手动管理
Kotlin
import android.app.Activity
import android.app.Application
import android.os.Bundle
/**
* Activity生命周期监听
*
* 用于在页面创建时自动添加水印
*/
class WatermarkLifecycle : Application.ActivityLifecycleCallbacks {
private var enable = false
fun setEnabled(flag: Boolean) {
enable = flag
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (!enable) return
WatermarkInjector.attach(activity)
}
override fun onActivityDestroyed(activity: Activity) {
if (!enable) return
WatermarkInjector.detach(activity)
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle{}
}
5.3 注入层(WatermarkInjector)
设计关键
- DecorView 注入(全局覆盖)
- tag 防重复
- 不拦截触摸事件
Kotlin
import android.app.Activity
import android.view.View
import android.view.ViewGroup
/**
* 水印视图注入器
*
* 负责:
* - 添加水印View
* - 避免重复添加
* - 移除水印View
*/
object WatermarkInjector {
private const val TAG = "wm_sdk"
fun attach(activity: Activity) {
val root = activity.window.decorView as ViewGroup
if (root.findViewWithTag<View>(TAG) != null) return
val view = WatermarkView(activity).apply {
tag = TAG
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
isClickable = false
isFocusable = false
}
root.addView(view)
}
fun detach(activity: Activity) {
val root = activity.window.decorView as ViewGroup
root.findViewWithTag<View>(TAG)?.let {
root.removeView(it)
}
}
}
5.4 配置模型(WatermarkConfig)
设计思想
- textProvider:支持动态用户信息
- gap:控制密度而不是依赖字体
- rotation:提升安全性(防规整识别)
Kotlin
import android.graphics.Color
/**
* 水印配置参数
*
* 用于控制水印的内容和样式
*/
data class WatermarkConfig(
/**
* 水印文字内容(支持动态生成)
*/
val textProvider: () -> String,
/**
* 字体大小(单位:sp)
*/
val textSizeSp: Float = 14f,
/**
* 透明度(0~1)
*/
val alpha: Float = 0.05f,
/**
* 旋转角度(单位:度)
*/
val rotation: Float = -15f,
/**
* 文字颜色
*/
val color: Int = Color.BLACK,
/**
* 水平间距(dp)
*/
val horizontalGapDp: Float = 80f,
/**
* 垂直间距(dp)
*/
val verticalGapDp: Float = 80f
)
5.5 核心绘制(WatermarkView)
❌ 避免
- Bitmap 生成(避免内存抖动)
- Shader tile(避免 GPU 复杂度,
BitmapShader+TileMode.REPEAT) - 多层 View(避免 layout cost)
✔ 采用
- 单 View Canvas 绘制
- 运行时网格计算,网格间距完全可控(动态参数)
- 不依赖 Bitmap,避免内存抖动
- 不依赖 Shader,降低 GPU 复杂度
- 支持奇偶错位,提升安全性
Kotlin
import android.content.Context
import android.graphics.*
import android.util.TypedValue
import android.view.View
/**
* 水印View(Canvas网格实现)
* ✔ Canvas直接绘制
* ✔ 完全可控密度
* ✔ 奇偶错位
*/
class WatermarkView(context: Context) : View(context) {
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val config = WatermarkSDK.config
init {
paint.color = config.color
paint.alpha = (config.alpha * 255).toInt()
paint.textSize = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_SP,
config.textSizeSp,
resources.displayMetrics
)
paint.isDither = true
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val text = config.textProvider()
val rect = Rect()
paint.getTextBounds(text, 0, text.length, rect)
val textW = rect.width()
val textH = rect.height()
val gapX = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
config.horizontalGapDp,
resources.displayMetrics
)
val gapY = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
config.verticalGapDp,
resources.displayMetrics
)
val stepX = textW + gapX
val stepY = textH + gapY
canvas.save()
/**
* ✔ 旋转整个坐标系(防规整)
*/
canvas.rotate(config.rotation, width / 2f, height / 2f)
/**
* ✔ 从左上角开始铺满整个屏幕(含扩展区域防空白)
*/
var y = -height.toFloat()
var row = 0
while (y < height * 2) {
var x = -width.toFloat()
while (x < width * 2) {
/**
* ✔ 奇偶错位(增强防规律)
*/
val offsetX = if (row % 2 == 0) 0f else stepX / 2f
canvas.drawText(
text,
x + offsetX,
y,
paint
)
x += stepX
}
y += stepY
row++
}
canvas.restore()
}
}