在做海外项目中,经常需要接入Facebook SDK ,现将CocosCreator Android 平台 Facebook 登录的接入流程记录下来,以备有需要的朋友做参考。
一、准备工作
1、首先在facebook 开发者平台 注册账号,创建app,获取app的应用编号。
怎么注册账号,怎么创建app ,这里就不再细说。
app的应用编号和密钥,如下所示:
2、设置应用密钥散列
生成密钥散列有2种方式:
(1)、通过代码获取
//获取facebook所需的密钥散列
try {
PackageInfo info = getPackageManager().getPackageInfo("com.x.x", PackageManager.GET_SIGNATURES);
for (Signature signature : info.signatures) {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
showLog("Base64 hash------" + Base64.encodeToString(md.digest(), Base64.DEFAULT));
}
} catch (PackageManager.NameNotFoundException e) {
} catch (NoSuchAlgorithmException e) {
}
(2)、通过工具进行获取
第一步,通过命令行的方式获取到证书指纹SHA1
发布证书指纹获取方式:
keytool -exportcert -alias YOUR_RELEASE_KEY_ALIAS -keystore YOUR_RELEASE_KEY_PATH | openssl sha1 -binary | openssl base64
开发证书指纹获取方式:
keytool -exportcert -alias androiddebugkey -keystore "C:\Users\USERNAME\.android\debug.keystore" | "PATH_TO_OPENSSL_LIBRARY\bin\openssl" sha1 -binary | "PATH_TO_OPENSSL_LIBRARY\bin\openssl" base64
证书指纹示例:
SHA1: 9D:2F:E0:3C:8E:DF:ED:8C:2B:28:88:92:60:A3:82:63:81:4F:3D:9D
第二步,使用工具,将SHA1 证书指纹转化为密钥散列
在线工具:http://tomeko.net/online_tools/hex_to_base64.php
详细使用步骤,请查看根据 sha-1 证书值获取 Facebook 的登录需要使用的散列值
密钥散列可填写多个:
3、添加平台
这里以添加 google play 平台为例进行说明。
(1)、点击下方“添加平台” 按钮 ,在弹出的二级页面依次选择 android ->google play 。
(2)、设置软件包名,包名就是项目包名 com.xxx.xxx。
(3)、设置类名,类名里面填写启动activity 全路径,例如:org.cocos2dx.javascript.AppActivity。
3、创建测试账号,修改密码
二、代码部分调整
1、在项目层 build.gradle ,引入facebook 最新版本sdk。
implementation 'com.facebook.android:facebook-login:latest.release'
implementation 'com.facebook.android:facebook-android-sdk:latest.release'
2、AndroidManifest.xml 配置
(1)、添加网络访问权限,如果已经开启,则忽略。
<uses-permission android:name="android.permission.INTERNET"/>
(2)、在 application 添加 meta-data 元素 和针对 Facebook 的 activity 元素。
<!-- SDK Facebook start! -->
<meta-data android:name="com.facebook.sdk.ApplicationId"
android:value="@string/facebook_app_id"/>
<activity android:name="com.facebook.FacebookActivity"
android:configChanges= "keyboard|keyboardHidden|screenLayout|screenSize|orientation"
android:label="@string/app_name" />
<!-- SDK Facebook end! -->
(3)、在 application 启动activity 元素添加 scheme
<activity
android:name="org.cocos2dx.javascript.AppActivity"
android:screenOrientation="sensorPortrait"
android:configChanges="orientation|keyboardHidden|screenSize|screenLayout"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:launchMode="singleTask"
android:exported="true"
android:taskAffinity="" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/fb_login_protocol_scheme" />
</intent-filter>
</activity>
3、res/values/strings.xml 配置
<string name="facebook_app_id">285717330186354</string>
<string name="fb_login_protocol_scheme">fb285717330186356</string>
facebook_app_id :app的应用编号
fb_login_protocol_scheme:fb+app的应用编号
4、登录调用、回调通知、用户信息获取简单封装
关于Facebook 登录,官方提供了 LoginButton 和LoginManager 两种方式。
不过我们经常使用自己的View 来显示 ,因此这里采用 LoginManager 的方式实现。
详细请查看以下代码:
package org.cocos2dx.javascript.tools;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.facebook.AccessToken;
import com.facebook.CallbackManager;
import com.facebook.FacebookCallback;
import com.facebook.FacebookException;
import com.facebook.FacebookRequestError;
import com.facebook.GraphRequest;
import com.facebook.GraphResponse;
import com.facebook.HttpMethod;
import com.facebook.login.LoginManager;
import com.facebook.login.LoginResult;
import org.cocos2dx.javascript.Native;
import org.json.JSONException;
import org.json.JSONObject;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
public class FaceBookUtils {
private CallbackManager m_callbackManager =null;
private String m_actTag ="FaceBookUtils";
private String m_facebookLoginCallBack="";
private Activity m_activity=null;
private static FaceBookUtils g_Instace = null;
public static FaceBookUtils getInstance() {
if (null == g_Instace) {
g_Instace = new FaceBookUtils();
}
return g_Instace;
}
public void loginFacebook(final String callback){
m_facebookLoginCallBack = callback;
LoginManager.getInstance().logInWithReadPermissions(m_activity, Arrays.asList("public_profile"));
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
m_callbackManager.onActivityResult(requestCode,resultCode,data);
}
public void getUserFacebookBasicInfo(Map<String,String> inmap) {
final String id = inmap.get("id");
final String token = inmap.get("token");
final String app = inmap.get("app");
// 获取基本文本信息
Log.d(m_actTag, "准备获取facebook用户基本信息");
GraphRequest request = GraphRequest.newMeRequest(AccessToken.getCurrentAccessToken(), new GraphRequest.GraphJSONObjectCallback() {
@Override
public void onCompleted(JSONObject object, GraphResponse response) {
if (response == null) {
Log.d(m_actTag, "无法获取fb用户基本信息");
return;
}
Log.d(m_actTag, "获取fb用户基本信息完毕,object是" + object);
JSONObject responseJsonObject = response.getJSONObject();
Log.d(m_actTag, "而response 的object是" + responseJsonObject);//这两个jsonObject是一样的
if (responseJsonObject == null) {
Log.d(m_actTag, "无法获取fb用户基本信息" + response.getError().getErrorType() + " " + response.getError().getErrorMessage());
return;
}
Map<String,String> map = new HashMap<String, String>();
map.put("result", "11");
map.put("info", "fb info success");
map.put("id", id);
map.put("token", token);
map.put("appid", app);
map.put("firstName", getFacebookGraphResponseString(responseJsonObject, "first_name"));
map.put("lastName", getFacebookGraphResponseString(responseJsonObject, "last_name"));
map.put("userName", getFacebookGraphResponseString(responseJsonObject, "name"));
// map.put("birthday", getFacebookGraphResponseString(responseJsonObject, "birthday"));
//map.put("updateTime", getFacebookGraphResponseString(responseJsonObject, "updated_time"));
// map.put("email", getFacebookGraphResponseString(responseJsonObject, "email"));
// map.put("gender", getFacebookGraphResponseString(responseJsonObject, "gender"));
//获取用户头像 (小)
//JSONObject object_pic = object.optJSONObject( "picture" ) ;
//JSONObject object_data = object_pic.optJSONObject( "data" ) ;
//String photo = object_data.optString( "url" ) ;
//map.put("head", photo);
Native.nativeToLogic(m_facebookLoginCallBack,map);
}
});
Bundle parameters = new Bundle();
// parameters.putString("fields", "id,name,link,email,first_name,last_name,gender,picture,locale,timezone,updated_time,verified");
parameters.putString("fields", "id,name,first_name,last_name");
request.setParameters(parameters);
request.executeAsync();
}
public String getFacebookGraphResponseString(JSONObject graphResponse, String flag) {
String value = "";
try {
value = graphResponse.getString(flag);
} catch (JSONException e) {
e.printStackTrace();
}
Log.d(m_actTag, "getFacebookInfo flag="+flag+" result="+value);
return value;
}
public void getFacebookUserPictureAsync(String facebookUserId) {
Log.d(m_actTag, "getFacebookUserPictureAsync");
Bundle parameters = new Bundle();
parameters.putBoolean("redirect", false);
parameters.putString("height", "200");
parameters.putString("type", "normal");
parameters.putString("width", "200");
GraphRequest graphRequest= new GraphRequest(AccessToken.getCurrentAccessToken(), "/" + facebookUserId + "/picture", parameters, HttpMethod.GET, new GraphRequest.Callback() {
public void onCompleted(GraphResponse response) {
if (response == null) {
Log.d(m_actTag, "get facebook photo fail");
return;
}
if (response.getError() != null) {
FacebookRequestError facebookRequestError = response.getError();
Log.d(m_actTag, "get facebook photo fail 2::" + facebookRequestError.getErrorMessage());
return;
}
JSONObject responseJsonObject = response.getJSONObject();
if (responseJsonObject == null) {
Log.d(m_actTag, "get facebook photo fail 3");
return;
}
Log.d(m_actTag, "facebook photo info:" + responseJsonObject.toString());
String avatarUrl = "";
try {
JSONObject dataJsonObject = responseJsonObject.getJSONObject("data");
avatarUrl = dataJsonObject.getString("url");
//分割出参数部分
// String[] sourceStrArray = avatarUrl.split("\\?");
avatarUrl = URLEncoder.encode(avatarUrl, "UTF-8");
Log.d(m_actTag, "facebook photo avatarUrl:" + avatarUrl);
Map<String,String> map = new HashMap<String, String>();
map.put("result", "12");
map.put("info", "fb head success");
map.put("head", avatarUrl);
map.put("height", dataJsonObject.getString("height"));
map.put("width", dataJsonObject.getString("width"));
map.put("isSilhouette", dataJsonObject.getString("is_silhouette"));
Native.nativeToLogic(m_facebookLoginCallBack,map);
} catch (Exception e) {
Log.d(m_actTag, "get facebook photo fail 4"+e.getStackTrace().toString());
}
}
}
);
Log.d(m_actTag, "getFacebookUserPictureAsync version:"+graphRequest.getVersion()+"");
graphRequest.executeAsync();
}
public void initSDK( final Activity activity){
m_activity = activity;
m_callbackManager = CallbackManager.Factory.create();
Log.d("fb login","initSDK");
LoginManager.getInstance().registerCallback(m_callbackManager, new FacebookCallback<LoginResult>() {
@Override
public void onSuccess(LoginResult loginResult) {
Log.e(m_actTag, "facebook login success: " + loginResult.getAccessToken().getToken());
Map<String,String> map = new HashMap<String, String>();
map.put("result", "1");
map.put("info", "fb login success");
map.put("id", loginResult.getAccessToken().getUserId());
map.put("token", loginResult.getAccessToken().getToken());
map.put("app", loginResult.getAccessToken().getApplicationId());
Native.nativeToLogic(m_facebookLoginCallBack,map);
Log.d("fb login","success!!!!!");
//紧接着获取用户信息
getUserFacebookBasicInfo(map);
// getFacebookUserPictureAsync(loginResult.getAccessToken().getUserId());
}
@Override
public void onCancel() {
Log.e(m_actTag, "facebook login cancel");
Map<String,String> map = new HashMap<String, String>();
map.put("result", "2");
map.put("info", "user cancel");
Log.d("user","cancel!!!!!");
Native.nativeToLogic(m_facebookLoginCallBack,map);
}
@Override
public void onError(FacebookException error) {
Log.e(m_actTag, "facebook login error:" + error.toString());
Map<String,String> map = new HashMap<String, String>();
map.put("result", "3");
map.put("info", error.toString());
Log.d("facebook login","error!!!!!");
Native.nativeToLogic(m_facebookLoginCallBack,map);
}
});
}
}
5、AppActivity 中使用 FaceBookUtils 封装类
(1)、onCreate 时初始化
FaceBookUtils.getInstance().initSDK(this);
(2)、设置回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
FaceBookUtils.getInstance().onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
(3)、登录接口导出
public static void loginFacebook(final String callback) {
FaceBookUtils.getInstance().loginFacebook(callback);
}
6、js 层 对java 的 Native 封装
private static CallBackKey: string = "nativeCallback";
private static _callBackPrefix: string; //回调集合字符前缀
private static _prevErr = ""
private static _hasInited = false;
public static init() {
if (cc.sys.isBrowser) {
return
}
if (this._hasInited) {
return
}
this._hasInited = true;
window[this.CallBackKey] = {}; //回调函数集合
this._callBackPrefix = "window." + this.CallBackKey + ".";
//捕获异常
if (cc.sys.isNative) {
window.__errorHandler = function (file: any, line: any, error: any, stack: any) {
if (this._prevErr != error + line) {
stack = String(stack)
console.log("========== GOT JS/TS ERROR ==========")
console.log("file:" + file, "line:" + line, "error:" + error)
console.log(stack)
this._prevErr = error + line;
}
}
}
}
// 登陆fb Native.loginFacebook((ret:any)=>{cc.log(ret)},"(%s)")
public static loginFacebook(func: any,funcParams:string): void {
if (cc.sys.isNative) {
let cbKey: string = "loginFacebook"
window[this.CallBackKey][cbKey] = func
let ret = 0;
if (cc.sys.os == cc.sys.OS_ANDROID) {
ret = jsb.reflection.callStaticMethod("org/cocos2dx/javascript/AppActivity", "loginFacebook", "(Ljava/lang/String;)V", this._callBackPrefix + cbKey + funcParams);
}
}
}
7、js 层调用Native. loginFacebook
getPlayerInfoBySdk(){
let self = this;
Native.loginFacebook((ret: any) => {
self.facebookSdkBack(ret)
},"(%s)")
}
//请求SDK返回
facebookSdkBack(backInfo:any){
if(backInfo){
if(backInfo.result == "2"){//用户取消FB登录
UIHelper.UICenterNotice(backInfo.info)
}else if(backInfo.result == "3"){//FB登录失败
UIHelper.UICenterNotice(backInfo.info)
}else if(backInfo.result == "11"){//FB登录成功并返回用户数据
this.checkFbToken(backInfo);
}else if(backInfo.result == "12"){//FB登录成功并返回头像信息
}
}
}
三、测试登录
需要注意2点:
(1)、使用的打包证书需要与facebook 开发者平台生成密钥散列证书一致。
(2)、需要使用 facebook 测试账号进行登录测试。