Android 基础补强 B13|通知、图片、相机与影音:先分清资源从哪里来
发布摘要:围绕一张文章附件,理解通知渠道与权限、系统照片选择器、拍照输出 URI,以及播放器的资源寿命。标签:通知、Photo Picker、相机、Media3。
《第一行代码》第 9 章把通知、拍照、选图和播放音视频放在多媒体主题下,但实际开发时它们不是“一套权限加几个按钮”。通知由系统展示,图片可能来自另一个内容提供者,拍照可以委托相机应用,播放器则持有解码与输出资源。先回答由谁执行、内容在哪里、谁负责释放,API 才不会混成一团。
本篇是第 9 章基础补强,适合放在 D19 后的选修时段。练习场景是给本地文章笔记选择附件,并用一条调试通知提示“附件已准备好”。下列代码尚未在用户工程编译运行,通知和媒体入口分别验证后再合并。
一、通知渠道与通知权限是两道条件
Android 8.0(API 26)引入通知渠道;面向该版本及以上的应用发送通知需要指定渠道。用户可以在系统设置中修改渠道行为,应用重复创建同一个渠道不会把用户设置随意重置。通知渠道
Android 13(API 33)及以上,非豁免通知还涉及 POST_NOTIFICATIONS 运行时权限。目标 SDK 33 及以上的应用能选择合适的申请时机;旧目标版本的弹窗时机由系统规则决定,不能直接套用同一流程。通知运行时权限
下面函数假设 compileSdk 至少 33、minSdk 至少 23,已声明通知权限,申请流程在宿主完成。依赖 AndroidX Core,导入需要 Android 的 Context、NotificationChannel、NotificationManager、Build、Manifest、PackageManager,和 AndroidX 的 ContextCompat、NotificationCompat、NotificationManagerCompat。
funpostAttachmentNotice(context:Context):Boolean{valchannelId="attachment_results"valmanager=context.getSystemService(NotificationManager::class.java)if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){manager.createNotificationChannel(NotificationChannel(channelId,"附件处理结果",NotificationManager.IMPORTANCE_DEFAULT))}if(Build.VERSION.SDK_INT>=33&&ContextCompat.checkSelfPermission(context,Manifest.permission.POST_NOTIFICATIONS)!=PackageManager.PERMISSION_GRANTED)returnfalsevalcompat=NotificationManagerCompat.from(context)if(!compat.areNotificationsEnabled())returnfalsevalnotice=NotificationCompat.Builder(context,channelId).setSmallIcon(android.R.drawable.ic_dialog_info).setContentTitle("附件已准备好").setContentText("可以返回文章笔记查看").setAutoCancel(true).build()compat.notify(1001,notice)returntrue}true 表示代码走到了提交通知,不保证用户实际看见:渠道可能被单独关闭,设备也可能采用不同展示策略。示例用系统图标方便实验,正式应用应提供符合通知要求的自身小图标。若需要点击进入笔记,再加入显式目标 PendingIntent,并正确指定可变性;上面的最小通知没有实现点击跳转。
二、选一张图片不等于读取整个图库
Photo Picker 让用户选定图片或视频,应用取得所选内容访问权。AndroidX Activity 的 PickVisualMedia 合约提供设备适配与回退支持,应使用工程配套的稳定版本,不根据某个 Android 版本号自行假设所有设备都安装相同选择器。Photo Picker
实现步骤是注册选择合约、点击时 launch、处理非空 URI、用图片加载器解码显示。结果为 null 代表没有选中内容,是正常取消;不能在回调里自动再次打开选择器。显示缩略图应让成熟图片加载库按目标尺寸加载,不把原图全部解码到一个随意大小的 Bitmap。
如果附件需要长期保留,先决定保留原文档授权还是复制到应用自己的存储。前者受提供者和授权寿命影响,后者需要管理容量、重复文件和删除时机。复制完成后才把附件元数据写入数据库,避免列表记录存在但实际文件尚未准备好。
三、拍照先准备输出,不能只等一个 Bitmap
使用系统相机时,完整照片应写到启动前准备好的 URI。ActivityResultContracts.TakePicture 接受这个输出 URI,返回是否成功;应用仍要保存待处理 URI,以便宿主重建后知道照片应该在哪里。仅使用相机返回的小缩略图,无法代表已经保存高清原图。
本课可以复用 FileProvider 的专用照片目录,授予相机应用必要的写入访问。若仅委托外部相机拍摄且应用没有自行声明使用 CAMERA 的需求,通常无需自己请求相机权限;直接使用 CameraX 或 Camera2 则属于应用使用相机,需要相应权限与生命周期管理。不要把两种方式混在同一权限结论里。相机委托拍照
用户取消拍照时清理本次创建的无效临时文件,成功后检查可读取性并生成展示缩略图。若写入的是公开媒体库,还需使用对应版本的 MediaStore 规则;本练习限定为应用控制的附件目录,避免把旧版外部存储路径写法照搬到现代系统。
四、播放器必须有明确的所有者
书中的 MediaPlayer 有自己的状态机,创建、准备、播放、暂停和释放不是任意顺序都合法。现代新功能可使用 Media3 ExoPlayer,但“换库”也不能省掉生命周期。阅读页内短视频由阅读页相关宿主管理,退出后释放;真正需要后台持续播放则应设计 MediaSession 与服务,不把页面播放器偷偷留在内存里。
创建播放器、设置 MediaItem、prepare、连接播放器视图和 release 应成对设计。重组时不能无条件重新创建播放器,释放后不能继续播放;前后台切换策略也要根据是否允许持续播放明确实现。Media3 播放器入门
五、故障实验与预期
关闭通知渠道后发送,预期不能仅凭 notify 调用成功判断可见;拒绝通知权限后,附件处理本身仍应成功。选图后取消,预期保留原附件;选择大图,预期按显示尺寸加载而不是造成明显内存压力。
拍照期间重建宿主,检查待处理 URI 是否保留;取消拍照检查临时文件清理。播放期间退出页面,再进入新页面,预期旧播放器不会继续占用当前页面的音视频输出。每种现象都需设备验证,本文不把预期写成既有测试记录。
六、原创面试问答与追问
本篇可联系题库权限申请和Activity 生命周期。下面问答为自拟。
问:通知权限允许就一定出现通知吗?答:渠道、全局设置和系统展示行为仍会影响结果。追问:如何定位?分别检查权限、渠道、构建字段和实际系统状态。
问:选一张图为什么通常不用申请整个媒体库权限?答:系统选择器提供针对选中内容的访问。追问:URI 能一直用吗?要考虑授权持久化、内容删除和复制策略。
问:播放器藏到单例就能避免重建吗?答:可能延长资源寿命并持有错误宿主,不是通用答案。追问:怎样决定归属?由页面播放或后台播放需求决定生命周期所有者。
七、练习与验收
建议分两次完成:第一次九十分钟做通知与选图,第二次九十分钟做拍照输出和短视频释放。每次只增加一个功能,故障注入后再整合。
交付包含版本条件表、附件 URI 流程图和四类取消或失败记录。主线只需要系统分享时,可以把本篇标为基础选修,不能为了覆盖书籍目录就宣称全部多媒体功能已经进入 DevCommunity。