1. 项目概述:为什么要在最近任务列表中隐藏视图?
最近在做一个Android应用,产品经理提了个需求,说希望应用在后台时,最近任务列表(也叫多任务视图、概览屏幕)里显示的界面是“干净”的,要么是应用的启动页,要么干脆是一张纯色背景图,总之不能把当前用户正在操作的、可能包含敏感信息的界面(比如支付页面、聊天窗口)给暴露出来。
这个需求听起来简单,不就是不让系统截图嘛。但真正动手做,才发现里面门道不少。Android系统从早期版本到现在,对这个功能的支持方式和实现细节一直在变,网上能找到的资料要么是过时的,要么就是只言片语,踩了几个坑才把稳定可靠的方案跑通。今天就把我趟过的路、踩过的坑,以及不同场景下的最佳实践,系统地梳理一遍。无论你是刚接触Android开发的新手,还是遇到过类似需求的老手,这篇文章都能给你一个清晰的实现路径和避坑指南。
简单来说,在最近任务列表中隐藏或替换特定视图,核心是为了保护用户隐私和提升应用体验。想象一下,你正在用银行App转账,突然有消息弹出,你切出去看了一眼,这时如果从最近任务列表切回银行App,别人一眼就能看到你的账户余额和转账信息,这显然是不安全的。同样,对于一些阅读类、播放类应用,后台时显示一个品牌Logo或专辑封面,也比显示一个暂停的视频画面要优雅得多。
2. 核心原理与方案选型
要实现这个功能,我们得先理解Android系统是如何生成最近任务列表缩略图的。
2.1 系统如何生成最近任务缩略图?
当你按下“最近任务”键(方形按钮)时,系统会遍历所有正在运行的任务(Task),并为每个任务栈顶的Activity生成一张快照(Snapshot)。这个快照,本质上就是当前Activity窗口内容的一帧渲染。在Android 5.0 (API 21) 之前,这个行为应用很难干预。从Android 5.0开始,系统提供了Activity.setTaskDescription()方法,允许我们为任务设置描述,包括缩略图、标题和颜色。但更直接的控制,来自于Android 7.0 (API 24) 引入的ActivityManager.TaskDescription以及android:excludeFromRecents和窗口标志FLAG_SECURE的配合使用。
这里有几个关键概念需要厘清:
- 任务(Task): 用户为了完成某项工作而与之互动的一系列Activity的集合。一个应用可以启动多个任务。
- Activity: 任务中的单个界面。
- 窗口标志(Window Flags): 可以设置在Activity的Window上,影响其显示和行为。
我们的目标,就是控制这个“快照”生成的过程,或者控制快照本身的内容。
2.2 主流实现方案对比
根据不同的需求和Android版本,主要有以下几种方案:
| 方案 | 核心API/属性 | 作用层级 | 效果 | 适用场景 | 优缺点 |
|---|---|---|---|---|---|
| 1. 完全隐藏任务 | android:excludeFromRecents=”true” | Activity (Manifest) | 该Activity所在的任务不会出现在最近任务列表中。 | 启动页、登录页等临时性、不希望用户通过最近任务返回的界面。 | 优:彻底,一劳永逸。 缺:过于粗暴,用户无法通过最近任务切换回应用,可能造成困惑。 |
| 2. 禁止截屏与录屏 | WindowManager.LayoutParams.FLAG_SECURE | Window (代码动态设置) | 系统无法对该窗口进行截屏、录屏,同时最近任务列表会用一张默认灰色背景图替代真实界面。 | 支付、密码输入、私密聊天等包含高度敏感信息的界面。 | 优:安全性高,系统级防护。 缺:无法自定义替代视图,永远是系统默认的灰色。 |
| 3. 自定义任务缩略图与标题 | Activity.setTaskDescription() | Activity (代码动态设置) | 可以自定义该任务在最近任务列表中显示的缩略图(Bitmap)和标题。 | 希望后台时展示品牌Logo、统一封面的媒体类应用(音乐、视频、阅读)。 | 优:灵活,可定制化程度高。 缺:需要手动管理Bitmap,API 21+。 |
| 4. 动态替换根视图 | Activity.onPause()/onStop()中替换ContentView | View (代码动态设置) | 在Activity进入后台时,用一张预设的图片或简单布局替换掉当前复杂的界面。 | 希望隐藏复杂UI但保留简单背景或Logo,且对版本兼容性要求高的场景。 | 优:兼容性好(可向下兼容到低版本),实现直观。 缺:需要小心处理视图状态恢复,可能引发布局闪烁。 |
在实际项目中,方案2(FLAG_SECURE)和方案3(setTaskDescription)是最常用且最规范的。方案1过于极端,方案4则是一种“黑科技”,可以作为备选或兼容方案。接下来,我们重点深入方案2和方案3。
3. 方案一:使用 FLAG_SECURE 实现安全窗口
这是保护隐私最直接、最有效的方式。它的原理是告诉系统:“这个窗口的内容是安全的,不允许被非信任的方式捕获”。
3.1 基础实现代码
你可以在Activity的onCreate方法中,在setContentView之后设置这个标志。
class SecureActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_secure) // 在setContentView之后设置FLAG_SECURE window.setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE ) } }或者,更清晰的写法是只添加这个标志,而不影响其他已存在的标志:
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)3.2 效果与实测
设置之后,你会发现:
- 在该界面下,系统截屏组合键(电源+音量下)会失效,或者截出一张纯黑/纯灰的图片。
- 使用
adb shell screencap命令也无法捕获该窗口。 - 最关键的是:当应用进入后台,打开最近任务列表,该任务显示的将不再是真实的界面快照,而是一张系统默认的、带有应用图标和名称的灰色卡片。这完美实现了“隐藏真实视图”的目标。
注意:
FLAG_SECURE是一个“全有或全无”的标志。它不能让你自定义替换的视图,只能是系统默认的灰色卡片。如果你需要展示自定义的Logo,这个方案就不合适了。
3.3 常见问题与排查
Q1:我在onCreate里设置了,为什么有时候好像没生效?A1:检查代码执行顺序。务必确保window.addFlags的调用在setContentView之后。因为setContentView会触发窗口的某些初始化过程,在此之前设置标志可能被覆盖。
Q2:我想在某个条件下(如进入编辑模式)才启用安全窗口,可以动态切换吗?A2:可以,但需要小心。你可以通过window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)来清除标志。但请注意,清除标志后,系统可能会重新对当前窗口进行快照。更常见的做法是为需要安全保护的界面单独创建一个Activity。
Q3:这个标志会影响屏幕录制吗?A3:会的。无论是系统自带的屏幕录制还是第三方录屏App,都无法录制被FLAG_SECURE保护的窗口内容。这对于防止恶意软件录制用户操作非常关键。
实操心得:对于支付、密码修改等核心敏感页面,我强烈建议直接使用FLAG_SECURE。这是谷歌官方推荐的安全实践,能最省心地获得系统级保护。不要试图在这些页面上玩“自定义背景”的花样,安全是第一位的。
4. 方案二:自定义任务描述(TaskDescription)
如果你希望应用在后台时,在最近任务列表里展示一个漂亮的品牌Logo、专辑封面或者书籍封面,而不是灰色卡片,那么Activity.setTaskDescription()就是为你准备的。
4.1 核心API详解
从 Android 5.0 (API 21) 开始,你可以通过Activity.setTaskDescription()方法来设置三个属性:
- label: 任务在最近任务列表中显示的标题。如果为null,则使用应用名称。
- icon: 任务在最近任务列表中显示的图标(一个Bitmap)。注意,这个图标会显示在系统生成的快照之上,它不会替代快照本身。在Android 7.1之后,系统行为有变化,图标可能不显示,主要依靠缩略图。
- color: 任务卡片在最近任务列表中的主要背景色(以ARGB整数表示)。这个颜色会作为背景。
但更强大的功能在 Android 7.0 (API 24) 引入的ActivityManager.TaskDescription构造器中,它允许你直接传入一个Bitmap作为thumbnail(缩略图),这个缩略图会直接替代系统自动生成的窗口快照。这才是实现“自定义视图”的关键。
4.2 实现自定义缩略图
假设我们有一个音乐播放界面MusicPlayerActivity,我们希望它进入后台时,在最近任务列表里显示专辑封面。
class MusicPlayerActivity : AppCompatActivity() { private fun setCustomTaskDescription(coverBitmap: Bitmap?) { // 检查API版本,因为TaskDescription(bitmap)需要API 24+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val taskDescription = if (coverBitmap != null) { // 使用专辑封面作为缩略图 ActivityManager.TaskDescription( "正在播放:歌曲名", // label coverBitmap, // thumbnail - 核心参数! ContextCompat.getColor(this, R.color.primaryColor) // color ) } else { // 如果没有封面,使用默认图标和颜色 ActivityManager.TaskDescription( "正在播放:歌曲名", BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher), ContextCompat.getColor(this, R.color.primaryColor) ) } setTaskDescription(taskDescription) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // API 21-23 的降级方案:只能设置图标和颜色,无法自定义缩略图 // 这里的图标是小的角标,不是背景图 val taskDescription = ActivityManager.TaskDescription( "正在播放:歌曲名", BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher), ContextCompat.getColor(this, R.color.primaryColor) ) setTaskDescription(taskDescription) } // API 21 以下,系统不支持此功能 } override fun onPause() { super.onPause() // 当Activity进入后台时,更新任务描述 // 假设我们从网络或本地加载了专辑封面Bitmap val albumCoverBitmap = loadAlbumCoverBitmap() setCustomTaskDescription(albumCoverBitmap) } override fun onResume() { super.onResume() // 可选:当Activity回到前台时,可以清除自定义缩略图,恢复系统默认行为 // 但这通常不是必须的,因为下次进入后台时会再次设置。 // 如果希望恢复,可以传一个null的thumbnail(部分机型可能不支持)。 } }4.3 图片处理与性能优化
这里有一个巨大的坑:直接加载一张高分辨率的专辑图片作为Bitmap,并设置为TaskDescription,可能会导致两个问题:
- 内存浪费:最近任务列表的缩略图尺寸很小(通常不超过屏幕尺寸的1/4),使用原图会造成不必要的内存占用。
- 崩溃风险:系统对传递的Bitmap可能有大小限制,过大的Bitmap可能导致
TransactionTooLargeException或直接崩溃。
正确的做法是创建一张尺寸合适的缩略图:
private fun createTaskThumbnail(originalBitmap: Bitmap): Bitmap { // 获取最近任务列表缩略图的大致尺寸(单位:像素) // 这是一个经验值,不同厂商、不同分辨率设备会有差异,取一个安全值。 val thumbnailMaxWidth = 400 // px val thumbnailMaxHeight = 400 // px val width = originalBitmap.width val height = originalBitmap.height // 计算缩放比例 val scale = Math.min( thumbnailMaxWidth.toFloat() / width, thumbnailMaxHeight.toFloat() / height ).coerceAtMost(1.0f) // 如果原图比目标尺寸小,则不放大 val scaledWidth = (width * scale).toInt() val scaledHeight = (height * scale).toInt() // 使用createScaledBitmap进行缩放(注意:这不是质量最高的方式,但简单快捷) // 对于质量要求高,可以使用Bitmap.createBitmap配合Matrix进行缩放 return Bitmap.createScaledBitmap(originalBitmap, scaledWidth, scaledHeight, true) } // 在设置前使用 val scaledCoverBitmap = createTaskThumbnail(albumCoverBitmap) setCustomTaskDescription(scaledCoverBitmap)实操心得:务必对用于TaskDescription的Bitmap进行缩放和压缩。我通常会将图片缩放至长宽都不超过500像素,并使用Bitmap.compress(Bitmap.CompressFormat.JPEG, 80, outputStream)进行适当压缩后再解码回Bitmap,以进一步减少传输大小。这能有效避免因Bitmap过大导致的不可预知的问题。
5. 方案三:动态视图替换(兼容性方案)
对于需要兼容Android 5.0以下版本,或者觉得FLAG_SECURE太丑、TaskDescription又不够灵活(比如想隐藏视图但保留一个动态效果)的场景,可以考虑这个“黑科技”方案。
5.1 实现思路
核心思路是:在Activity即将被放到后台(onPause)时,用一个简单的布局(如一个ImageView显示Logo)替换掉当前的窗口内容;当Activity回到前台(onResume)时,再恢复原来的布局。
class HideContentActivity : AppCompatActivity() { private lateinit var originalContentView: ViewGroup private var isViewReplaced = false private val placeholderView: View by lazy { // 这是一个简单的占位视图,可以是一张图片或一个纯色布局 ImageView(this).apply { setImageResource(R.drawable.app_logo) scaleType = ImageView.ScaleType.CENTER_CROP setBackgroundColor(Color.BLACK) } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 保存原始的根视图 originalContentView = window.decorView.findViewById(android.R.id.content) as ViewGroup setContentView(R.layout.activity_main) // 你的正常布局 } override fun onPause() { super.onPause() // 延迟一小段时间替换,避免与界面跳转动画冲突 originalContentView.postDelayed({ if (!isFinishing && !isDestroyed) { replaceContentWithPlaceholder() } }, 100) // 100ms延迟是一个经验值,可根据需要调整 } override fun onResume() { super.onResume() // 如果视图被替换了,则恢复 if (isViewReplaced) { restoreOriginalContent() } } private fun replaceContentWithPlaceholder() { if (isViewReplaced) return // 1. 移除所有原始子View originalContentView.removeAllViews() // 2. 添加占位视图 originalContentView.addView(placeholderView) isViewReplaced = true } private fun restoreOriginalContent() { if (!isViewReplaced) return // 1. 移除占位视图 originalContentView.removeAllViews() // 2. 重新添加原始布局(需要重新inflate或已保存引用) // 简单做法:重新setContentView。但这会重新创建所有视图状态。 // 更好做法:在replaceContentWithPlaceholder前,保存原始子View的引用。 setContentView(R.layout.activity_main) // 这里需要根据业务恢复数据状态 isViewReplaced = false } }5.2 潜在问题与规避措施
这个方案听起来简单,但隐患非常多:
- 布局闪烁:在
onPause和onResume间快速切换视图,如果时机不对,用户可能会看到一瞬间的空白或闪烁。上述代码中的postDelayed就是为了缓解这个问题,但并非完美。 - 状态丢失:直接
removeAllViews()和setContentView会导致当前界面所有视图状态(如EditText的输入内容、ListView的滚动位置)丢失。恢复起来非常麻烦。 - 性能开销:频繁地重建布局对性能不友好。
- 兼容性玄学:不同厂商的ROM对
onPause/onResume的调用时机和最近任务截图时机处理可能不同,可能导致方案失效。
因此,我强烈不推荐将其作为首选方案。它更像是一个在特定约束下(如极低版本兼容)的“保底”方案。如果必须使用,务必进行充分的测试,并且只在不包含复杂交互和状态的简单页面上使用。
重要提示:经过测试,在Android 10 (API 29) 及更高版本上,系统截取最近任务快照的时机可能更早,在
onPause中替换视图可能已经来不及。这使得此方案的可靠性进一步降低。
6. 混合策略与最佳实践
在实际开发中,我们很少只使用单一方案。根据不同的页面类型,采用混合策略才是王道。
6.1 分场景策略选择
我通常遵循以下决策流程:
判断页面敏感性:
- 高度敏感(支付、密码、私密信息):无脑使用
FLAG_SECURE。这是金科玉律。 - 普通敏感/希望品牌展示(主界面、内容页):使用
setTaskDescription自定义缩略图。 - 完全不敏感/临时页面(启动页、广告页):可以考虑使用
android:excludeFromRecents或不做特殊处理。
- 高度敏感(支付、密码、私密信息):无脑使用
判断Android版本:
- API >= 24:优先使用
ActivityManager.TaskDescription的thumbnail参数,功能最完善。 - API 21-23:使用
ActivityManager.TaskDescription,但只能设置图标和颜色,效果有限。可以考虑配合FLAG_SECURE或动态视图替换(如果必须自定义)。 - API < 21:系统不支持官方API。如果确有需求,可谨慎尝试动态视图替换方案,并做好充分的兼容性测试和降级处理(即不做任何处理)。
- API >= 24:优先使用
6.2 一个健壮的实现示例
下面是一个结合了FLAG_SECURE和TaskDescription的封装工具类,它根据页面标签自动选择策略:
object RecentTaskHelper { /** * 配置Activity的最近任务显示策略 * @param activity 目标Activity * @param mode 模式 * @param customThumbnail 自定义缩略图Bitmap(仅SECURE_WITH_CUSTOM模式需要) * @param backgroundColor 任务卡片背景色(ARGB) */ @SuppressLint("ObsoleteSdkInt") fun configure(activity: Activity, mode: RecentTaskMode, customThumbnail: Bitmap? = null, @ColorInt backgroundColor: Int = Color.GRAY) { when (mode) { RecentTaskMode.DEFAULT -> { // 恢复默认:清除安全标志,清除自定义描述(如果需要) activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { activity.setTaskDescription(null) // 传null恢复默认 } } RecentTaskMode.SECURE -> { // 安全模式:设置FLAG_SECURE activity.window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) } RecentTaskMode.CUSTOM -> { // 自定义模式:清除安全标志,设置自定义缩略图 activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24+ 使用缩略图 val td = ActivityManager.TaskDescription( activity.title?.toString(), customThumbnail, backgroundColor ) activity.setTaskDescription(td) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // API 21-23 降级为设置图标 val icon = customThumbnail?.let { // 将缩略图缩放成一个小图标 Bitmap.createScaledBitmap(it, 144, 144, true) // 144dp是Launcher图标常见尺寸 } ?: BitmapFactory.decodeResource(activity.resources, R.mipmap.ic_launcher) val td = ActivityManager.TaskDescription( activity.title?.toString(), icon, backgroundColor ) activity.setTaskDescription(td) } // API 21以下,此模式无效 } } } /** * 创建一个适用于TaskDescription的安全缩略图(缩放+压缩) */ fun createSafeThumbnail(original: Bitmap, maxSizeKb: Int = 200): Bitmap { var output = original // 1. 缩放 val maxDimension = 500 // 像素 if (output.width > maxDimension || output.height > maxDimension) { val scale = maxDimension.toFloat() / output.width.coerceAtLeast(output.height) val newWidth = (output.width * scale).toInt() val newHeight = (output.height * scale).toInt() output = Bitmap.createScaledBitmap(output, newWidth, newHeight, true) } // 2. 压缩(循环压缩,直到满足大小要求) val baos = ByteArrayOutputStream() var quality = 90 do { baos.reset() output.compress(Bitmap.CompressFormat.JPEG, quality, baos) quality -= 10 } while (baos.toByteArray().size / 1024 > maxSizeKb && quality > 10) // 重新解码压缩后的字节流,确保我们设置的是压缩后的Bitmap val compressedData = baos.toByteArray() return BitmapFactory.decodeByteArray(compressedData, 0, compressedData.size) ?: output } } enum class RecentTaskMode { DEFAULT, // 系统默认行为 SECURE, // 安全模式(FLAG_SECURE) CUSTOM // 自定义缩略图模式 } // 在Activity中的使用示例 class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_my) // 假设这是一个音乐播放页面,我们想用专辑封面做自定义缩略图 val albumCover = loadAlbumCoverBitmap() // 你的加载逻辑 val safeThumbnail = RecentTaskHelper.createSafeThumbnail(albumCover) RecentTaskHelper.configure( activity = this, mode = RecentTaskMode.CUSTOM, customThumbnail = safeThumbnail, backgroundColor = Color.parseColor("#3F51B5") ) } override fun onDestroy() { // 可选:在Activity销毁时恢复默认设置,避免影响其他Activity // RecentTaskHelper.configure(this, RecentTaskMode.DEFAULT) super.onDestroy() } }6.3 厂商兼容性处理
不同手机厂商(小米、华为、OPPO、vivo等)对Android原生最近任务列表的修改程度很大。你的自定义缩略图或FLAG_SECURE效果可能会被削弱甚至无视。
测试与应对策略:
- 必须进行真机测试:在主流厂商的主流机型上进行测试,观察效果。
- 关注
onPause/onStop时机:有些厂商会提前截图。确保你的配置代码(如setTaskDescription)在onCreate或onStart中执行,而不是onPause。 FLAG_SECURE通常是可靠的:这个标志涉及系统安全,大多数厂商都会尊重。如果发现不生效,需要检查代码设置时机,或者考虑是否是ROM的Bug。- 做好降级处理:在
configure方法中,我们已经根据API版本做了降级。对于厂商兼容性问题,如果发现自定义缩略图无效,一个务实的做法是回退到FLAG_SECURE模式,至少保证了隐私安全,虽然牺牲了美观。
7. 问题排查与调试技巧
开发过程中,你可能会遇到各种“玄学”问题。这里分享一些调试技巧。
7.1 调试最近任务缩略图
使用ADB命令强制刷新:有时候系统会缓存旧的任务缩略图。你可以尝试杀掉应用进程再重新启动,或者使用以下ADB命令(需要设备有root权限或开发人员选项中的“不保留活动”开启,效果有限):
adb shell am task lock <TASK_ID> # 锁定任务(测试效果不明显)更直接的方法是,在开发者选项中打开“不保留活动”,然后反复切换应用,强制系统重新创建Activity和截图。
检查Bitmap格式和大小:这是
setTaskDescription失败最常见的原因。务必使用RecentTaskHelper.createSafeThumbnail这样的工具方法对图片进行预处理。可以通过Bitmap.getAllocationByteCount()打印处理前后Bitmap的大小。查看系统日志:在设置
TaskDescription或FLAG_SECURE时,关注Logcat中WindowManager或ActivityTaskManager相关的日志,可能会有错误提示。
7.2 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 自定义缩略图不显示,显示为灰色或空白。 | 1. Bitmap尺寸过大,传输失败。 2. API版本低于24,却使用了 thumbnail参数构造TaskDescription。3. 厂商定制系统不支持。 | 1. 缩放压缩Bitmap。 2. 检查API版本,做降级处理。 3. 真机测试,考虑降级到 FLAG_SECURE。 |
FLAG_SECURE设置了但最近任务仍显示内容。 | 1. 设置时机太晚(如在onResume里)。2. 某些厂商ROM存在Bug或故意放宽限制(极少见)。 | 1. 确保在onCreate的setContentView之后立即设置。2. 尝试在 onAttachedToWindow回调中再次设置。 |
| 从最近任务点开应用,界面状态(如滚动位置)丢失。 | 使用了“动态视图替换”方案,在onResume时错误地重建了视图。 | 避免使用动态视图替换方案。如果使用,需要精心保存和恢复视图状态(使用ViewModel或onSaveInstanceState)。 |
| 低版本Android(4.x)上想实现类似效果。 | 系统无API支持。 | 权衡需求必要性。如非必须,放弃。如必须,可尝试极其谨慎的动态视图替换,并接受其不稳定性。 |
7.3 一个实用的测试流程
- 基础功能测试:在原生或接近原生的Android设备(如Pixel)上,测试
FLAG_SECURE和TaskDescription是否能按预期工作。 - 兼容性遍历测试:准备几台主流厂商(小米、华为、荣耀、OPPO、vivo)的测试机,覆盖不同的Android大版本(如10, 11, 12, 13)。
- 边界情况测试:
- 快速连续切换应用,观察最近任务缩略图是否正确更新。
- 应用在后台被系统回收后,从最近任务恢复,观察界面和缩略图状态。
- 横竖屏切换时,观察缩略图是否适配。
- 性能与内存测试:使用Profiler工具,观察在频繁设置自定义缩略图时,是否有内存抖动或泄漏(注意Bitmap的回收)。
最后,记住一个核心原则:隐私安全优先。如果一个页面涉及敏感信息,即使牺牲一点用户体验(显示灰色卡片),也要优先使用FLAG_SECURE。自定义缩略图是锦上添花的功能,在确保安全和不引发兼容性问题的前提下使用。