多平台登录sdk代码优化

232 阅读4分钟

需求

公司要求写一个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()
    }
}