Delphi Firemonkey中通过Intents对接Dropbox API的OAuth回调问题
我来帮你搞定Delphi Android上Dropbox OAuth从TWebBrowser切换到系统浏览器的问题!你遇到的「只能收到Cancelled消息」大概率是回调配置没做对,咱们一步步把两个核心问题(获取返回数据、处理浏览器回调)解决掉:
一、先搞定AndroidManifest的回调配置(关键!)
首先得让系统知道你的应用能处理Dropbox授权后的回调URL,这一步没做好,浏览器根本找不到你的应用,自然只会判定为授权取消。
- 打开Delphi项目里的
AndroidManifest.template.xml文件 - 在主Activity标签(一般是
com.embarcadero.firemonkey.FMXNativeActivity)内添加一个<intent-filter>,比如你的回调URL设为myapp://dropbox-auth(Scheme可以自定义,建议用你的应用包名避免冲突):
<intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <!-- 这里的scheme和host要和你Dropbox应用后台设置的redirect_uri完全一致 --> <data android:scheme="myapp" android:host="dropbox-auth" /> </intent-filter>
- 同步去Dropbox开发者后台,把这个
redirect_uri(比如myapp://dropbox-auth)添加到应用允许的回调地址列表里,不然Dropbox会拒绝跳转请求。
二、用Intent打开系统浏览器发起授权
替换原来TWebBrowser的代码,用系统Intent唤起浏览器打开授权URL,注意redirect_uri要和上面配置的完全匹配:
procedure TfrmmDropBox.OpenOAuthInBrowser; var LIntent: JIntent; LAuthURL: string; begin // 替换成你的Dropbox Client ID,redirect_uri和Manifest里的一致 LAuthURL := 'https://www.dropbox.com/oauth2/authorize?client_id=YOUR_DROPBOX_CLIENT_ID&response_type=code&redirect_uri=myapp://dropbox-auth'; LIntent := TJIntent.JavaClass.init(TJIntent.JavaClass.ACTION_VIEW, TJUri.JavaClass.parse(StringToJString(LAuthURL))); // 可选:指定用Chrome打开,也可以不指定让用户自行选择浏览器 LIntent.setPackage('com.android.chrome'); if TAndroidHelper.Context.getPackageManager.queryIntentActivities(LIntent, 0).size > 0 then TAndroidHelper.Activity.startActivity(LIntent) else ShowMessage('未找到可用浏览器!'); end;
三、接收浏览器回调并获取授权数据
现在要处理浏览器跳回应用时的Intent,从中解析出授权码(code)。这里有两种实现方式:
方法1:重写主Activity(推荐,更稳定)
- 新建一个继承自
TFMXNativeActivity的类:
unit MainActivity; interface uses Androidapi.JNI.App, Androidapi.JNI.Os, Androidapi.Helpers, FMX.Platform.Android; type TMainActivity = class(TFMXNativeActivity) protected procedure OnNewIntent(Intent: JIntent); override; end; implementation uses frmmDropBox, Androidapi.JNI.Net; procedure TMainActivity.OnNewIntent(Intent: JIntent); var LUri: JUri; LCode: string; begin inherited; if Intent.getData <> nil then begin LUri := Intent.getData; // 验证回调URL的Scheme和Host是否匹配 if LUri.getScheme.equals(StringToJString('myapp')) and LUri.getHost.equals(StringToJString('dropbox-auth')) then begin LCode := JStringToString(LUri.getQueryParameter(StringToJString('code'))); if LCode <> '' then // 拿到授权码,去换Access Token frmmDropBox.HandleOAuthCode(LCode) else // 用户主动取消授权 frmmDropBox.HandleOAuthCancelled; end; end; end; initialization // 替换默认的Activity类 RegisterActivity(TMainActivity); end.
- 在你的主窗体中添加对应的处理方法:
procedure TfrmmDropBox.HandleOAuthCode(const ACode: string); begin // 这里调用Dropbox API,用授权码换取Access Token ShowMessage('拿到授权码:' + ACode); end; procedure TfrmmDropBox.HandleOAuthCancelled; begin ShowMessage('授权已取消'); end;
方法2:用消息管理器监听(适合不想重写Activity的场景)
在主窗体的OnCreate事件中添加监听:
uses FMX.Platform, Androidapi.JNI.App; procedure TfrmmDropBox.FormCreate(Sender: TObject); begin TMessageManager.DefaultManager.SubscribeToMessage(TMessageReceivedNotification, procedure(const Sender: TObject; const M: TMessage) var LMsg: TMessageReceivedNotification; LIntent: JIntent; LUri: JUri; LCode: string; begin LMsg := TMessageReceivedNotification(M); if LMsg.Message is JIntent then begin LIntent := TJIntent(LMsg.Message); if LIntent.getData <> nil then begin LUri := LIntent.getData; if LUri.getScheme.equals(StringToJString('myapp')) and LUri.getHost.equals(StringToJString('dropbox-auth')) then begin LCode := JStringToString(LUri.getQueryParameter(StringToJString('code'))); if LCode <> '' then HandleOAuthCode(LCode) else HandleOAuthCancelled; end; end; end; end); end;
四、关于「关闭浏览器」的说明
其实在标准OAuth流程里,授权完成后浏览器会自动跳回你的应用,此时浏览器会留在后台,不需要你手动关闭——用户如果需要可以自行关闭,强行关闭反而会影响体验。你之前只有关闭浏览器才收到Cancelled消息,正是因为回调配置没做好,导致浏览器无法跳回应用,用户只能手动关闭,系统因此判定为授权取消。
排查当前问题的小技巧
- 检查Dropbox后台的
redirect_uri和代码、Manifest里的内容是否完全一致(大小写、符号都不能错) - 确认Manifest里的
<intent-filter>确实添加到了主Activity标签内 - 尽量用真实设备测试(模拟器可能存在回调跳转的兼容性问题)
- 测试时观察授权完成后,浏览器是否会自动唤起你的应用
内容的提问来源于stack exchange,提问作者Parodius




