手游SDK — 第四篇(SDK架构设计代码实现篇(下)- 项目需求开发)

第二部分:项目需求开发

基础库搭建好了之后就是根据项目需求进行实际功能开发了,因为不同的项目有不同的项目需求,我简单的以项目初始化、账号登录/切换账号/账号登出、支付三大功能点来进行框架的代码实现。

1、需求开发 - Manager控制模块

Manager模块是SDK的核心模块,主要负责业务的功能实现及逻辑控制。根据项目需求,暂定为初始化Manager、账号Manager、支付Manager。

初始化Manager:处理SDK的初始化逻辑,全局参数缓存、环境切换、权限问题等。
public class InitManager {

    private final String TAG = getClass().getSimpleName();

    private volatile static InitManager INSTANCE;

    private InitManager() {
    }

    public static InitManager getInstance() {
        if (INSTANCE == null) {
            synchronized (InitManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new InitManager();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 加载SDK项目配置入口插件(这是项目最开始加载的)
     * @param context 上下文
     * @param isdebug 日志调试开关
     */
    public void initApplication(Application cxt, Context context, boolean isdebug){

        ApplicationCache.init(cxt);
        LogUtils.setDebugLogModel(isdebug);
        ProjectManager.init(context).loadAllProjects();
        //聚合SDK加载渠道插件
        ChannelManager.init(context).loadChannel();
    }


    private static Handler sApiHandler;
    private static boolean initState = false;

    /**
     * SDK初始化逻辑
     * @param activity
     * @param callBackListener
     */
    public void init(final Activity activity, final String gameid, final String gamekey, final CallBackListener callBackListener) {

        if (sApiHandler == null) {
            HandlerThread ht = new HandlerThread("project_sdk_thread",
                    Process.THREAD_PRIORITY_BACKGROUND);
            ht.start();
            sApiHandler = new Handler(ht.getLooper());
        }

        Runnable r = new Runnable() {
            @Override
            public void run() {

                //1、初始化全局缓存变量
                BaseCache.init(activity.getApplication());
                BaseCache.getInstance().put(KeyConfig.GAME_ID,gameid);
                BaseCache.getInstance().put(KeyConfig.GAME_KEY,gamekey);

                //2、初始化SDK参数
                SDKInfoCache.getDefault(activity.getApplication());

                //3、初始化持久化数据
                SharePreferencesCache spCache = new SharePreferencesCache(activity);
                spCache.init();

                //4、加载功能插件
                PluginManager.init(activity).loadAllPlugins();

                //5、初始化域名配置
                UrlConfig.initUrl();

                //6、开始初始化逻辑
                startInitLogic(activity,callBackListener);

            }
        };
        sApiHandler.post(r);
    }


    /**
     * 真正的初始化逻辑
     */
    private void startInitLogic(final Activity activity, final CallBackListener callBackListener){

        //-----------------------------已初始化完成--------------------------------
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                setInitState(true);
                callBackListener.onSuccess(null);
            }
        });
    }

    /**
     * 初始化功能插件()
     */
    private void initFunctionPlugin(Activity activity){

        //腾讯bugly日志收集

    }


    public void setInitState(boolean state) {
        initState = state;
        //将当前状态存储到全局变量供其他模块插件使用
        BaseCache.getInstance().put(KeyConfig.IS_INIT,initState);
    }

    public boolean getInitState() {
        return initState;
    }
}
账号Manager:管理账号的各个功能接口:登录、切换账号、注销账号、绑定账号。登录可细分为设备登陆、游客登录、账号登陆、三方登陆(google/facebook/微信)等登录逻辑和切换、绑定逻辑。
public class AccountManager {

    public static final String TAG = "AccountManager";

    private volatile static AccountManager INSTANCE;

    private AccountManager() {
    }

    public static AccountManager getInstance() {
        if (INSTANCE == null) {
            synchronized (AccountManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new AccountManager();
                }
            }
        }
        return INSTANCE;
    }

    private Activity mActivity;
    private AccountBean mLoginInfo; //当前登陆的登陆信息
    private boolean isSwitchAccount = false; //通过标记位来判断是否是切换账号按钮的登录回调


    /******************************************      获取Project账号监听     ****************************************/

    private CallBackListener projectLoginCallBackListener;
    public void setLoginCallBackLister(CallBackListener callBackLister){
        projectLoginCallBackListener = callBackLister;
    }

    private void CallBackToProject(int event, int code, AccountBean accountBean, String msg){

        //设置回调信息
        AccountCallBackBean accountCallBackBean = new AccountCallBackBean();
        accountCallBackBean.setEvent(event); //事件类型ID
        accountCallBackBean.setErrorCode(code); //事件码
        accountCallBackBean.setAccountBean(accountBean); //事件的账号信息
        accountCallBackBean.setMsg(msg); //设置事件的信息

        if (projectLoginCallBackListener != null){
            projectLoginCallBackListener.onSuccess(accountCallBackBean);//回调给Project的信息
        }
    }


    /**
     * 登录结果监听
     */
    private CallBackListener LoginCallBackLister = new CallBackListener<AccountBean>(){

        @Override
        public void onSuccess(AccountBean loginInfo) {
            LogUtils.d(TAG, "loginInfo:" + loginInfo.toString());

            mLoginInfo = loginInfo;
            //登陆成功,设置登录信息
            setLoginSuccess(loginInfo);

            if (isSwitchAccount){
                CallBackToProject(TypeConfig.SWITCHACCOUNT, ErrCode.SUCCESS,loginInfo, "user switchAccount success");
                isSwitchAccount = false; //置为false

            }else {
                CallBackToProject(TypeConfig.LOGIN,ErrCode.SUCCESS,loginInfo, "user login success");
            }
        }

        @Override
        public void onFailure(int code, String msg) {
            mLoginInfo = null; //当前登陆失败就置为null

            if (isSwitchAccount){

                if (code == ErrCode.CANCEL){ //如果切换账号时,不走登录,给登出回调
                    CallBackToProject(TypeConfig.LOGOUT, ErrCode.SUCCESS, null, "user logout success");

                }else {
                    CallBackToProject(TypeConfig.SWITCHACCOUNT, code, null, msg);
                }

            }else {
                CallBackToProject(TypeConfig.LOGIN, code, null, msg);
            }
        }
    };

    /******************************************      登录     ****************************************/


    /**
     * 显示登录界面
     */
    public void showLoginView(final Activity activity, HashMap<String,Object> loginMap){
        mActivity = activity;


        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setMessage("是否登录?");
        builder.setTitle("登录界面");
        builder.setPositiveButton("登录",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {

                        AccountBean loginInfo = new AccountBean();
                        loginInfo.setLoginState(true); //将登录成功状态返回
                        loginInfo.setUserToken("dasfkaf-SAFA-kfad");
                        loginInfo.setUserID("userID-123");
                        loginInfo.setUserName("测试用户"); //聚合将用名设置为UserID
                        LoginCallBackLister.onSuccess(loginInfo);

                    }
                });
        builder.setNegativeButton("取消",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        LoginCallBackLister.onFailure(ErrCode.FAILURE,"login fail");
                    }
                });
        builder.create().show();

    }


    /**
     * 授权登录,具体项目具体实现逻辑
     */
    public void authLogin(Activity activity, HashMap<String,Object> loginMap){
        mActivity = activity;

        AccountBean loginInfo = new AccountBean();
        loginInfo.setLoginState(true); //将登录成功状态返回
        loginInfo.setUserToken("dasfkaf-SAFA-kfad");
        loginInfo.setUserID("userID-123");
        loginInfo.setUserName("测试用户"); //聚合将用名设置为UserID

        LoginCallBackLister.onSuccess(loginInfo);
    }


    /**
     * 获取当前登陆状态,默认false
     * @return
     */
    public boolean getLoginState() {
        if (mLoginInfo != null){
            return mLoginInfo.getLoginState();
        }
        return false;
    }

    /******************************************      切换账号    ****************************************/

    /**
     * 切换账号
     * @param activity
     */
    public void switchAccount(Activity activity){

        mActivity = activity;

        //先走登出逻辑
        mLoginInfo = null; //登录信息清空
        isSwitchAccount = true;
        clearLoginInfo(activity);
    }


    /******************************************      登出   ****************************************/

    /**
     * 账号登出
     */
    public void logout(Activity activity){

        mActivity = activity;

        mLoginInfo = null; //登录信息清空
        isSwitchAccount = false;
        clearLoginInfo(activity);

        CallBackToProject(TypeConfig.LOGOUT, ErrCode.SUCCESS, null, "user logout success");
    }


    /**
     * 设置登录成功行为
     */
    private void setLoginSuccess(AccountBean loginInfo){

        if (loginInfo != null){
            BaseCache.getInstance().put(KeyConfig.PLAYER_ID,loginInfo.getUserID());
            BaseCache.getInstance().put(KeyConfig.PLAYER_NAME,loginInfo.getUserName());
            BaseCache.getInstance().put(KeyConfig.PLAYER_TOKEN,loginInfo.getUserToken());

            //将当前状态存储到全局变量供其他模块插件使用
            BaseCache.getInstance().put(KeyConfig.IS_LOGIN, getLoginState());
        }
    }


    /**
     * 清空登陆信息
     */
    private void clearLoginInfo(Activity activity){

        mLoginInfo = null;

        //清空内存的用户信息
        BaseCache.getInstance().put(KeyConfig.PLAYER_ID,"");
        BaseCache.getInstance().put(KeyConfig.PLAYER_NAME,"");
        BaseCache.getInstance().put(KeyConfig.PLAYER_TOKEN,"");

        //将当前状态存储到全局变量供其他模块插件使用
        BaseCache.getInstance().put(KeyConfig.IS_LOGIN, getLoginState());

    }

}
支付Manager:购买管理类,管理SDK的各个购买功能接口:创建订单、三方支付、运营商支付、渠道支付、补单逻辑、包月、订阅等。注意可能还会有各个复杂的支付逻辑: 可能会先短代支付、然后渠道支付、三方支付,还有后台切换支付开关等。
public class PurchaseManager {

    public static final String TAG = "PurchaseManager";

    private volatile static PurchaseManager INSTANCE;

    private PurchaseManager() {
    }

    public static PurchaseManager getInstance() {
        if (INSTANCE == null) {
            synchronized (PurchaseManager.class) {
                if (INSTANCE == null) {
                    INSTANCE = new PurchaseManager();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 创建订单,具体项目具体实现
     */
    public void createOrderId(Activity activity, HashMap<String, Object> payParams , final CallBackListener callBackListener){

        LogUtils.debug_d(TAG,"payParams = " + payParams.toString());
        String orderID = "DD1441";
        callBackListener.onSuccess(orderID);

    }

    /**
     * 显示支付界面
     */
    public void showPayView(Activity activity, HashMap<String, Object> payParams, final CallBackListener callBackListener){
        LogUtils.debug_d(TAG,"payParams = " + payParams.toString());

        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        String message = "充值金额:" + "2"
                + "\n商品名称:" + "大饼"
                + "\n商品数量:" + "1"
                + "\n资费说明:" + "2元";
        builder.setMessage(message);
        builder.setTitle("请确认充值信息");
        builder.setPositiveButton("确定",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(final DialogInterface dialog, int index) {
                        //支付结果回调到这里来
                        PurchaseResult purchaseResult = new PurchaseResult(PurchaseResult.PurchaseState,null);
                        callBackListener.onSuccess(purchaseResult);
                    }
                });
        builder.setNegativeButton("取消",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        callBackListener.onFailure(ErrCode.FAILURE,"pay fail");
                    }
                });
        builder.create().show();
    }
}

2、需求开发 - Plugin三方功能插件层

Plugin三方功能插件层分两部分组成:反射API和具体Plugin插件。反射API是动态插拔功能插件的关键,可以打包时动态打对应的供插件。Plugin插件是具体实现和封装对应的功能层。额外拓展新的功能插件时,继承Plugin类即可。下面以微信插件为例:

微信功能Plugin:封装和实现微信的登录、支付、分享等常见功能
public class WechatPlugin extends Plugin {

    private String TAG = "WechatPlugin";

    @Override
    protected synchronized void initPlugin() {
        super.initPlugin();
        LogUtils.d(TAG,"init " + getClass().getSimpleName());
    }

    /**
     * 调用微信支付接口
     */
    public void wechatPay(Context context, Map<String,Object> payMap, CallBackListener callBackListener){
        WechatPay.getInstance().pay(context,payMap,callBackListener);
    }


    /**
     * 调用微信登录接口
     */
    public void wechatLogin(Context context, Map<String,Object> LoginMap, CallBackListener callBackListener){
        
    }


    /**
     * 调用微信分享接口
     */
    public void wechatShare(Context context, Map<String,Object> ShareMap, CallBackListener callBackListener){

    }

    /**
     * 根据当前的生命周期
     * @param context
     */
    @Override
    public void onResume(Context context) {
        WechatPay.getInstance().onResume(context);
    }
}
微信功能PluginAPI:对接微信Plugin接口。
public class WechatPluginApi extends PluginReflectApi {

    private String TAG = "WechatPluginApi";

    private Plugin wechatPlugin;

    private volatile static WechatPluginApi INSTANCE;

    private WechatPluginApi() {
        wechatPlugin = PluginManager.getInstance().getPlugin("plugin_wechat");
    }

    public static WechatPluginApi getInstance() {
        if (INSTANCE == null) {
            synchronized (WechatPluginApi.class) {
                if (INSTANCE == null) {
                    INSTANCE = new WechatPluginApi();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 调用微信app支付
     */
    public void pay(Context context, Map<String,Object> map, CallBackListener callBackListener){

        if (wechatPlugin != null){
            invoke(wechatPlugin,"wechatPay",new Class<?>[]{Context.class, Map.class, CallBackListener.class},
                    new Object[]{context, map, callBackListener});
        }
    }

}

3、需求开发 - Project项目业务层

Project层主要分自定义SDK项目和聚合SDK项目两大类,自定义SDK是自己实现SDK的功能逻辑;聚合SDK主要是封装渠道SDK用于游戏的联运。业务需求是不一样的。相对而已聚合SDK会简单点。如有新的项目需求,可继承Project对应实现就OK了。主要自定义SDK为例讲解下

public class CustomProject extends Project{

    private final String TAG = getClass().getSimpleName();


    /**
     * 项目实例化入口
     */
    @Override
    protected synchronized void initProject() {
        LogUtils.d(TAG, getClass().getSimpleName() + " has init");
        super.initProject();
    }


    /******************************************      初始化      ****************************************/

    @Override
    public void init(Activity activity, String gameid, String gamekey, final CallBackListener callBackListener) {
        LogUtils.d(TAG,"init");

        if (activity == null || callBackListener == null) {
            callBackListener.onFailure(ErrCode.PARAMS_ERROR,"activity or callBackListener is null");
            return;
        }

        //设置账号监听
        AccountManager.getInstance().setLoginCallBackLister(projectAccountCallBackListener);

        InitManager.getInstance().init(activity, gameid, gamekey, new CallBackListener() {
            @Override
            public void onSuccess(Object object) {
                callBackListener.onSuccess(null);
            }

            @Override
            public void onFailure(int code, String msg) {
                callBackListener.onFailure(code,msg);
            }
        });
    }


    /******************************************      账号      ****************************************/

    /*** SDKApi层设置回调监听 */
    private CallBackListener ApiAccountCallback;

    @Override
    public void setAccountCallBackLister(CallBackListener callBackLister) {
        ApiAccountCallback = callBackLister;
    }

    /**
     * 监听AccountManager登录、切换账号、绑定、注销的回调信息
     */
    private CallBackListener projectAccountCallBackListener = new CallBackListener<AccountCallBackBean>() {

        @Override
        public void onSuccess(AccountCallBackBean callBackBean) {
            ApiAccountCallback.onSuccess(callBackBean);
        }

        @Override
        public void onFailure(int code, String msg) {
            //不会走到这里来
        }
    };

    private void AccountOnFailCallBack(int event, int code, String msg){

        AccountCallBackBean callBackBean = new AccountCallBackBean();
        callBackBean.setEvent(event);
        callBackBean.setErrorCode(code);
        callBackBean.setMsg(msg);
        ApiAccountCallback.onSuccess(callBackBean);
    }


    @Override
    public void login(Activity activity, HashMap<String, Object> loginParams) {
        LogUtils.d(TAG,"login");

        if (!InitManager.getInstance().getInitState()){
            Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
            return;
        }

        if (activity == null ) {
            AccountOnFailCallBack(TypeConfig.LOGIN,ErrCode.PARAMS_ERROR,"activity is null");
            return;
        }

        AccountManager.getInstance().showLoginView(activity,loginParams);
    }


    @Override
    public void switchAccount(Activity activity) {
        LogUtils.d(TAG,"switchAccount");

        if (!InitManager.getInstance().getInitState()){
            Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
            return;
        }

        if (!AccountManager.getInstance().getLoginState()){
            AccountOnFailCallBack(TypeConfig.SWITCHACCOUNT,ErrCode.NO_LOGIN,"account has not login");
            return;
        }

        if (activity == null ) {
            AccountOnFailCallBack(TypeConfig.LOGIN,ErrCode.PARAMS_ERROR,"activity is null");
            return;
        }

        AccountManager.getInstance().switchAccount(activity);
    }

    @Override
    public void logout(Activity activity) {
        LogUtils.d(TAG,"logout");

        if (!InitManager.getInstance().getInitState()){
            Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
            return;
        }

        if (!AccountManager.getInstance().getLoginState()){
            AccountOnFailCallBack(TypeConfig.LOGOUT,ErrCode.NO_LOGIN,"account has not login");
            return;
        }

        if (activity == null ) {
            AccountOnFailCallBack(TypeConfig.LOGIN,ErrCode.PARAMS_ERROR,"activity is null");
            return;
        }

        AccountManager.getInstance().logout(activity);
    }


    /******************************************      购买      ****************************************/


    @Override
    public void pay(Activity activity, HashMap<String, Object> payParams, CallBackListener callBackListener) {
        LogUtils.d(TAG,"pay");

        if (!InitManager.getInstance().getInitState()){
            Toast.makeText(activity,"请先初始化",Toast.LENGTH_SHORT).show();
            return;
        }

        if (!AccountManager.getInstance().getLoginState()){
            callBackListener.onFailure(ErrCode.NO_LOGIN,"account has not login");
            return;
        }

        if (activity == null || payParams == null || callBackListener == null) {
            callBackListener.onFailure(ErrCode.PARAMS_ERROR,"activity or PayParams or callBackListener is null");
            return;
        }

        PurchaseManager.getInstance().showPayView(activity,payParams,callBackListener);
    }


    /******************************************      退出      ****************************************/


    /**
     * 退出SDK
     */
    @Override
    public void exit(Activity activity, CallBackListener callBackListener) {
        LogUtils.d(TAG,"exit");

        if (activity == null || callBackListener == null) {
            callBackListener.onFailure(ErrCode.PARAMS_ERROR,"activity or callBackListener is null");
            return;
        }

        callBackListener.onFailure(ErrCode.NO_EXIT_DIALOG,"channel not exitDialog");
    }


    /*************************************  生命周期接口(必接) ****************************************/

    @Override
    public void onCreate(Activity activity, Bundle savedInstanceState) {
        LogUtils.d(TAG,"onCreate");

        if (InitManager.getInstance().getInitState()){
            super.onCreate(activity, savedInstanceState);
        }
    }

    @Override
    public void onStart(Activity activity) {
        LogUtils.d(TAG,"onStart");

        if (InitManager.getInstance().getInitState()){
            super.onStart(activity);
        }
    }

    @Override
    public void onResume(Activity activity) {
        LogUtils.d(TAG,"onResume");

        if (InitManager.getInstance().getInitState()){
            super.onResume(activity);
        }
    }

    @Override
    public void onPause(Activity activity) {
        LogUtils.d(TAG,"onPause");

        if (InitManager.getInstance().getInitState()){
            super.onPause(activity);
        }
    }

    @Override
    public void onStop(Activity activity) {
        LogUtils.d(TAG,"onStop");

        if (InitManager.getInstance().getInitState()){
            super.onStop(activity);
        }
    }

    @Override
    public void onDestroy(Activity activity) {
        LogUtils.d(TAG,"onDestroy");

        if (InitManager.getInstance().getInitState()){
            super.onDestroy(activity);
        }
    }

    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        LogUtils.d(TAG,"onActivityResult");

        if (InitManager.getInstance().getInitState()){
            super.onActivityResult(activity, requestCode, resultCode, data);
        }
    }

    @Override
    public void onRequestPermissionsResult(Activity activity, int requestCode, String[] permissions,int[] grantResults) {
        LogUtils.d(TAG,"onRequestPermissionsResult");

        if (InitManager.getInstance().getInitState()){
            super.onRequestPermissionsResult(activity, requestCode, permissions, grantResults);
        }
    }
}

4、需求开发 - 项目Channel业务层

主要是面向聚合SDK项目,主要封装渠道的SDK内容。如需封装新的渠道SDK,继承Channel类即可。

public class TestChannelSDK extends Channel {

    private final String TAG = getClass().getSimpleName();

    @Override
    protected void initChannel() {
        LogUtils.d(TAG, getClass().getSimpleName() + " has init");
    }

    @Override
    public String getChannelID() {
        return "1";
    }

    @Override
    public boolean isSupport(int FuncType) {

        switch (FuncType){
            case TypeConfig.FUNC_SWITCHACCOUNT:
                return true;

            case TypeConfig.FUNC_LOGOUT:
                return true;

            case TypeConfig.FUNC_SHOW_FLOATWINDOW:
                return true;

            case TypeConfig.FUNC_DISMISS_FLOATWINDOW:
                return true;

            default:
                return false;
        }
    }

    @Override
    public void init(Context context, HashMap<String, Object> initMap, CallBackListener initCallBackListener) {
        LogUtils.d(TAG,getClass().getSimpleName() + " init");
        initOnSuccess(initCallBackListener);
    }

    @Override
    public void login(Context context, HashMap<String, Object> loginMap, CallBackListener loginCallBackListener) {
        LogUtils.d(TAG,getClass().getSimpleName() + " login");
        showLoginView(context,loginCallBackListener);
    }

    @Override
    public void switchAccount(final Context context, final CallBackListener changeAccountCallBackLister) {

        LogUtils.d(TAG,getClass().getSimpleName() + " switchAccount");
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage("是否切换账号?");
        builder.setTitle("切换账号");
        builder.setPositiveButton("切换账号",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        showLoginView(context,changeAccountCallBackLister);
                    }
                });
        builder.setNegativeButton("取消",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        switchAccountOnCancel("channel switchAccount cancel",changeAccountCallBackLister);
                    }
                });
        builder.create().show();
    }

    @Override
    public void logout(Context context, final CallBackListener logoutCallBackLister) {

        LogUtils.d(TAG,getClass().getSimpleName() + " logout");
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage("是否注销账号?");
        builder.setTitle("注销账号");
        builder.setPositiveButton("成功",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        logoutOnSuccess(logoutCallBackLister);
                    }
                });
        builder.setNegativeButton("失败",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        logoutOnFail("channel logout fail",logoutCallBackLister);
                    }
                });
        builder.create().show();

    }

    @Override
    public void pay(Context context, HashMap<String, Object> payMap, final CallBackListener payCallBackListener) {

        LogUtils.d(TAG,getClass().getSimpleName() + " pay");

        String orderID = (String) payMap.get("orderId");
        String productName = (String) payMap.get("productName");
        String productDesc = (String) payMap.get("productDesc");
        String money = String.valueOf(payMap.get("money"));
        String productID = String.valueOf(payMap.get("productID"));
        LogUtils.d(TAG,productID);

        final HashMap<String,Object> paymap = new HashMap<>();
        paymap.put("orderID",orderID);
        paymap.put("productName",productName);
        paymap.put("money",money);

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        String message = "充值金额:" + money
                + "\n商品名称:" + productName
                + "\n商品数量:" + "1"
                + "\n资费说明:" + productDesc;
        builder.setMessage(message);
        builder.setTitle("请确认充值信息");
        builder.setPositiveButton("确定",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(final DialogInterface dialog, int index) {
                        payOnSuccess(payCallBackListener);
                    }
                });
        builder.setNegativeButton("取消",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        OnCancel(payCallBackListener);
                    }
                });
        builder.create().show();

    }


    private void showLoginView(final Context context, final CallBackListener loginCallBackListener){

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage("是否登录?");
        builder.setTitle("登录界面");
        builder.setPositiveButton("登录",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        JSONObject json = new JSONObject();
                        try {
                            json.put("sid", "testID");
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }

                        loginOnSuccess(json.toString(),loginCallBackListener);
                    }
                });
        builder.setNegativeButton("取消",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int index) {
                        loginOnFail("channel login fail",loginCallBackListener);
                    }
                });
        builder.create().show();

    }


    @Override
    public void exit(Context context, CallBackListener exitCallBackLister) {
        LogUtils.d(TAG,getClass().getSimpleName() + " exit");
        channelNotExitDialog(exitCallBackLister);
    }
}

项目代码总结

----------------------------------------------------------------------------------------------

GameSDK_API层:

   SDK对外接口层:是暴露给CP的接口,底层返回的数据格式在这一层转化,该层不参与混淆。
                 所以不要在该层做业务逻辑处理,避免被反射调用修改。

   后续有新的接口,可以在该层做拓展对外给CP。

----------------------------------------------------------------------------------------------

GameSDK_BeginProject层:

   SDK项目层:是与Api层对接的项目入口层,可以通过修改配置文件 Project_config.txt
   进入和替换成不同的Project SDK项目。

   注意:

       顶层Project已经确定好后,不要修改太多,如果有拓展的功能接口,
       可以通过extendFunction()对应type拓展,避免改动太多底层代码。


    SDK大体分为两类:
    1、Project_JuHe为聚合SDK项目,主要业务用于封装三方渠道。

    2、Project_Custom为自定义SDK项目,主要是做自己的渠道,每个公司可能会有不同的名称,
       主要业务是,实现用户入口、支付逻辑、数据统计等功能。

       可能根据不同的游戏运营需求会有不同的登录逻辑、绑定逻辑、支付方式、数据上报等
       会细分为公版项目、海外项目(针对地区)、根据游戏的定制化项目等。可根据不同的项目,拓展Project


    该层是做业务逻辑处理的,希望能有清晰的架构思路。
    
----------------------------------------------------------------------------------------------

SYSDK_Channel: SDK项目渠道层

   该层负责对接渠道接入的业务,不同的渠道都会有对应的Module实现具体的代码调用和逻辑处理,以及资源配置
   通过配置文件 Channel_config.txt 管理。

   而且每个渠道Module都会有对应的开发者说明文件,方便后续的开发同事维护更新。详见开发者说明。

   开发者格式说明:

      格式: 渠道名 -- 版本号 -- 开发人员 (如果渠道没有版本就按V1.0.0来)    日期

                相关注意事项说明:
                1、.... xxx ....
                2、.... xxx ....


----------------------------------------------------------------------------------------------

SYSDK_Manager:

   SDK逻辑管理层:

      该层为整个SDK功能逻辑的实现:初始化、账号、支付、获取道具信息、补单逻辑、退出
      为避免逻辑层因业务太乱导致代码过多,及后续的功能模块抽离.
      采用模块化化思想进行模块管理.

      初始化:SDK初始化(全局参数缓存、环境切换、权限问题等) >- 项目初始化(默认初始化 和 项目初始化)

      登陆:设备登陆、游客登录、账号登陆、三方登陆(google/facebook/微信)

      支付:三方支付:google、支付宝、微信、及特有的项目支付、补单

      退出:

      当有额外的功能添加的时候,在该层实现即可。

----------------------------------------------------------------------------------------------

GameSDK_Manager_Impl:

   SDK逻辑功能拓展反射层,通过反射解耦插件:

        该层更多的是针对后续Plugin插件做不同的功能反射,具体调用Plugin层插件。
        只负责对接GameSDK_Manager_Impl 和 Plugin插件层。

----------------------------------------------------------------------------------------------

GameSDK_Plugin:

    SDK功能插件层,与渠道层有点类似,通过配置文件 Plugin_config.txt 管理。
    通过逻辑控制层 Manager_Logic_Impl 反射调用实现,把插件拔除不影响。
    可以是三方的功能插件,也可以是自己实现的插件。

    目前为说明功能,用支付宝和微信说明
    Alipay功能插件层:实现Alipay功能,封装Alipay相关接口
    Wechat功能插件层:实现Wechat功能,封装Wechat相关接口

   当有额外的功能添加的时候,在该层实现即可。
   
----------------------------------------------------------------------------------------------

GameSDK_Utils:

   GameSDK_Utils层:该层为整个SDK功能基础库:目前分为业务基础库 和 功能基础库,方便后续将业务分离。

      基础组件:

         1、数据缓存、域名配置、项目/插件/渠道管理
            注意:将项目、渠道、插件分别加载的目的:

            是为了快速替换项目Project的入口类

            一个项目Project 可以对应多个渠道、多个插件。后续可以在多渠道、多插件上
            进行快速的插拔和后台开关的切换渠道。

            不过正常的需求都是一个项目,对应零个或一个渠道、一个或多个功能插件


         2、网络请求、日志输出、Gson解析

            注意:网络请求,目前封装的volly (volly是比较轻量级的,主要是为减少SDK包体)
                 日志输出,目前封装的logger(logger比较轻量级,主要用于开发日志信息输出)

                 第三方库快速替换方案思路:(方便后续维护替换成更好的库)
                 接口解耦封装,实现上层业务网络请求接口不改动,只做封装层的api调用即可


   基础库不要轻易修改! 不要轻易修改! 不要轻易修改! 不要轻易修改! 不要轻易修改!

---------------------------------------------------------------------------------------
结语:

关于手游SDK的客户端的实现就大体介绍到这里。Demo的地址: 手游SDK框架Demo
声明下,该Demo只是框架的讲解,不具备任何商业价值。如涉及到纠纷,可不能赖我呀。

关于打包篇,可移步:
手游SDK — 第五篇(游戏打包篇(上)- 打包系统设计)
手游SDK — 第六篇(游戏打包篇(中)- 自动化打包)
手游SDK — 第七篇(游戏打包篇(下)- 自动化打包踩坑记录)

如果觉得我的文章对你有帮助,请随意赞赏。您的支持将鼓励我继续创作!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容