需求
公司要求写一个sdk用来给cocos 游戏提供Facebook , Google , Line 登录
一把梭实现
当看到这个需求的时候 , 一把梭实现于是有了下面代码
class LoginActivity : Activity() {
companion object {
private const val signType_fb = 1
private const val signType_google = 2
private const val signType_line = 3
private var signType = 0
private var line_channelId = ""
fun open(activity: Activity) {
activity.startActivity(Intent(activity, LoginActivity::class.java))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (signType == signType_line) {
LineSignInHelper.signIn(
this, line_channelId
)
}
if (signType == signType_fb) {
FacebookSignInHelper.signIn(
this, getFacebookLoginCallBack(this)
)
}
if (signType == signType_google) {
GoogleSignInHelper.signIn(this)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (signType == signType_line) {
LineSignInHelper.onActivityResult(
this,
requestCode,
resultCode,
data,
getLineLoginCallBack(this)
)
}
if (signType == signType_google) {
GoogleSignInHelper.onActivityResult(
this, requestCode, resultCode, data,
getGoogleLoginCallBack(this)
)
}
if (signType == signType_fb) {
FacebookSignInHelper.onActivityResult(requestCode, resultCode, data)
}
}
}
上面代码大致就是弄个透明的activity , 不影响cocos游戏界面 , 在onCreate 中调用相关平台api去登录 , onActivityResult 方法中处理登录的结果 ,
写完之后总感觉代码有点不对劲 , 如果以有支付 , 广告 等其他功能也需要用到透明的activity , 这样的写法岂不是要建 N 个透明的activity ?
优化1: 把activity 相关的方法抽出来 , 弄成接口
因为我这里只需要用到 onCreate , 和 onActivityResult , 所以就先抽出这两方法
interface ActivityCallBack {
fun onCreate(activity:Activity ,savedInstanceState: Bundle?);
fun onActivityResult(activity:Activity ,requestCode: Int, resultCode: Int, data: Intent?);
}
然后把LoginActivity 更名为TransparentActivity , TransparentActivity 内部不耦合任何与多平台登录相关的逻辑 , 这样就能做到通用
打开界面的时候给activityCallBack赋值 , 界面关闭 finish 时把 activityCallBack = null 防止内存泄漏 , 代码如下
class TransparentActivity : Activity() {
companion object {
var activityCallBack: ActivityCallBack? = null
fun open(activity: Activity, activityCallBack: ActivityCallBack) {
TransparentActivity.activityCallBack = activityCallBack
activity.startActivity(Intent(activity, TransparentActivity::class.java))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activityCallBack?.onCreate(this, savedInstanceState)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
activityCallBack?.onActivityResult(this, requestCode, resultCode, data)
}
override fun finish() {
super.finish()
activityCallBack = null
}
}
具体使用
object LoginManager {
private val loginCallBack = object : ActivityCallBack by noOpDelegate() {
override fun onCreate(activity: Activity, savedInstanceState: Bundle?) {
startLogin(activity)
}
override fun onActivityResult(
activity: Activity,
requestCode: Int,
resultCode: Int,
data: Intent?
) {
handleLoginResult(activity, requestCode, resultCode, data)
}
private fun startLogin(activity: Activity) {
//省略类似代码
if (signType == signType_fb) {
FacebookSignInHelper.signIn(
activity, getFacebookLoginCallBack(activity)
)
}
}
private fun handleLoginResult(
activity: Activity,
requestCode: Int,
resultCode: Int,
data: Intent?
) {
//省略类似代码
if (signType == signType_fb) {
FacebookSignInHelper.onActivityResult(requestCode, resultCode, data)
}
}
}
//这个方法提供给cocos调用
fun facebookSignIn(activity: Activity) {
signType = signType_fb
TransparentActivity.open(activity, loginCallBack)
}
private fun getFacebookLoginCallBack(activity: Activity) =
object : LoginCallBackWrapper<GraphResponse>(activity) {
override fun onSuccess(userInfo: GraphResponse) {
super.onSuccess(userInfo)
val user = userInfo.getJSONObject();
val pictureJson = user?.getJSONObject("picture").toString()
var photoUrl = ""
if (pictureJson.isNotBlank()) {
photoUrl =
JSONObject(pictureJson).getJSONObject("data").get("url")
.toString()
}
//登录成功 调用cocos方法把用户信息返回
callCocosUnityMethod("facebookLoginSuccess", "$user")
}
override fun onFail(e: Exception?) {
super.onFail(e)
//登录失败 调用cocos方法返回错误信息
callCocosUnityMethod("facebookLoginFailed", "${e?.message}")
}
}
}
by noOpDelegate 这个是啥玩意 ,
internal inline fun <reified T : Any> noOpDelegate(): T {
val javaClass = T::class.java
return Proxy.newProxyInstance(
javaClass.classLoader, arrayOf(javaClass), NO_OP_HANDLER
) as T
}
这样做其实就是为了以后如果ActivityCallBack 中添加了 onStart , onResume 等其他方法的时候 ,
我们又用不到 , 如果不用这种动态代理的方式全部要实现 , 感觉没啥必要 , 所以弄个动态代理帮我们实现 , 我们只要按需实现用到的方法就行
这样一来感觉很舒服了 , 以后如果有支付相关的 再弄个内部类实现ActivityCallBack就行了 , 代码复用性极高
还能继续优化吗?
如果以后有其他平台登录需要添加 , 是不是又要添加一个signType_xxx , 然后 if 判断有的加上一个 , 改动的地方非常多
private fun startLogin(activity: Activity) {
if (signType == signType_line) {
LineSignInHelper.signIn(
activity, line_channelId
)
}
if (signType == signType_fb) {
FacebookSignInHelper.signIn(
activity, getFacebookLoginCallBack(activity)
)
}
if (signType == signType_google) {
GoogleSignInHelper.signIn(activity)
}
}
能怎么优化呢?
能不能把平台登录相关方法抽象出来 , 如果以后有其他的登录只要弄一个子类就行了
优化2 :平台登录相关接口抽象出来 , 面向接口编程
有些是需要先初始化才能调用登录相关的api的 比如facebook , 所以除了 登录/登出 /处理登录结果,还需要个初始化接口
interface ISignInHelper <RESULT> {
fun init(activity: Activity)
fun signIn(activity: Activity)
fun signOut(activity: Activity)
val callBack: LoginCallBack<RESULT>
fun onActivityResult(
activity: Activity,
requestCode: Int,
resultCode: Int,
data: Intent?
)
}
interface LoginCallBack<T> {
fun onSuccess(userInfo: T)
fun onFail(e: Exception?)
}
再弄个sealed class 类 , 用于判断是哪个平台登录
sealed class LoginType {
object FB : LoginType()
object Google : LoginType()
class Line(var channelId: String) : LoginType()
}
然后弄个map 用来保存ISignInHelper
key 对应LoginType , value 对应ISignInHelper的子类
val signHelps = mapOf<LoginType, ISignInHelper<*>>(
LoginType.FB to FacebookSignInHelper,
//其他的省略
)
提供给cocos调用的方法
var curSignInHelper :ISignInHelper<*>?=null
fun signIn(activity: Activity ,type :LoginType ){
curSignInHelper = signHelps[type]
TransparentActivity.open(activity , activityCallBack)
}
startLogin的众多if判断 就可以去掉了 , 优化后的代码
private fun startLogin(activity: Activity) {
curSignInHelper?.signIn(activity);
}
优化3 : 登录成功/失败后 要关闭透明Activity
没优化之前的代码
private fun getFacebookLoginCallBack(activity: Activity) =
object : LoginCallBack<GraphResponse>(activity) {
override fun onSuccess(userInfo: GraphResponse) {
activity.finish()
//省略业务逻辑
}
override fun onFail(e: Exception?) {
activity.finish()
//省略业务逻辑
}
}
每个平台的LoginCallBack 的 onSuccess / onFail的都需要调用 activity.finish() 关闭页面 , 很繁琐 .
可以对LoginCallBack 包装一层
abstract class LoginCallBackWrapper<T>(var activity:Activity) : LoginCallBack<T>{
override fun onSuccess(userInfo: T) {
activity.finish()
}
override fun onFail(e: Exception?) {
activity.finish()
}
}