Android 全局安全水印最佳实践(无侵入 + 高性能)

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()
    }
}
相关推荐
sdszoe49223 小时前
华为设备安全管理之路由器+ACL
网络·安全·华为·路由器+acl
AI自动化工坊3 小时前
Claude Mythos技术解析:AI自主发现零日漏洞的安全实践
人工智能·安全·ai agent
0pen13 小时前
ZygiskNext 源码解析(三):zygiskd 的模块管理、memfd 与 companion
android·安全·开源
byoass3 小时前
企业云盘全文检索实战:Elasticsearch集成与分布式搜索
网络·分布式·安全·elasticsearch·云计算·全文检索
不灭锦鲤3 小时前
网络安全学习第98天
学习·安全
星幻元宇VR3 小时前
VR自行车骑行模拟系统|让交通安全教育“骑”进现实
科技·学习·安全·vr
志栋智能14 小时前
超自动化安全:构建智能安全运营的核心引擎
大数据·运维·服务器·数据库·安全·自动化·产品运营
星幻元宇VR14 小时前
VR航空航天科普设备【VR时空直升机】
科技·学习·安全·生活·vr
weixin_5142531816 小时前
428-uitars tmux
安全·web安全