整理自项目模拟面试,覆盖桌面 Widget 架构、图片与视频链路、点击与动画实现、Firebase Storage 媒体处理、相机方案与工程取舍
PicPat 是一个偏熟人社交方向的图片分享产品,核心体验是用户把自己当下拍到的照片,以低门槛、强即时性的方式分享给好友,并通过桌面 Widget 快速触达。AppWidgetProvider,而是 Provider -> WidgetUpdateService -> WidgetUpdater -> LayerChain -> RemoteViews 这条统一更新链路。AppWidgetProvider 只适合接系统生命周期,不适合承载复杂业务onUpdateBasePhotoWidgetonUpdate、onAppWidgetOptionsChanged、onDeleted不直接拼 UI,而是转发给 WidgetUpdateService
WidgetUpdateService
恢复正常态
WidgetUpdater
@RemoteViewsUpdater(...) 绑定不同 updaterPhotoWidgetUpdaterMiddlePhotoWidgetUpdaterBigPhotoWidgetUpdaterMix42WidgetUpdaterMix44WidgetUpdater
LayerChain
EventLayerLikeLayerAvatarLayerVideoLayerPendantLayerFrameLayerPhotoLayerBackgroundLayerBasisLayerAppWidgetManager.requestPinAppWidget(...) 请求添加appWidgetIdAppWidgetProvider.onUpdate()BasePhotoWidget 把 widgetId 和 provider 交给 WidgetUpdateServiceWidgetUpdateService 通过 provider 上的 @RemoteViewsUpdater(...) 找到具体 WidgetUpdaterWidgetUpdater 基于当前 widgetId 组装 WidgetInfoLayerChain 生成最终 RemoteViewsAppWidgetManager.updateAppWidget(...) 发给桌面宿主展示partialUpdateAppWidget(...) 的地方尽量局刷,减少宿主压力WidgetUpdater 是 widget 更新框架里的统一渲染入口和类型分发中心。WidgetUpdateService 根据 provider 找到具体 updaterwidgetId 从本地状态中拿到当前数据WidgetInfogetRemoteView(...) 生成完整 RemoteViewsAppWidgetManager.updateAppWidget(...) 完成全量更新WidgetUpdater 还定义了 getPartialUpdater(action)PhotoPartialUpdaterAvatarPartialUpdaterSkinPartialUpdaterLikeAnimPartialUpdaterRealLayerChain 本质上是责任链模式,实现思路上很像 OkHttp 的拦截器链。nextRequest -> ResponseWidgetInfo -> RemoteViewsBitmap。WidgetSpWidgetViewData 里取本地路径PhotoLayer 用 Glide asBitmap().load(filePath) 加载本地文件RemoteViews.setImageViewBitmap(...) 写入 widgetRemoteViews 的能力有限Bitmap 更可控RemoteViews 最终要通过 Binder 发给桌面宿主。TransactionTooLargeException 或宿主侧异常override(size, size)RemoteViews 的传输updateAppWidget(...)主图、头像、皮肤、点赞、视频态尽量走 partiallyUpdateAppWidget(...)
统一调度更新入口
WidgetUpdateService中间层可以做去重、拦截和状态控制
重逻辑放后台
图片解码、数据组装、下载等工作不放在 Provider 主线程里直接做
控制图片尺寸
只传 widget 需要尺寸的 bitmap
使用本地缓存
WidgetSp 会缓存图片路径、视频路径、Top3 数据和状态信息PendingIntent + BroadcastReceiver + WidgetUpdateService + 状态机 统一处理。RemoteViews 时,为不同区域绑定不同 PendingIntentWidgetClickBroadcastReceiverWidgetUpdateServiceWidgetUpdateService 根据 action 分发:NormalMenuRemoteViews 加载带 layoutAnimation 的布局rotatetranslatealphascaleLikeAnimPartialUpdater 里用了 ValueAnimator.ofInt(...)partiallyUpdateAppWidget(...) 更新到桌面RemoteViewsTextureView、SurfaceView、ExoPlayer 这类页面内播放器能力WidgetUpdateServiceWidgetUpdateService 创建 MediaCodeFrameUtilMediaExtractor + MediaCodec 解码视频帧RemoteViews,但代价是 CPU、内存和 IPC 成本都更高MyDownloadThreadFirebase.storage.reference.child(path).getFile(localFile)HistoryRepositoryCloudVideoSyncMgr 负责videoLocalPath 写回数据库Glide.load(localPath) 或加载普通 URLPartialAppWidgetTarget] 专门继承 CustomTarget<Bitmap>into(ImageView),而是:RemoteViews.setImageViewBitmap(...)AppWidgetManager.partiallyUpdateAppWidget(...)ImageView 引用SimpleTarget<Bitmap>videoPathcompressedPathFirebaseStorageMgr.uploadVideoFromPath(...)MyUploadVideoServicecompressedPath 对应文件上传到 Firebase StorageFFmpegBridge.doCompress(...)CameraX + OpenGL 预览渲染 + 自定义编码链路。CameraEngine 里用了:ProcessCameraProviderPreviewImageCaptureCameraSelectorPreviewViewGLCameraViewGLCameraRenderSurfaceTextureVideoCaptureMediaVideoEncoderRunnableMediaCodec + MediaMuxer 完成编码和封装WidgetUpdaterLayerChain 解决复杂 RemoteViews 拼装问题RemoteViews 能力有限,只能按不同场景选最稳实现PicPat 这个项目最有代表性的难点,不是单点功能,而是围绕图片社交场景,把拍摄、上传、下载、展示、桌面触达和多媒体体验串成了一条完整链路。