news 2026/10/10 7:59:47

iOS相册多选与删除完整指南:PHPicker与PhotoKit权限避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
iOS相册多选与删除完整指南:PHPicker与PhotoKit权限避坑

简介:面向iOS开发者的相册图片多选与删除功能实现资料,聚焦如何借助QBImagePickerController完成系统相册多选、删除、拍照及图片压缩等常见需求,适合正在开发社交或图片编辑类应用、需要处理图片选择的初中级iOS工程师。资源包为1个PDF文件,整体77KB,内容以代码解析和步骤说明为主。已有288人学习浏览,可帮助读者快速理解图片选择器的集成方式、代理回调处理、选中数据模型ShowEditItem的设计以及删除后相册状态同步等关键细节。这份文档还覆盖了UIActionSheet确认删除、最大选图数量限制、已选AssetURL同步和压缩上传等实操要点,能有效减少踩坑成本。

1. 相册多选和删除一起做,卡住的往往不是业务代码而是权限

如果你准备在自己的 iOS App 里做「相册图片多选」和「删除」这两个功能,先记住一个反直觉的结论:多选几乎不碰权限,删除却必须走系统授权。同一个页面里一个选图、一个删图,背后的权限模型完全不一样——选图用 PHPicker 时连相册权限弹窗都没有,而删除必须申请PHPhotoLibrary的读写权限,用户在系统弹窗里拒绝一次,你的删除按钮就永久变灰。这件事让不少新手在联调时误以为是自己代码写错了,其实是授权状态机没理清楚。这篇文章把从「多选」到「删除」整条链路拆开,覆盖选型、代码、参数和坑,适合做工具类、社交类、相册整理类 App 的 iOS 业务开发照着落地。另外提醒一句:如果你的 App 要上架,多选和删除对应的权限文案、用途说明会被审核盯着看,这块我也会在后面讲到。

2. 多选用 PHPicker 还是 PhotoKit:先搞懂两套 API 的分工

2.1 UIImagePickerController 为什么不适合多选

很多从 Objective-C 时代过来的老项目,第一个想到的还是UIImagePickerController。它的allowsEditing、sourceType这些参数很眼熟,但用它做多选有三个硬伤:第一,它本质是「单选 + 编辑」,虽然 iOS 11 之后可以通过PHPickerConfiguration之外的方式支持多选,但那是UIImagePickerController私有 API 里PHAsset数组的桥接,可靠性和审核风险都不合适;第二,它的 UI 是系统相机相册二合一的,无法嵌入你自己的页面里做定制;第三,用户在相册里点了「取消」之后,代理回调里拿到的info[UIImagePickerControllerOriginalImage]可能是 nil,你还要做二次兜底。所以现在做新功能,我一般直接放弃UIImagePickerController。

PHPickerViewController是 iOS 14 出的替代品,它最大的特点是「不需要相册权限就能选图」。系统在PHPicker里自己跑一个独立的相册浏览界面,你的 App 不会直接拿到用户的PHAsset列表,只能拿到被选中的NSItemProvider。这带来一个好处:你的 App 不需要在 Info.plist 里声明NSPhotoLibraryUsageDescription,用户授权弹窗根本不会出现,审核负担轻很多。代价是:你拿不到相册的全量数据,也没法直接操作PHAsset做删除。

那删除怎么办?删除必须回到PhotoKit这一套,也就是PHPhotoLibrary加PHAsset。这里就出现了一个「选图和删除不是一个 API 体系」的分岔路口,很多人的项目就是在这里开始纠结的:到底用PHPicker做多选再用PhotoKit做删除,还是干脆全部走PhotoKit。

2.2 全部走 PhotoKit 的取舍:一次授权、全程可控

如果你全部用PHPhotoLibrary,用PHAsset.fetchAssets(with: .image, options: nil)拉取相册,自己做网格 UI,那多选和删除就统一了。好处是你能拿到PHAsset的本地唯一标识localIdentifier,删除、收藏、编辑都能直接做;坏处是你要处理相册权限、增量加载、cell 复用时的缓存,开发和维护成本是PHPicker方案的三倍以上。

我一般这样权衡:如果产品里已经有相册浏览页了,比如要做「整理相册」「清理相似照片」这种功能,那就直接用PhotoKit;如果只是某个业务环节需要用户挑几张图上传,比如头像、反馈截图、商品图片,那PHPicker一套下来最简单,删除单独走一次PhotoKit授权即可。这篇文章后面用「PHPicker做多选 +PhotoKit做删除」作为主线,也是因为这个组合对大部分 App 来说性价比最高。

2.3 PHPickerConfiguration 里必调的三个参数

PHPickerConfiguration是PHPickerViewController的配置入口,我建议你每次都明确设置这三个参数:

var config = PHPickerConfiguration() config.filter = .images config.selectionLimit = 9 config.preferredAssetRepresentationMode = .current let picker = PHPickerViewController(configuration: config) picker.delegate = self present(picker, animated: true)

第一个参数filter = .images表示只允许选图片,如果你想允许视频可以改成.any或者用.images.concat(.videos)——注意 iOS 15 之前没有concat,要用PHPickerFilter.any(of: [.images, .videos])。第二个参数selectionLimit是最大可选数,设为0表示不限数量,但产品上一般不建议无限选,因为后面删除和上传的压力都会变大。第三个preferredAssetRepresentationMode控制加载原图还是快速预览图,current模式会优先加载当前模型对应的数据,适合社交类轻量场景;如果是做「原图上传」或「删除前预览大图」,改成.original更稳。

不过PHPickerConfiguration有一个容易忽略的点:它默认在 iOS 14 上不支持横屏。如果你的 App 在 iPad 上允许横屏,presentPHPicker时系统会强制转回竖屏,这个现象在 iOS 15 之后缓解了,但仍建议在真机横屏环境下测一遍。

3. PHPicker 多选落地:从 present 到把图片取回来

3.1 最小可运行的多选代码与逻辑说明

PHPickerViewController的使用门槛很低,但很多初学 iOS 的开发者会在「拿图片」这一步卡住——代理方法里返回的不是UIImage,而是NSItemProvider。我们要通过它异步加载数据。以下是最小可运行版本:

final class ImagePickerDelegate: NSObject, PHPickerViewControllerDelegate { var onPick: (([UIImage]) -> Void)? func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { picker.dismiss(animated: true) var images: [UIImage] = [] let group = DispatchGroup() for result in results { group.enter() let provider = result.itemProvider if provider.canLoadObject(ofClass: UIImage.self) { provider.loadObject(ofClass: UIImage.self) { image, error in if let image = image as? UIImage { images.append(image) } group.leave() } } else { group.leave() } } group.notify(queue: .main) { [weak self] in self?.onPick?(images) } } }

这段代码里有几个关键点。DispatchGroup是必须的,因为loadObject是异步回调,每个PHPickerResult对应一个NSItemProvider,你需要等所有 provider 都加载完再统一回调,否则 UI 上会出现「选完图但图片还没显示」的空窗。canLoadObject(ofClass:)判断是否确实能加载UIImage,防止某些云端资源或 GIF 动图在特殊情况下加载不出来时直接走 else 分支静默跳过。

参数说明:results数组的顺序是不保证的,后面会专门讲顺序恢复的问题;provider.loadObject的内部实现会根据PHPickerConfiguration.preferredAssetRepresentationMode决定是解码原图还是快速预览图,所以不要在这里再手动UIImage(contentsOfFile:)二次解码。另外,onPick回调要回到主线程,这里用group.notify(queue: .main)完成了,但如果你是逐张回调 UI,记得DispatchQueue.main.async。

3.2 用 loadObject 还是 loadDataObject:两种加载方式的本质区别

很多项目会在「图片太大、内存暴涨」这个问题上翻车。loadObject(ofClass: UIImage.self)内部会做一次完整解码,生成的UIImage直接占内存;而loadDataObject拿到的是原始字节数据,你可以自己控制解码时机和采样尺寸。如果是选多张大图,我更推荐先取 data,再按目标尺寸解码:

provider.loadDataRepresentation(forTypeIdentifier: UTType.image.identifier) { data, error in guard let data = data else { return } let downsampleImage = UIImage.downsampledImage(from: data, to: CGSize(width: 300, height: 300), scale: UIScreen.main.scale) }

UIImage.downsampledImage是 iOS 15 之后系统提供的UIImage扩展方法,直接用CGImageSourceCreateThumbnailAtIndex做降采样,比UIImage(data:)后再 resize 省内存得多。在 iOS 14 上你只能手动写这个降采样逻辑。这里的关键参数是目标尺寸targetSize,选 300×300 足够做网格缩略图;如果后续要上传原图,再单独用.current或.original模式加载一份完整数据。

这里有一个被忽视的坑:loadDataRepresentation拿到的data有可能是 HEIC 格式。你上传到服务器之前如果服务器不认 HEIC,需要先用CGImageDestination转成 JPEG。这个转换最好在后台队列做,不要在主线程同步转,否则大图一把就卡掉帧。

3.3 多选顺序恢复:orderIdentifierHints 和 assetIdentifiers

用户选图的顺序是有业务意义的,特别是「第一张是封面」这类场景。PHPickerResult.assetIdentifier是被选中的PHAsset的localIdentifier,但它是可选值——如果用户从 iCloud 云端相册选图,某些情况下这个字段会是 nil。处理顺序的标准做法是记录picker的orderIdentifierHints,然后用assetIdentifier排序:

let hints = config.selectionLimit > 1 ? picker.configuration.orderIdentifierHints : nil // 在回调里,用 assetIdentifier 和 hints 做映射

不过这个接口在 iOS 14 的表现并不稳定,我自己更常用的方案是:在didFinishPicking里直接遍历results时,用result.assetIdentifier作为 key,把UIImage存进字典,再用picker.configuration.orderIdentifierHints给出的顺序数组去取出有序结果;如果 orderIdentifierHints 为空,就退回到「按用户点击顺序」不可用的事实,按数组原始顺序处理并给 UI 加一个可拖动排序的机制。血泪经验是:不要在代理回调里依赖results本身的顺序,它有时候和用户点击顺序一致,有时候不一致,属于「看心情」的级别。

为什么会出现不一致?因为PHPickerResult的数组顺序由系统内部的数据源决定,不是点击顺序。如果你的 App 强调「选图顺序 = 展示顺序」,一定要在业务层自己排序,不能赌系统行为。

4. 删除的权限链:PhotoKit 授权与 PHAsset 删除

4.1 从 Info.plist 到 PHAccessLevel:删除需要什么权限

删除相册图片必须走PHPhotoLibrary,这套 API 的权限声明和PHPicker完全不同。第一步是在 Info.plist 里声明用户提示文案:

<key>NSPhotoLibraryUsageDescription</key> <string>用于选择需要清理的图片</string> <key>NSPhotoLibraryAddUsageDescription</key> <string>用于保存处理后的图片到相册</string>

这里要说明:如果你只删除、不新增图,NSPhotoLibraryUsageDescription是必需的;如果你还要把处理后的图存回相册,才需要NSPhotoLibraryAddUsageDescription。很多开发者在审核时被拒,就是因为删除功能只写了NSPhotoLibraryAddUsageDescription,被苹果判定为「权限和用途不匹配」。iOS 14 之后系统引入了PHAccessLevel这个粒度,区分「只读」和「读写」,并分别对应不同的权限弹窗。删除照片需要的权限在 iOS 14+ 上对应的是.readWrite级别里的删除能力,而这个级别的授权请求和相册访问请求是同一个弹窗,所以你别想着「只申请读取权限就能删」,不行。

4.2 授权状态机:authorizationStatus 的四个分支与请求时机

权限请求的正确姿势是「先查状态、再决定弹不弹窗」,代码如下:

let status = PHPhotoLibrary.authorizationStatus(for: .readWrite) switch status { case .notDetermined: PHPhotoLibrary.requestAuthorization(for: .readWrite) { newStatus in DispatchQueue.main.async { if newStatus == .authorized || newStatus == .limited { // 可以执行删除 } else { // 弹窗提示去设置 } } } case .authorized, .limited: // 可以直接删除 break case .denied, .restricted: // 引导去系统设置 break @unknown default: break }

注意几个参数细节。第一,authorizationStatus(for:)是 iOS 14 的 API,iOS 13 及以下要降级到无参的authorizationStatus(),并且requestAuthorization(for:)也要换成无参版本,所以如果你的 App 最低版本支持 iOS 13,这段代码要加#available(iOS 14, *)分支。第二,limited状态是 iOS 14 新增的「部分授权」,用户只选了几张图给你访问,此时你PHAsset.fetchAssets只能拿到那几张,删除也只对那几张有效,如果用户以为能删全部就会产生困惑,UI 上最好显式区分「已授权全部照片」和「仅部分照片」。第三,弹窗时机不要放在启动时,也不要和PHPicker的 present 同时进行——系统同一时间只允许一个权限弹窗,如果你启动时先弹了定位权限,相册权限弹窗会被系统静默挂起。

4.3 performChanges 删除:事务操作与进度

删除操作本身不算复杂,但要注意PHPhotoLibrary的一切修改都要放在performChanges的变更块里:

var assetsToDelete: [PHAsset] = [] // 通过 localIdentifier 找回 PHAsset for id in localIdentifiers { let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil) if let asset = fetchResult.firstObject { assetsToDelete.append(asset) } } PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.deleteAssets(assetsToDelete as NSArray) }) { success, error in DispatchQueue.main.async { if success { // 更新 UI,移除已删图片 } else { // 处理 error } } }

PHAssetChangeRequest.deleteAssets接受的是数组,可以一次批量删多个PHAsset,但有几个边界情况要留意:如果你传入的PHAsset来自一个已经失效的PHFetchResult(比如相册在别处被 iCloud 同步修改过),fetchAssets(withLocalIdentifiers:)可能返回空。稳妥做法是:用户点了「删除」之后,先用localIdentifier重新 fetch 一次,确认 asset 还存在,再放进删除数组。另外,performChanges的完成回调不一定在主线程,所以 UI 更新要主动切主队列。

删除是「高影响操作」,我建议你在 UI 上做二次确认弹窗,并且用PHPhotoLibrary的performChanges的 error 回调来区分两种失败场景:权限不足和 asset 不存在。权限不足时 error 的domain是PHPhotosErrorDomain,错误码PHPhotosErrorUserDeniedPhotoLibrary;asset 不存在时PHAsset.fetchAssets返回 count 为 0,你可以在变更前就拦住。

5. 相册多选删除避坑:六个高频踩坑点

5.1 坑一:用 PHPicker 选完图直接拿 PHAsset 去删,结果删了个寂寞

现象:用户用PHPickerViewController选完图片,你拿到result.assetIdentifier,然后用PHAsset.fetchAssets(withLocalIdentifiers:)去查询并删除,发现有时候删不掉,有时候删错图。

原因:PHPickerResult.assetIdentifier在用户未授权PHPhotoLibrary的情况下是 nil;即使授权了,它只在 iOS 14.2 之后稳定返回。如果你的 App 先用了PHPicker,系统可能根本没要求用户授权相册,这时assetIdentifier完全是空的,删除链路自然断裂。

解决:PHPicker选图后不要立即依赖assetIdentifier删除。先检查PHPhotoLibrary.authorizationStatus(),如果是.notDetermined,先主动请求授权并等回调结束,再重新走删除流程。

5.2 坑二:用户删除了相册里的某张图,App 里的缓存 PHAsset 还在

现象:PHFetchResult第一次加载是正常的,用户切到系统相册手动删了一张图,回到 App 再点删除这张图,performChanges报错或 UI 上图片还在。

原因:PHAsset是一个「对象引用」,不是数据快照。相册被 iCloud 同步或系统相册改动后,旧的PHFetchResult里的 asset 对象可能已被标记为无效,但你持有的内存引用不会自动消失。

解决:每次展示网格和每次删除前,都用PHAsset.fetchAssets重新拉取,不要长时间持有PHFetchResult。常见做法是:进入删除页面时 fetch 一次,用户下拉刷新时再 fetch 一次,删除成功后手动从数据源移除并刷新集合视图。

5.3 坑三:iCloud 云端照片未下载完就删除,导致删除失败或云端残留

现象:用户开启了「优化 iPhone 存储空间」,相册里很多图片只有缩略图,原图在 iCloud。你删除时只删掉了本地占位图,远端原图还在,用户实际没删干净。

原因:删除PHAsset时,系统需要确保该 asset 的元数据一致。若原图尚未下载,PHAssetChangeRequest会把删除操作挂起或直接失败,具体表现取决于系统版本。

解决:删除前先询问PHAsset的isCloudPlaceholder属性,如果为 true,先用PHImageManager请求原图下载,等PHImageResultIsInCloudKey为 false 后,再执行删除。这里有个细节:请求下载的targetSize要传PHImageManagerMaximumSize,否则只会下载一个低清版本,删除后云端清理可能不彻底。

提示:如果删除前要预览原图,不要用PHImageManager.requestImage加载原图后又立刻删,至少等加载回调完成再删,避免系统并发操作同一 asset 导致未知错误。

5.4 坑四:权限被拒后连续弹系统设置引导,触发 iOS 弹窗节流

现象:用户第一次点了「不允许」,你在按钮上加了「去设置开启权限」的引导,结果 App 在 iOS 上反复跳UIApplication.openSettingsURLString,第二次点击时直接没有反应。

原因:iOS 对跳转系统设置有限流机制,短期内多次调用openSettings可能会被系统忽略。同时,requestAuthorization在用户拒绝后再次调用不会弹窗,只会立刻返回 denied。

解决:用一个布尔值记录权限引导弹窗是否已经展示过。如果用户拒绝过,只在自定义 UI 里展示「当前未授权」的提示,不要每次点击删除按钮都去调系统设置;跳转openSettings的按钮加一个冷却时间(比如 10 秒内只能点一次)。这里的坑很隐蔽但遇到过一次就知道痛了。

5.5 坑五:多选大图导致内存暴涨

现象:选择 20 张高清照片后,App 内存瞬间涨到 600MB 以上,在低端机型上直接崩溃。

原因:loadObject(ofClass: UIImage.self)会完整解码每张原图,20 张 12MP 的图同时解码,每张占 30–50MB,累计下来相当恐怖。

解决:不要拿到结果后立刻全部加载原图。把preferredAssetRepresentationMode设为.current(拿预览图),或者统一用loadDataRepresentation后降采样。真正的原图上传放到用户点击「下一步」之后再按需加载,避免一次性并发加载全部。

5.6 坑六:删除权限和上架审核的绑定关系

现象:App 审核被拒,理由是「你的 App 声明了相册权限,但在使用场景中没有提供明确的用途说明」,或者反过来,你只用了删除功能但权限文案写的是「选择图片」。

原因:审核员会逐字对照NSPhotoLibraryUsageDescription和实际功能。删除权限属于相册访问权限,苹果审核时对「批量修改用户数据」类功能会额外看,所以文案里要明确出现类似「删除所选照片」的字眼,而不能只有「选取图片」。

解决:把权限用途文案拆开或合并写清楚,并保证 App 界面上的提示和 Info.plist 文案一致。做过上架流程的同学应该知道,这块写不好会白白浪费两周审核周期。

6. 进阶:升级权限与批量删除的进度反馈

6.1 授权状态从 limited 升级到全量:用系统弹窗补全授权

如果是 iOS 14+ 的.limited状态,你删除功能只能作用于用户勾选的那几张图。这时候有一个系统自带的能力:点击相册权限里的「允许访问所有照片」,会唤起一个 system prompt 让用户升级授权。开发者能做的,是在 UI 上给「仅部分照片」状态提供一个引导入口:

// 展示自定义提示,告知用户当前只能访问部分照片 // 引导按钮点击后: if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) }

UIApplication.openSettingsURLString是跳转系统设置的标准方式,但前面说过它有限流,所以这个入口在limited状态下也不要频繁出现。更好的方式是:直接展示PHPhotoLibrary自带的提示——当 app 处于 limited 状态且用户点击了PHPhotoLibrary相关权限管理页时,系统会在设置里显示「允许访问所有照片」的按钮。

6.2 批量删除进度的两种反馈模式

删除多张图片时,performChanges是一次事务,没有逐张进度回调,用户容易觉得界面卡死或用完无反馈。常见做法有两种:

第一种是删除前把界面切到「处理中」状态,用UIActivityIndicatorView转圈,等performChanges回调回来再刷新网格。适合几十张以内的批量删除,代码最简单。

第二种是针对「清理相似照片」「按月清理」这类可能删几百张的场景,用PHPhotoLibrary的performChanges加PHChange监听做不到逐张进度,所以更实际的方案是把删除分批执行,每批 20 张:

let batchSize = 20 let batches = stride(from: 0, to: assets.count, by: batchSize).map { Array(assets[$0..<min($0 + batchSize, assets.count)]) } for (index, batch) in batches.enumerated() { PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.deleteAssets(batch as NSArray) }) { success, error in DispatchQueue.main.async { let progress = Float((index + 1) * batch.count) / Float(assets.count) // 更新进度条 } } }

这里的进度不是精确的逐张进度,而是「批次完成度」,但 UI 上足够用了。注意分批删除的PHAsset数组必须是从一个PHFetchResult里取出来的,删除后这个PHFetchResult的 count 会变化,如果你循环里用固定索引取,下一次批次会越界。所以每批删除前要重新 fetch 一批PHAsset,或者用localIdentifier存好、每批前重新查询一次再删。

6.3 我的习惯和收尾建议

权限申请时机、批大小、降采样尺寸这些都建立在你对业务场景的理解上。我习惯把多选和删除封装成两个独立的 manager,一个管PHPicker加载,一个管PhotoKit删除,中间通过localIdentifier桥接,便于日后单独换实现。最后提醒一句:删除相册图片是不可逆的高影响操作,iPad 上如果实现了多选手势,记得删除前做二次弹窗并明确展示张数。

希望这篇文章能帮你把「相册图片多选和删除」这两个功能一次做对,少走我当年踩过的弯路。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 7:59:46

用Claude Opus 5.5打造代码视频五步流水线

先给你打个预防针&#xff1a;Claude Opus 5.5 并不会像 Sora 那样&#xff0c;给你一句话就吐出一段视频。很多人第一次听到“用 Claude 做视频”就往生成式视频那个方向想&#xff0c;但代码视频这条路完全是另一套逻辑——模型负责写代码&#xff0c;渲染引擎负责画画面&…

作者头像 李华
网站建设 2026/10/10 7:59:14

DeepSeek大模型实战指南:从架构解析到128K上下文部署

1. 这不是“笔记”&#xff0c;而是一份可复现的大模型认知地图DeepSeek大模型学习笔记——看到这个标题&#xff0c;很多人第一反应是&#xff1a;又一份堆砌术语的PPT式总结&#xff1f;或者干脆是某位同学课后随手记的零散想法&#xff1f;但如果你真这么想&#xff0c;就错…

作者头像 李华
网站建设 2026/10/10 7:58:59

CTF MISC文件操作实战:从类型识别到分离合并

简介&#xff1a;面向CTF竞赛初学者及安全爱好者的PDF资料&#xff0c;系统讲解杂项基础解题技能&#xff0c;重点覆盖文件类型识别、分离与合并三大模块。文档从常考题型切入&#xff0c;逐步演示file命令与010Editor判别文件类型&#xff0c;再结合Binwalk、foremost、dd、fc…

作者头像 李华
网站建设 2026/10/10 7:58:27

零基础AI漫剧量产全流程:从剧本到成片的实战工作流

如果你既不会画画、也不懂剪辑&#xff0c;却想做出能连续更新的 AI 漫剧&#xff0c;这套“零基础 AI 漫剧智能量产创作营”的笔记应该能帮到你。我认真跟完整个创作营&#xff0c;亲手跑通了一条2分钟漫剧短片的生产全流程——从写剧本、画分镜、生成动态画面、配音到合成字幕…

作者头像 李华
网站建设 2026/10/10 7:58:12

MCP协议:模型能力协商与智能编排实战指南

1. MCP 不是新名词&#xff0c;而是新范式&#xff1a;从“接口调用”到“能力协商”的认知跃迁 最近在多个技术社区和工程现场反复听到一个词&#xff1a;MCP。不是某个新出的框架&#xff0c;也不是某家公司的私有协议&#xff0c;而是一种正在快速落地的 模型能力交互范式…

作者头像 李华
网站建设 2026/10/10 7:57:42

Android Studio开发记事本App:SQLite与RecyclerView实战

简介&#xff1a;一款基于Android Studio开发的安卓记事本应用&#xff0c;面向初学Android开发的Java学习者&#xff0c;提供从登录注册到记事增删改查的完整移动端小项目。核心功能涵盖用户注册登录、记事列表展示、添加与修改记事、基于SQLite的数据持久化&#xff0c;并自动…

作者头像 李华