解决Google Photos iOS集成难题:Library API迁移Picker API
2025-03-21 17:17:32
Google Photos 从 Library API 迁移到 Picker API (iOS) 的难题及解决
最近在搞 Google Photos 集成,遇到个麻烦事。Google Photos 的 Library API 要在 3 月 31 号停用了,得换成 Picker API。
按 Google 官方文档说的,集成步骤挺清楚的:
- 搞到用户的 OAuth 2.0 访问令牌 (access token)。
- 创建一个会话 (session),这会返回一个包含
pickerUri
的PickingSession
。 - 用
pickerUri
把用户导到 Google Photos App 里。
问题就卡在最后一步了。试了两种方法,都有问题:
尝试一:直接用 PickerUri 打开 Google Photos App
func openGooglePhotosApp(with pickerUri: String) {
guard let url = URL(string: pickerUri) else {
print("Invalid pickerUri")
return
}
if UIApplication.shared.canOpenURL(url) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
print("Cannot open URL. Make sure the Google Photos app is installed.")
}
}
结果这代码直接打开了 Safari,跳到 pickerUri
,然后,让输 Google 账号密码…… 我可不想让用户再输一遍。
尝试二:用 WKWebView 加载 PickerUri
private func loadGoogleWebViewToSelectPhotos(pickerUri: String) {
guard let urlToLoad = URL(string: pickerUri) else {
return
}
var request = URLRequest(url: urlToLoad)
request.addValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
googleWebView.load(request)
}
还是一样,WebView 加载出来,又要输账号密码。这用户体验也太差了。
看起来这 Picker API 好像只考虑了 Web 应用,没怎么考虑 iOS。安卓那边好像挺简单的,用他们的 Photo Picker 组件就行了。
问题出在哪儿?
根本原因在于,Google Photos Picker API 的 pickerUri
设计主要是为了在浏览器环境中使用。它不是一个可以直接用来在 iOS 上调起 Google Photos App 并自动登录的 URL Scheme。
- URL Scheme 问题 : 直接用
UIApplication.shared.open(url)
打开pickerUri
,系统不知道该怎么处理,所以默认用 Safari 打开。 - 授权问题 : 即使用 WKWebView 加载,并且在请求头里加了
Authorization: Bearer \(accessToken)
,这个 token 是给 Picker API 用的,不是给 Google Photos Web 界面用的,所以还是会要求登录。
解决思路
既然官方的 Picker API 在 iOS 上直接用不行,那就只能曲线救国。我研究了一番,找到了几种可能的解决办法:
方案一:继续用 Library API (短期)
虽然官方说 Library API 要停用,但实际上不一定马上就完全不能用。可以先苟着,同时积极寻找其他方案。但这只是权宜之计,不能长期依赖。
操作: 啥也不用改,维持现状。
优点:
* 简单。
* 不需要立即做改动。
缺点:
* 有潜在风险, 该 API 可能随时彻底停止工作.
方案二: 反向代理(Proxy) + 网页内嵌选择
自己搭个反向代理服务器。客户端先把请求发到你的服务器,你的服务器带着用户的 access token 去请求 Picker API,获取到 pickerUri
。然后服务器生成一个简单的 HTML 页面,这个页面里用 JavaScript 打开 pickerUri
(因为 pickerUri
是设计给浏览器用的)。最后,客户端用 WKWebView 加载你服务器上的这个 HTML 页面。
原理:
- 客户端发送请求到你的反向代理服务器,带上用户的 access token。
- 反向代理服务器使用这个 access token 请求 Google Photos Picker API,获取
pickerUri
。 - 反向代理服务器构建一个简单的 HTML 页面:
<!DOCTYPE html> <html> <head> </head> <body> <script> window.location.href = "[pickerUri]"; // 替换成实际的 pickerUri </script> </body> </html>
- 反向代理服务器把这个 HTML 页面返回给客户端。
- 客户端用 WKWebView 加载这个 HTML 页面。由于这个 HTML 页面会在浏览器环境中打开
pickerUri
,所以能正常调出 Google Photos 的网页版选择器,并且由于是你的服务器去请求的pickerUri
,已经带上了正确的授权,所以不会再要求用户登录。
代码示例 (客户端, 假设反向代理服务器地址是 https://your-proxy-server.com/photos-picker
):
func loadPhotosPicker() {
guard let url = URL(string: "https://your-proxy-server.com/photos-picker?accessToken=\(accessToken)") else { // 将accessToken传递给你的代理
return
}
let request = URLRequest(url: url)
googleWebView.load(request)
}
代码示例(Node.js 简易反向代理服务器,仅做概念性展示,需要自己根据实际情况完善):
const express = require('express');
const axios = require('axios');
const app = express();
app.get('/photos-picker', async (req, res) => {
const accessToken = req.query.accessToken;
if (!accessToken) {
return res.status(400).send('Missing access token');
}
try{
// 1. Create the session by calling REST endpoint
const sessionResponse = await axios.post('https://photos.googleapis.com/v1/sessions',
{},
{
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
}
});
//2. Get the pickerURI
const pickerUri = sessionResponse.data.pickerUri
//3. Create HTML file with pickerUri
const html = `
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script>
window.location.href = "${pickerUri}";
</script>
</body>
</html>
`;
res.send(html);
}catch(ex){
res.status(500).send(`Error creating the session: ${ex.message}`);
}
});
app.listen(3000, () => {
console.log('Proxy server listening on port 3000');
});
优点:
- 能绕过 iOS 上直接使用
pickerUri
的问题。 - 能保证用户不需要重复登录。
缺点:
- 需要自己搭服务器,增加开发和维护成本。
- 稍微复杂了点。
- 用户体验可能比原生的略差。
安全建议:
- 反向代理服务器必须做好安全防护,防止被攻击。
- access token 要妥善保管,避免泄露。建议使用 HTTPS。
- 通信需要做加密。
方案三:寻找第三方库 (如果存在)
可能有其他开发者已经遇到了同样的问题,并开发了相应的第三方库来解决。可以在 GitHub、CocoaPods 等地方搜索一下,看看有没有现成的解决方案。
优点:
- 省事,不需要从头开发。
缺点:
- 可能没有完全符合需求的库.
- 需要评估第三方库的可靠性和安全性。
方案四:GoogleSignIn + Google Drive API + 自定义 Picker
如果你有使用 GoogleSignIn
, 并有读取Google Drive
的权限,那么就可以利用这个优势来获取用户的google photo:
- 通过GoogleSignIn获取access Token
- 调用 Google Drive API, 利用查询参数来检索在 Google Photos 的照片。比如
q=mimeType contains 'image/' and 'me' in owners and trashed=false
优点:
- 利用既有权限。
缺点:
- 需要有
Google Drive API
的相关权限。 - 不能使用原生的
google photo picker
。
代码示例 (假设已有google access token):
func listGooglePhotos() {
let url = URL(string: "https://www.googleapis.com/drive/v3/files?q=mimeType contains 'image/' and 'me' in owners and trashed=false&fields=files(id,name,thumbnailLink,webContentLink)")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
//handle data...
}
task.resume()
}
thumbnailLink
: 图片缩略图.webContentLink
: 直接下载链接.- 安全提示: 你必须申请
https://www.googleapis.com/auth/drive.readonly
orhttps://www.googleapis.com/auth/drive
scope
方案五: 换用其他云存储服务 (下策)
如果实在搞不定 Google Photos 的集成,又不想自己搭服务器,可以考虑换用其他云存储服务,比如 Dropbox、OneDrive 等。这些服务可能提供了更方便的 iOS 集成方案。
优点:
- 一劳永逸,避免了 Google Photos API 变更带来的麻烦。
缺点:
- 可能需要迁移用户的数据,比较麻烦。
- 用户可能不习惯用其他服务。
总结
就目前来看,方案二(反向代理 + 网页内嵌选择)或者方案四 (Google Drive API + 自定义 Picker) 可能是比较靠谱的。方案一虽然简单,但是不可持续。
这事儿确实挺坑的,Google 没给 iOS 开发者提供一个方便的 Picker API 集成方案,只能自己想办法绕。 希望 Google 以后能改进一下。