1、写在前面的话
作为一个接过很多国内外sdk的同学,我深深的表示Facebook特别难接。包括它的接口、文档、示例demo都有很多的坑,真的非常痛苦。而且Google搜索的结果也能看出国外同行的不容易,而且问题的回答少之又少。
经过不断的摸索和几乎阅读遍了所有的英文文档,也阅读了facebook源码才跑通了facebook的登录、分享、邀请功能。这里我总结和分享一下应用出海第一步facebook的接入经验。
2、Facebook接入前准备
2.1、应用信息配置
Facebook里面创建的应用一个applicationId可以对应多个平台(包括:android、ios、网页端等),其中对于单个android平台,可以对应多个包名和签名。整体的关系如下:
添加单个平台时需要提供包名、密钥散列、类名(深度链接和facebook邀请通知拉起需要)。下面是获取密钥散列的方法:
1 |
keytool -exportcert -alias <RELEASE_KEY_ALIAS> -keystore <RELEASE_KEY_PATH> | openssl sha1 -binary | openssl base64 |
2.2、添加测试账号
这一步非常重要哦,在实际的调试过程中会遇到各种奇奇怪怪的问题,如果你用的测试账号会减少很多这方面带来的烦恼。
2.3、facebook调试工具
2.3.1、GraphRequest调试
工具链接:链接
facebook提供了图谱API,图谱 API 是存取 Facebook 开放平台数据的主要方式。它是一种基于 HTTP 的 API,应用可使用它以编程方式查询数据、发布新动态、管理广告、上传照片和执行各种其他任务。
相关的官方介绍链接:链接
2.3.2、自动生成访问口令
工具链接:链接
这个工具会自动生成您的应用对应的测试用的用户口令access_token,应用口令app_token,方便您快速本地调试
3、Facebook授权登录接入
打开developer.facebook.com进入facebook管理后台,在页面的左上角选择您要打开的应用,在左边菜单栏的“PRODUCTS”添加产品,搜索“Facebook Login”并添加该产品。此时左侧的PRODUCTS栏目会出现”Facebook Login”,点击展开再点击“Quickstart”,此时会有各个平台的详细接入流程:
详细的接入过程这里不做介绍,因为facebook这里还是描述的非常清楚的。我这里补充一张facebook的登录交互时序图:
整体包含4个步骤:
- 调用facebook登录接口logInWithReadPermissions(有些情况会需要用到logInWithPublishPermissions)
- 注册监听登录的回调结果
- 回调facebook的onActivityResult方法
- 接收登录成功回调。此时你可以获取用户的userId,access_token信息等
获取用户的基本信息需要用到GraphRequest类,代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/** * Get user facebook profile. * * @param loginResult login result with user credentials. */ private void getUserProfile(LoginResult loginResult) { AccessToken accessToken = loginResult.getAccessToken(); final String tokenStr; final String userId; if(accessToken != null && !accessToken.isExpired()) { tokenStr = accessToken.getToken(); userId = accessToken.getUserId(); } else { mListener.onAuthFailed(mAccountType, LoginConstants.ErrMsg.AUTH_FAILED, LoginConstants.ErrorCode.ERR_CODE_RETURN_DATA_INVALID); return; } // App code GraphRequest request = GraphRequest.newMeRequest( loginResult.getAccessToken(), new GraphRequest.GraphJSONObjectCallback() { @Override public void onCompleted(JSONObject object, GraphResponse response) { LogUtil.i("response: ", response + ""); Activity activity = mActivityRef.get(); if(activity == null) { mListener.onAuthFailed(mAccountType, LoginConstants.ErrMsg.AUTH_FAILED, LoginConstants.ErrorCode.ERR_CODE_ACTIVITY_IS_NULL); return; } try { mListener.onAuthSuccess(activity, parseResponse(tokenStr, userId, object)); } catch (Exception e) { e.printStackTrace(); mListener.onAuthFailed(mAccountType, e.getMessage(), LoginConstants.ErrorCode.ERR_CODE_RETURN_DATA_INVALID); } } }); Bundle parameters = new Bundle(); parameters.putString("fields", "name,gender,picture"); request.setParameters(parameters); request.executeAsync(); } |
3.1、Facebook的LoginBehavior
facebook的 LoginBehavior 是个枚举类型,里面设置了7种默认的登录方式,默认的登录方式为:NATIVE_WITH_FALLBACK。这种登录方式是优先尝试用facebook app登录,如果失败则回到web对话框授权登录。
下面列举了几种常见的登录方式的描述:
- NATIVE_WITH_FALLBACK:指定登录应尝试使用Facebook App登录,如果不起作用则返回到Web对话框auth。 这是默认行为。
- WEB_ONLY :指定仅应使用Web对话框auth。此时是打开了系统的浏览器打开登录界面
- WEB_VIEW_ONLY:指定仅应使用WebView对话框auth。弹出webview对话框授权登录
3.2、Facebook的几种访问口令
当用户使用 Facebook 登录连接应用并批准应用的权限请求时,应用将能够获得访问口令,用于临时安全访问 Facebook API。
访问口令是识别用户、应用或主页的非明文字符串,应用可用它来执行图谱 API 调用。访问口令可通过多种方法获得,后文中将依次说明。口令包含关于口令过期时间和哪个应用生成口令的信息。由于隐私设置检查,Facebook 上的大多数 API 调用需要包含访问口令。不同的访问口令有不同的用途:
访问口令类型 | 描述 |
---|---|
用户访问口令 | 用户口令是最常使用的口令类型。应用任何时候以特定用户身份调用 API 读取、修改或写入该用户的 Facebook 数据时,都需要此类访问口令。用户访问口令通常通过“登录”对话框获得,并需要用户允许应用获取。 |
应用访问口令 | 此类访问口令用于修改和读取应用设置,也可用于发布开放图谱操作。它使用应用与 Facebook 之间的预定义密钥生成,之后用于执行更改整个应用设置的调用。您可以通过服务器到服务器的调用获得应用访问口令。 |
主页访问口令 | 这些访问口令与用户访问口令类似,不同之处在于它们向读取、写入或修改 Facebook 主页数据的 API 授予权限。要获取主页访问口令,您需要先获取用户访问口令,并请求 manage_pages 权限。获得用户访问口令后,您可以通过图谱 API 获得主页访问口令。 |
客户端口令 | 客户端口令是可以嵌入原生移动二进制文件或桌面应用以识别应用的标识符。客户端口令不是密钥标识符,因为其是嵌入应用中的。它用于访问应用层 API,但只能访问非常有限的子集。客户端口令可在应用面板中找到。由于客户端口令使用极少,本文不再详述。使用客户端口令的 API 文档中会有所涉及。 |
3.2.1、短期口令和长期口令
用户访问口令分为两种形式:短期有效口令和长期有效口令。短期有效口令的有效期通常为一两小时,长期有效口令的有效期通常为 60 天左右。您不应假设这些有效期会维持不变;有效期或会在不发出警告的情况下更改,也有可能提前到期。详情请参阅处理错误。
通过网页登录生成的访问口令是短期有效口令,但您可以通过使用应用密钥执行服务器端 API 调用将它们转换为长期有效口令。
使用 Facebook iOS 和 Android SDK 的移动应用默认获得长期有效口令。
通过长期有效口令享有 Facebook 市场营销 API 标准访问权限的应用将收到没有过期时间的长期有效口令。这些口令仍然可能因为其他原因失效,但不会仅因为时间而过期。商务管理平台中的系统用户访问口令也是如此。
3.2.2、应用访问口令
应用访问口令用于代表应用而非用户请求 Facebook API。它可以用于修改应用参数、创建和管理测试用户,或者读取应用的成效分析。
限制
对于使用用户访问口令发出请求的应用通常可以看见的一些用户数据,在使用应用访问口令时有时会看不见。如果您是读取用户数据并在应用中使用,应使用的是用户访问口令而不是应用访问口令。
如果在应用面板的高级设置中将应用设置为 Native/Desktop
,应用访问口令将被视为不安全,因此无法用于调用 API。这是因为我们假定原生或桌面应用会在内部某处嵌入应用密钥(因此使用该密钥生成的应用访问口令不安全)。
生成应用访问口令
要生成应用访问口令,您需要调用图谱 API:
1 2 3 4 |
curl -X GET "https://graph.facebook.com/oauth/access_token ?client_id=your-app-id &client_secret=your-app-secret &grant_type=client_credentials" |
3.2.3、查询access_token拥有的权限
可以使用下面这个graphrequest
1 |
https://graph.facebook.com/me/permissions?access_token=xxxx |
使用2.1.1节介绍的GraphRequest调试工具可以获取某个access_token已获得的权限,例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
{ "data": [ { "permission": "user_friends", "status": "granted" }, { "permission": "public_profile", "status": "granted" } ] } |
4、Facebook applink 深度链接接入
4.1、交互流程图
官方文档链接:链接
深度链接的关键是分享的页面Header头里面配置的metadata信息。
如果某个人从您的应用分享了内容到Facebook app,其他人点击这个内容,内容的链接需要包含applink的metadata,这样facebook会按照上面的流程拉起应用或者googleplay商店。Android和IOS平台的metadata协议请参考:链接
这里介绍几个关键的metadata属性和他们的作用:
1、 <meta property=”al:web:should_fallback” content=”false” />
对应上面流程图的MobileOnly检查,如果设置为false,就会根据meta配置的al:android:app_name和al:android:package查找对应的游戏。注意,如果app_name和googleplay 商店上架的游戏不一致也是无法拉起googleplay应用市场的。
2、og:image meta 属性
该属性为分享链接时展示的图片内容。由于我们用代码分享一个深度链接时Facebook是不支持传入图片和标题等内容的,facebook只会从该链接的og:image 和og:title meta-tag读取图片和标题信息来展示的。facebook 的ShareLinkContent代码如下:
1 2 3 |
ShareLinkContent content = new ShareLinkContent.Builder() .setContentUrl(Uri.parse("http://www.huahuaxie.com?test_key1=test_value1")) .build(); |
注意,facebook对og:image设置的图片有以下限制:
- 允许的最小图像尺寸为200 x 200像素。
- 图像文件的大小不得超过8 MB。
- 使用至少1200 x 630像素的图像,以便在高分辨率设备上获得最佳显示效果。 至少,您应该使用600 x 315像素的图像来显示包含较大图像的链接页面帖子。
- 如果您的图像小于600 x 315像素,它仍会显示在链接页面中,但尺寸会小得多。
- 我们还重新设计了链接页面帖子,以便在桌面和移动新闻Feed中图像的宽高比相同。 尽量使图像尽可能接近1.91:1的宽高比,以便在没有任何裁剪的情况下在新闻Feed中显示完整图像。
- 我们的抓取工具只接受gzip和deflate编码,因此请确保您的服务器使用正确的编码。
4.2、接入遇到的问题
从链接点击到google-play应用市场下载、安装、启动应用后拉起参数丢失。使用fechDeferredAppLinkData也没有效果,因为fechDeferredAppLinkData是给广告使用的,深度链接使用无效。
4.3、测试深度链接和更新缓存
Facebook对深度链接的解析是有缓存的,所以您需要记得在每次更新了您链接的meta-data数据后更新一下缓存。更新facebook抓取信息的链接:https://developers.facebook.com/tools/debug/og/object/
4.4、Facebook app invite 已经弃用
随着Facebook SDK版本4.28.0的发布,App Invites已被弃用。 它将被支持到2018年2月5日。
详细描述:https://stackoverflow.com/questions/47196972/facebook-app-invites-is-deprecated
5、GameRequest 接入
5.1、交互流程图
5.2、接入前准备
1、添加App Center
a) 在facebook管理后台的“PRODUCTS”里面搜索和添加”App Center“产品。在应用中登录facebook后过一段时间后该应用会出现在https://www.facebook.com/settings?tab=applications
b) 设置appcenter支持的平台,如下图所示:
2、设置应用的类别为游戏
如下图所示:
facebook游戏服务详细介绍参考这里:链接
3、勾选深度链接设置
facebook后台在应用设置>基础>Android>钩上单点登录&深度链接,如下图所示:
同时需要确保应用设置里面的Android平台的Class Name需要设置为待拉起的Activity,一般为游戏的Launcher activity。保证该Activity的包名和类名正确才能被正确拉起。
4、检查Settings>Basic>Display Name和googleplay里面发布应用的app_name是否一致。确保google-play能正常搜索到应用
5、检查接收通知的应用名和在facebook后台配置的应用名是否一致,否则点击邀请通知无法拉起应用
5.3、接入过程中遇到的问题
5.3.1、GameRequestDialog首次即使登录过也弹出登录窗
现象:先拉起facebook授权登录成功,再次点击GameRequest邀请时会再次拉起facebook web登录框。
原因:通过分析源码和调试发现GameRequestDialog只支持web的方式,虽然在拉起游戏邀请页面时传递的url包含access_token信息,且该信息是有效的,但该页面仍然会跳转到facebook login.php页面地址。
原游戏邀请地址:
1 |
https://m.facebook.com/v3.3/dialog/app_requests?redirect_uri=fbconnect%3A%2F%2Fsuccess&display=touch&access_token=EAAcsydZA1CZAQBAJEAygFiaOifu8dTfvQczNyup2vNGS7Lr4NCvkRxP5aqONnw2FL9g8s9iNvYtZC2hL4WLDSv5KKIwpG95ZCcnJF36tZC3wngIbd3dxfYLmv1czizJypXZBL5LbZCEEYrsMNntPa7nO3AWj9Gb2BM3WuRIRXIVkARc2WNY8D6pJs7zpZC4MRp3i0hq8iOxjPo4wZCpZAmLaEKoka9KGmQxycZD&title=Challenge+a+Friend&message=Please+come+play+RPS+with+me%21&client_id=2019570235017111&ret=login&sdk=android-5.0.2#_=_ |
始终跳转的地址为:
1 |
https://m.facebook.com/login.php?skip_api_login=1&api_key=2019570235011111&signed_next=1&next=https%3A%2F%2Fm.facebook.com%2Fv3.3%2Fdialog%2Fapp_requests%3Fredirect_uri%3Dfbconnect%253A%252F%252Fsuccess%26display%3Dtouch%26access_token%3DEAAcsydZA1CZAQBAMIswWkdL015qBh6jbR4MU844w84lSgvmfYc5beBys2nj7DFWH2ogNKbHAMLGETM65BR7idNPSw2LVyhhzpuPl7LQrcD9rOIMM2u3A1a5knXgHvEo4yfcDfAZABk8ctTMsSvYTFRYmq0xULZCAXOxiX4DCbLgCpxeFVK3dKfIgjYsQkckZBGEZBBHSxttZBM1hZAdESjknvtmNCoCYbft1tluv0mxg4gZDZD%26title%3DChallenge%2Ba%2BFriend%26message%3DPlease%2Bcome%2Bplay%2BRPS%2Bwith%2Bme%2521%26client_id%3D2019570235017620%26ret%3Dlogin%26sdk%3Dandroid-5.0.2&cancel_url=fbconnect%3A%2F%2Fsuccess%3Ferror_code%3D4201%26error_message%3DUser%2Bcanceled%2Bthe%2BDialog%2Bflow&display=touch&locale=zh_CN&_rdr |
解决方法:
在调用facebook登录授权前设置LoginBehavior为LoginBehavior.WEB_VIEW_ONLY
1 2 |
LoginManager.getInstance().setLoginBehavior(LoginBehavior.WEB_VIEW_ONLY); LoginManager.getInstance().logInWithReadPermissions(activity, permissions); |
GameRequest的调用时序图如下:
5.4、GameRequest 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//生成GameRequestContent GameRequestContent gameRequestContent = new GameRequestContent.Builder() .setMessage(message) .setTitle(title) .setData(extraData) .build(); //发起游戏邀请 GameRequestDialog requestDialog = new GameRequestDialog(activity); if(requestDialog.canShow(gameRequestContent)) { requestDialog.registerCallback(mCallbackManager, facebookCallback); requestDialog.show(gameRequestContent); } else { facebookCallback.onError(new FacebookException("facebook game request can not show the content!")); } |
GameRequest邀请成功后会返回被邀请对象的facebook_id数组,当用户点击通知接收邀请时会拉起App,此时facebook会传入拉起信息到intent给游戏,其中我们关心的是传入的Uri对象,它的格式如下:
1 |
https://m.facebook.com/appcenter/2019570235011111?fbs=1001&request_ids=2317762114971372&ref=notif&app_request_type=user_to_user |
我们需要做的是获取request_ids的值,并请求GraphRequest获得发起邀请来源的facebook_id,整体流程如下:
我们通过GET请求获取该request_id的信息,
1 |
GET https://graph.facebook.com/<REQUEST_OBJECT_ID>?access_token=USER_ACCESS_TOKEN |
返回信息如下:
其中from下面就是邀请者的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ "application":{ "category":"Games", "id":"2019570235017623", "link":"https:\/\/www.facebook.com\/games\/?fbs=-1&app_id=2019570235011111", "name":"ejoy_s3_game_demo" }, "created_time":"2019-06-17T07:53:01+0000", "data":"This is invite extra data!", "from":{ "id":"636188516793352", "name":"Ejoy Sean" }, "id":"343059016380397", "message":"This is invite Message", "to":{ "id":"456072975149653", "name":"Gang Pan" } } |
6、Facebook分享接入
关于facebook分享,facebook官方的这篇文章已经介绍的非常详细了,这里不再赘述。
这里主要介绍在使用分享接口时的几个注意点:
- 分享到timeline只能是图片或者链接,不显示标题和文字
- 分享到timeline的图片只能是本地的,不能是远端图片地址。可以将远端图片下载到本地再分享。
- 分享到timeline 如果分享媒体则需要使用SharePhotoContent(注意不要使用ShareMediaContent,因为SharePhoto可以在没有facebook app的情况下拉起web分享。而ShareMediaContent只能facebook app分享),如果分享链接需要使用ShareLinkContent
- 无论分享到timeline还是messenger,媒体和链接只能同时存在一种,不能同时存在
- 如果分享到messenger,图片只能传网络地址,参考链接:链接,messenger暂不支持本地图片。
6.1、分享到timeline代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private static ShareContent parseTimelineShareContent(JSONObject params, byte[] allImageChunkData) { ShareContent shareContent = null; JSONArray mediaArr = params.optJSONArray(KEY_MEDIA); int mediaSize = mediaArr != null ? mediaArr.length() : 0; //facebook media 和 文字链接不能同时添加,优先取媒体信息 if(mediaSize > 0) { SharePhotoContent.Builder shareContentBuilder = new SharePhotoContent.Builder(); for(int i=0; i< mediaSize; i++) { JSONObject mediaObj = mediaArr.optJSONObject(i); SharePhoto sharePhoto = parseShareMedia(mediaObj, allImageChunkData); if(sharePhoto != null) { shareContentBuilder.addPhoto(sharePhoto); } else { LogUtil.e(TAG, "share media parse error:"+mediaObj); } shareContent = shareContentBuilder.build(); } } else { String contentUrl = params.optString(KEY_CONTENT_URL); if(TextUtils.isEmpty(contentUrl)) { LogUtil.e(TAG, "content url is empty!"); } else { shareContent = new ShareLinkContent.Builder() .setContentUrl(Uri.parse(contentUrl)) .build(); } } return shareContent; } |
6.2、分享到messenger代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
public static ShareContent parseMessengerShareContent(JSONObject params) { String title = params.optString(KEY_TITLE); String message = params.optString(KEY_MESSAGE); String contentUrl = params.optString(KEY_CONTENT_URL); ShareMessengerGenericTemplateElement.Builder elementBuilder = new ShareMessengerGenericTemplateElement.Builder() .setTitle(title) .setSubtitle(message); JSONArray mediaArr = params.optJSONArray(KEY_MEDIA); int mediaSize = mediaArr != null ? mediaArr.length() : 0; //messenger 分享,facebook media 和 文字链接不能同时添加,优先取媒体信息 //messenger 分享只能放一张图片, 我们只取第一张是image_url类型的图片 boolean hasValidMedia = false; if(mediaSize > 0) { for(int i=0; i<mediaSize; i++) { JSONObject mediaObj = mediaArr.optJSONObject(i); if(mediaObj != null) { String mediaType = mediaObj.optString(KEY_MEDIA_TYPE); //messenger分享只能放图片网址,不能放本地图片 if(MEDIA_TYPE_IMAGE_URL.equals(mediaType)) { String mediaUrl = mediaObj.optString(KEY_MEDIA_DATA); elementBuilder.setImageUrl(Uri.parse(mediaUrl)); hasValidMedia = true; //find one media and break break; } } } } ShareContent shareContent = null; if(hasValidMedia) { ShareMessengerGenericTemplateContent.Builder contentBuilder = new ShareMessengerGenericTemplateContent.Builder() .setPageId("1") .setGenericTemplateElement(elementBuilder.build()); shareContent = contentBuilder.build(); } else if (!TextUtils.isEmpty(contentUrl)) { shareContent = new ShareLinkContent.Builder() .setContentUrl(Uri.parse(contentUrl)) .build(); } return shareContent; } |
7、接入问题汇总
7.1、google getIdToken 始终返回旧token,导致服务端在Google校验返回Invalid token
1 2 3 |
GoogleSignInAccount acct = result.getSignInAccount(); String token = acct.getIdToken(); long expireTime = acct.getExpirationTimeSecs(); |
问题描述:如果手机的系统时间被手动改成过去时间(例如1小时前),那么google sdk始终返回之前登录成功的旧token,不会刷新
问题原因:Google的token有效时间为1个小时。上面的代码返回的expireTime为失效时间,该时间为服务端的UTC时间戳,如果本地的系统时间修改了(改成了过去时间,例如1个小时前)那么Google的getIdToken返回的token值也会是旧的,因为它认为token未失效。
google id token和 access token的校验方法:
1 2 |
'https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=xxx'; 'https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=xxx'; |
如果token校验失效,则返回以下内容
1 2 3 4 |
{ "error": "invalid_token", "error_description": "Invalid Value" } |
Google 官方回复:
https://github.com/firebase/FirebaseUI-Android/issues/855
google的回复是不会修复这个问题,他们认为这种是正常的现象。
解决方案:
如果出现这种token校验失败的错误时,需要有一种方法能拿到正确的服务端,并能判断本地时间是否已经改动,如果改动需要提示用户检查一下手机的系统时间,来尝试修复这个问题
设置为WEB_VIEW_ONLY后 手机上有app登录过了 但还是要输入账号密码 求联系方式解决一下
Android出海之Facebook登录、分享、游戏邀请
vxnqqkp http://www.g91fdo2v04j6bcd33g3n1r680nb3b2r2s.org/
avxnqqkp
[url=http://www.g91fdo2v04j6bcd33g3n1r680nb3b2r2s.org/]uvxnqqkp[/url]