Android 实现 Facebook 第三方登录

Facebook 的 Android 登录链接,其中包含了很多账号注册信息。这里贴出一些 facebook 登录中重点的代码,申请的 App 相关信息这里就不多介绍,Facebook 此网页支持中文。

项目的 build.gradle 中

repositories {
    mavenCentral()
}

模块下的 build.gradle 中

    implementation 'com.facebook.android:facebook-android-sdk:[4,5)'

按照官网的指示,strings.xml 中填写申请的两个 key

    <string name="facebook_app_id" translatable="false">填写自己申请的facebook_app_id</string>
    <string name="fb_login_protocol_scheme" translatable="false">填写自己申请的fb_login_protocol_scheme</string>

这两个值在 AndroidManifest.xml 中使用,当然需要 INTERNET 权限

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        ...
        >

        <meta-data
            android:name="com.facebook.sdk.ApplicationId"
            android:value="@string/facebook_app_id" />


        <activity
            android:name=".ui.login.UserActivity"
            android:windowSoftInputMode="stateVisible|adjustResize">
            <intent-filter>
                <data android:scheme="@string/fb_login_protocol_scheme" />
            </intent-filter>
        </activity>

    </application>

我所定义的登录 Activity 为 UserActivity

文档中提示 

Mac

keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1 -binary | openssl base64

Windows

keytool -exportcert -alias androiddebugkey -keystore %HOMEPATH%\.android\debug.keystore | openssl sha1 -binary | openssl base64

生成发布密钥散列

keytool -exportcert -alias YOUR_RELEASE_KEY_ALIAS -keystore YOUR_RELEASE_KEY_PATH | openssl sha1 -binary | openssl base64

openssl 可以去网上下载,这里暂不提供了。为方便使用这里提供一个 Java 工具方法直接获取 SIGNATURES ,之后将此 SIGNATURES 填写上去就可以了

    public static void getSignatures(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(
                    "您的包名", PackageManager.GET_SIGNATURES);
            for (Signature signature : info.signatures) {
                MessageDigest md = MessageDigest.getInstance("SHA");
                md.update(signature.toByteArray());
                Log.e(TAG, Base64.encodeToString(md.digest(), Base64.DEFAULT));
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

代码正式开始,Kotlin 编写

初始化,两者二选一,sdkInitialize 被标记过时

        FacebookSdk.sdkInitialize(this.getApplicationContext());
        AppEventsLogger.activateApp(this);

Facebook 自带了登录按钮,您可以查看源码去定义这些按钮,我这里是用的自己的图标。按钮为如下

        <com.facebook.login.widget.LoginButton
            android:id="@+id/login_facebook"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="80dp"
            android:layout_marginBottom="40dp"
            app:layout_constraintBottom_toTopOf="@id/tvLogin"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

//我所用的为下面这个自定义 ImageView
    <ImageView
        android:id="@+id/login_twitter"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="80dp"
        android:layout_marginBottom="40dp"
        android:src="@mipmap/ic_twitter"
        app:layout_constraintBottom_toTopOf="@id/tvLogin"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

先定义回调接口 CallbackManager,在 onActivityResult 中注册


    private val callbackManager = CallbackManager.Factory.create()

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        callbackManager.onActivityResult(requestCode, resultCode, data)
    }

正式的数据回调接口

        LoginManager.getInstance().registerCallback(callbackManager, object :
            FacebookCallback<LoginResult?> {
            override fun onSuccess(loginResult: LoginResult?) {
                Log.e("FACEBOOK", "onSuccess")
            }

            override fun onCancel() {
                Log.e("FACEBOOK", "onCancel")
            }

            /*
            SERVER_ERROR: [code] 1349195 [message]: 该密钥散列不匹配任何已存储的密钥散列。请前往 https://developers.facebook.com/docs/facebook-login/android 了解更多信息。 [extra]:
             */
            override fun onError(exception: FacebookException) {
                Log.e("FACEBOOK", "onError " + exception.toString())
            }
        })

点击事件,也就是请求 Facebook app时,传入参数 public_profile 、email,其他的一些可参考官方文档,有些是需要用户给予权限

        login_facebook.setOnClickListener {
            LoginManager.getInstance()
                .logInWithReadPermissions(this, listOf("public_profile", "email"))
        }

点击后如果成功,会在 onSuccess 的回调中获取 loginResult 。其中包含了 accessToken 可用作获取 Facebook 其他的一些信息使用,accessToken 中也包含一个重要属性 applicationId ,看你的业务逻辑如何使用了。

由返回的 accessToken 可以发起 GraphRequest 请求了

                    val newMeRequest =
                        GraphRequest.newMeRequest(accessToken) { obj, _ ->
                            XLog.e(obj)
                        }
                    val parameters = Bundle()
                    parameters.putString(
                        "fields",
                        "id,age_range,birthday,email,first_name,last_name,gender,link,location,middle_name,name,apprequests,friends,permissions,picture"
                    )
                    newMeRequest.setParameters(parameters)
                    newMeRequest.executeAsync()

这里说明下请求的参数中 fields 中的几个主要的属性

id:应用程序用户的应用程序范围内的用户id。此id对于应用程序是唯一的,不能被其他应用程序使用。

age_range:此人的年龄段以最小和最大年龄表示。比如18岁以上,21岁以下。

birthday:固定格式的字符串,如 MM/DD/YYYY。但是,账户可以控制谁可以看到自己出生的年份,而不是月份和日期,因此这个字符串只能是年(YYYY)或月+日(MM/DD)。

email:向用户的个人资料中列出的主要电子邮件地址发送电子邮件。如果没有有效的电子邮件地址,则不会返回此字段。

first_name、last_name 不多解释,middle_name :此人的中间名,name 即全名。

gender:男性或女性。如果性别设置为自定义值,则此值将基于首选代词;如果首选代词为中性,则忽略此值

link:将链接链接到此人的时间轴。只有当点击链接的人登录到Facebook并且是其个人资料被查看者的朋友时,该链接才会被解析。

location:该人员在其个人资料中输入的当前位置。此字段需要用户“位置”权限。

friends:朋友。

permissions:用户授予此应用程序的权限。

picture:描绘此人的个人资料图片。

返回格式为 JSONObject ,可以解析此 json 得到以上的信息(由于一些信息可能用户未设置,所以获取不到,多注意判断)

 

此时第三方登录的标准流程已经实现,下来关注两个回调,更全面的了解 Facebook sdk

    private val accessTokenTracker = object : AccessTokenTracker() {
        override fun onCurrentAccessTokenChanged(
            oldAccessToken: AccessToken?,
            currentAccessToken: AccessToken?
        ) {
            XLog.e("oldAccessToken $oldAccessToken  currentAccessToken $currentAccessToken")
            if (currentAccessToken == null) {
                LoginManager.getInstance().logOut()
            }
        }
    }

    private val profileTracker = object : ProfileTracker() {
        override fun onCurrentProfileChanged(
            oldProfile: Profile?,
            currentProfile: Profile?
        ) {
            XLog.e("oldProfile $oldProfile  currentProfile $currentProfile")
        }
    }

定义 AccessTokenTracker、ProfileTracker,官方用蹩脚的中文解释说 " 如果您希望应用持续追踪当前访问口令和个人资料,您可以部署 AccessTokenTracker 和 ProfileTracker 类。 这些类将在访问口令或个人资料发生更改时调用您的代码。 在内部,它们使用接收器,因此您需要调用活动上的 stopTracking() 或调用代码片段的 onDestroy() 方法。 "

其实 AccessTokenTracker 就是请求访问令牌,如权限 "public_profile", "email" 等更改是回调,ProfileTracker 为配置文件更改时的通知。

使用方法也很简单

        //开启
        accessTokenTracker.startTracking()
        profileTracker.startTracking()
        //关闭
        accessTokenTracker.stopTracking()
        profileTracker.stopTracking()

 

facebook 提供了退出登录方法,再上面已看到 AccessTokenTracker 定义中已看到

                LoginManager.getInstance().logOut()

同时,它还可以查看是否已经登录 

        val accessToken = AccessToken.getCurrentAccessToken()
        val isLoggedIn = accessToken != null && !accessToken.isExpired

目前所了解的就这些,最后附上代码



import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.fragment.app.Fragment
import com.elvishew.xlog.XLog
import com.facebook.*
import com.facebook.login.LoginManager
import com.facebook.login.LoginResult
import com.facebook.login.widget.LoginButton
import org.json.JSONObject

/**
 * <pre>
 * @user : milanxiaotiejiang
 * @email : 765151629@qq.com
 * @version : 1.0
 * @date : 2020/8/1
 * @description : facebook工具类
 * </pre>
 */

/**
email - 允许访问用户的首选邮箱信息。
user_likes - 允许访问用户点赞的内容列表。
user_photos - 允许您的应用获取用户已发布的照片。
appsecret_proof 参数签名,以降低被恶意软件和垃圾邮件发送者攻击的可能性。

"email", "user_likes", "user_status", "user_photos", "user_birthday", "public_profile", "user_friends"
 */
fun View.onFacebookLogin(fragment: Fragment) {
    setOnClickListener {
        LoginManager.getInstance()
            .logInWithReadPermissions(fragment, listOf("public_profile", "email", "user_gender"))
    }
}

fun LoginButton.onFacebookLogin(callbackManager: CallbackManager) {
    registerCallback(callbackManager, object : FacebookCallback<LoginResult?> {
        override fun onSuccess(loginResult: LoginResult?) {
            XLog.e("FACEBOOK onSuccess" + loginResult.toString())
        }

        override fun onCancel() {
            XLog.e("FACEBOOK onCancel")
        }

        override fun onError(exception: FacebookException) {
            XLog.e("FACEBOOK onError" + exception.toString())
        }
    })
}

/**
如果您希望应用持续追踪当前访问口令和个人资料,您可以部署 AccessTokenTracker 和 ProfileTracker 类。
这些类将在访问口令或个人资料发生更改时调用您的代码。 在内部,它们使用接收器,因此您需要调用活动上的 stopTracking() 或调用代码片段的 onDestroy() 方法。
 */
private val accessTokenTracker = object : AccessTokenTracker() {
    override fun onCurrentAccessTokenChanged(
        oldAccessToken: AccessToken?,
        currentAccessToken: AccessToken?
    ) {
        XLog.e("oldAccessToken $oldAccessToken  currentAccessToken $currentAccessToken")
        if (currentAccessToken == null) {
            LoginManager.getInstance().logOut()
        }
    }
}

private val profileTracker = object : ProfileTracker() {
    override fun onCurrentProfileChanged(oldProfile: Profile?, currentProfile: Profile?) {
        XLog.e("oldProfile $oldProfile  currentProfile $currentProfile")
    }
}

fun facebookStartTracking() {
    if (AccessToken.getCurrentAccessToken() != null && !AccessToken.getCurrentAccessToken().isExpired) {
        accessTokenTracker.startTracking()
        profileTracker.startTracking()
    }
}

fun facebookStopTracking() {
    accessTokenTracker.stopTracking()
    profileTracker.stopTracking()
}

/**
id          The app user's App-Scoped User ID. This ID is unique to the app and cannot be used by other apps.
age_range   The age segment for this person expressed as a minimum and maximum age. For example, more than 18, less than 21.
birthday    The person's birthday. This is a fixed format string, like MM/DD/YYYY. However, people can control who can see the year they were born separately from the month and day so this string can be only the year (YYYY) or the month + day (MM/DD)
email       The User's primary email address listed on their profile. This field will not be returned if no valid email address is available.
first_name  The person's first name
gender      The gender selected by this person, male or female. If the gender is set to a custom value, this value will be based off of the preferred pronoun; it will be omitted if the preferred pronoun is neutral
last_name   The person's last name
link        A link to the person's Timeline. The link will only resolve if the person clicking the link is logged into Facebook and is a friend of the person whose profile is being viewed.
location    The person's current location as entered by them on their profile. This field requires the user_location permission.
middle_name The person's middle name
name        The person's full name
apprequests This person's pending requests from an app

friends     The person's friends
permissions The permissions that the person has granted this app
picture     The person's profile picture
 */
fun facebookCallback(
    callbackManager: CallbackManager,
    block: (jSONObject: JSONObject) -> Unit
) {
    LoginManager.getInstance().registerCallback(callbackManager, object :
        FacebookCallback<LoginResult?> {
        override fun onSuccess(loginResult: LoginResult?) {
            if (loginResult != null) {
                loginResult.apply {

                    //无效日志
//                    accessToken.apply {
//                        applicationId// 287891882425066
//                        dataAccessExpirationTime //Sun Oct 18 12:44:34 GMT+08:00 2020
//                        declinedPermissions /// {  }
//                        expires// Tue Sep 29 10:18:45 GMT+08:00 2020
//                        lastRefresh// Fri Jul 31 10:19:00 GMT+08:00 2020
//                        source.apply {
//                            name // FACEBOOK_APPLICATION_SERVICE
//                            ordinal // 3
//                        }
//                        token //EAAEF1g1iuuoBAPIWlNLeCMwKBZBIBC3Q8XZAGYI5FWg5YEmyNoU6yoP9ZCNrGj2BVqCjBGyhfdZAVy28iLHuZAzmDwldI5GgZAEwf5uvZAxCWSpTDAV1nsxvwroKjc8IQfcl7P5LzXwb1qdj1UU7AfH5A5pOlqKg6K4MQpWuqZC9NdbZBmHhTMYdzeHMcOkNUBD1sBuHbkcSJ4gARMzkZC77KdfJroNbZAF2jZBpCeFG515GrwZDZD
//                        userId //122057319574816
//                    }
//                    recentlyGrantedPermissions // { public_profile }
//                    recentlyDeniedPermissions // {   }

                    val newMeRequest = GraphRequest.newMeRequest(accessToken) { obj, _ ->
                        block(obj)
                    }

                    val parameters = Bundle()
                    parameters.putString(
                        "fields",
                        "id,email,gender,name,picture"
                    )
                    newMeRequest.parameters = parameters
                    newMeRequest.executeAsync()
                }
            } else {
                XLog.e("loginResult is null")
            }
        }

        override fun onCancel() {
            Log.e("FACEBOOK onCancel", "")
        }

        /*
        SERVER_ERROR: [code] 1349195 [message]: 该密钥散列不匹配任何已存储的密钥散列。请前往 https://developers.facebook.com/docs/facebook-login/android 了解更多信息。 [extra]:
         */
        override fun onError(exception: FacebookException) {
            Log.e("FACEBOOK onError", exception.toString())
        }
    })
}

最新发现 AppEventsLogger.activateApp(this); 初始化 facebook 与极光推送冲突,可使用 FacebookSdk.sdkInitialize(this); 初始化即可。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值