手游SDK — 第四篇(SDK架構(gòu)設(shè)計代碼實現(xiàn)篇(下)- 項目需求開發(fā))

第二部分:項目需求開發(fā)

基礎(chǔ)庫搭建好了之后就是根據(jù)項目需求進(jìn)行實際功能開發(fā)了,因為不同的項目有不同的項目需求,我簡單的以項目初始化、賬號登錄/切換賬號/賬號登出、支付三大功能點(diǎn)來進(jìn)行框架的代碼實現(xiàn)。

1、需求開發(fā) - Manager控制模塊

Manager模塊是SDK的核心模塊,主要負(fù)責(zé)業(yè)務(wù)的功能實現(xiàn)及邏輯控制。根據(jù)項目需求,暫定為初始化Manager、賬號Manager、支付Manager。

初始化Manager:處理SDK的初始化邏輯,全局參數(shù)緩存、環(huán)境切換、權(quán)限問題等。
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 日志調(diào)試開關(guān)
     */
    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參數(shù)
                SDKInfoCache.getDefault(activity.getApplication());

                //3、初始化持久化數(shù)據(jù)
                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;
        //將當(dāng)前狀態(tài)存儲到全局變量供其他模塊插件使用
        BaseCache.getInstance().put(KeyConfig.IS_INIT,initState);
    }

    public boolean getInitState() {
        return initState;
    }
}
賬號Manager:管理賬號的各個功能接口:登錄、切換賬號、注銷賬號、綁定賬號。登錄可細(xì)分為設(shè)備登陸、游客登錄、賬號登陸、三方登陸(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; //當(dāng)前登陸的登陸信息
    private boolean isSwitchAccount = false; //通過標(biāo)記位來判斷是否是切換賬號按鈕的登錄回調(diào)


    /******************************************      獲取Project賬號監(jiān)聽     ****************************************/

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

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

        //設(shè)置回調(diào)信息
        AccountCallBackBean accountCallBackBean = new AccountCallBackBean();
        accountCallBackBean.setEvent(event); //事件類型ID
        accountCallBackBean.setErrorCode(code); //事件碼
        accountCallBackBean.setAccountBean(accountBean); //事件的賬號信息
        accountCallBackBean.setMsg(msg); //設(shè)置事件的信息

        if (projectLoginCallBackListener != null){
            projectLoginCallBackListener.onSuccess(accountCallBackBean);//回調(diào)給Project的信息
        }
    }


    /**
     * 登錄結(jié)果監(jiān)聽
     */
    private CallBackListener LoginCallBackLister = new CallBackListener<AccountBean>(){

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

            mLoginInfo = loginInfo;
            //登陸成功,設(shè)置登錄信息
            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; //當(dāng)前登陸失敗就置為null

            if (isSwitchAccount){

                if (code == ErrCode.CANCEL){ //如果切換賬號時,不走登錄,給登出回調(diào)
                    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); //將登錄成功狀態(tài)返回
                        loginInfo.setUserToken("dasfkaf-SAFA-kfad");
                        loginInfo.setUserID("userID-123");
                        loginInfo.setUserName("測試用戶"); //聚合將用名設(shè)置為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();

    }


    /**
     * 授權(quán)登錄,具體項目具體實現(xiàn)邏輯
     */
    public void authLogin(Activity activity, HashMap<String,Object> loginMap){
        mActivity = activity;

        AccountBean loginInfo = new AccountBean();
        loginInfo.setLoginState(true); //將登錄成功狀態(tài)返回
        loginInfo.setUserToken("dasfkaf-SAFA-kfad");
        loginInfo.setUserID("userID-123");
        loginInfo.setUserName("測試用戶"); //聚合將用名設(shè)置為UserID

        LoginCallBackLister.onSuccess(loginInfo);
    }


    /**
     * 獲取當(dāng)前登陸狀態(tài),默認(rèn)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");
    }


    /**
     * 設(shè)置登錄成功行為
     */
    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());

            //將當(dāng)前狀態(tài)存儲到全局變量供其他模塊插件使用
            BaseCache.getInstance().put(KeyConfig.IS_LOGIN, getLoginState());
        }
    }


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

        mLoginInfo = null;

        //清空內(nèi)存的用戶信息
        BaseCache.getInstance().put(KeyConfig.PLAYER_ID,"");
        BaseCache.getInstance().put(KeyConfig.PLAYER_NAME,"");
        BaseCache.getInstance().put(KeyConfig.PLAYER_TOKEN,"");

        //將當(dāng)前狀態(tài)存儲到全局變量供其他模塊插件使用
        BaseCache.getInstance().put(KeyConfig.IS_LOGIN, getLoginState());

    }

}
支付Manager:購買管理類,管理SDK的各個購買功能接口:創(chuàng)建訂單、三方支付、運(yùn)營商支付、渠道支付、補(bǔ)單邏輯、包月、訂閱等。注意可能還會有各個復(fù)雜的支付邏輯: 可能會先短代支付、然后渠道支付、三方支付,還有后臺切換支付開關(guān)等。
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;
    }

    /**
     * 創(chuàng)建訂單,具體項目具體實現(xiàn)
     */
    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商品數(shù)量:" + "1"
                + "\n資費(fèi)說明:" + "2元";
        builder.setMessage(message);
        builder.setTitle("請確認(rèn)充值信息");
        builder.setPositiveButton("確定",
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(final DialogInterface dialog, int index) {
                        //支付結(jié)果回調(diào)到這里來
                        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、需求開發(fā) - Plugin三方功能插件層

Plugin三方功能插件層分兩部分組成:反射API和具體Plugin插件。反射API是動態(tài)插拔功能插件的關(guān)鍵,可以打包時動態(tài)打?qū)?yīng)的供插件。Plugin插件是具體實現(xiàn)和封裝對應(yīng)的功能層。額外拓展新的功能插件時,繼承Plugin類即可。下面以微信插件為例:

微信功能Plugin:封裝和實現(xiàn)微信的登錄、支付、分享等常見功能
public class WechatPlugin extends Plugin {

    private String TAG = "WechatPlugin";

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

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


    /**
     * 調(diào)用微信登錄接口
     */
    public void wechatLogin(Context context, Map<String,Object> LoginMap, CallBackListener callBackListener){
        
    }


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

    }

    /**
     * 根據(jù)當(dāng)前的生命周期
     * @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;
    }

    /**
     * 調(diào)用微信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、需求開發(fā) - Project項目業(yè)務(wù)層

Project層主要分自定義SDK項目和聚合SDK項目兩大類,自定義SDK是自己實現(xiàn)SDK的功能邏輯;聚合SDK主要是封裝渠道SDK用于游戲的聯(lián)運(yùn)。業(yè)務(wù)需求是不一樣的。相對而已聚合SDK會簡單點(diǎn)。如有新的項目需求,可繼承Project對應(yīng)實現(xiàn)就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;
        }

        //設(shè)置賬號監(jiān)聽
        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層設(shè)置回調(diào)監(jiān)聽 */
    private CallBackListener ApiAccountCallback;

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

    /**
     * 監(jiān)聽AccountManager登錄、切換賬號、綁定、注銷的回調(diào)信息
     */
    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、需求開發(fā) - 項目Channel業(yè)務(wù)層

主要是面向聚合SDK項目,主要封裝渠道的SDK內(nèi)容。如需封裝新的渠道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商品數(shù)量:" + "1"
                + "\n資費(fèi)說明:" + productDesc;
        builder.setMessage(message);
        builder.setTitle("請確認(rèn)充值信息");
        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);
    }
}

項目代碼總結(jié)

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

GameSDK_API層:

   SDK對外接口層:是暴露給CP的接口,底層返回的數(shù)據(jù)格式在這一層轉(zhuǎn)化,該層不參與混淆。
                 所以不要在該層做業(yè)務(wù)邏輯處理,避免被反射調(diào)用修改。

   后續(xù)有新的接口,可以在該層做拓展對外給CP。

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

GameSDK_BeginProject層:

   SDK項目層:是與Api層對接的項目入口層,可以通過修改配置文件 Project_config.txt
   進(jìn)入和替換成不同的Project SDK項目。

   注意:

       頂層Project已經(jīng)確定好后,不要修改太多,如果有拓展的功能接口,
       可以通過extendFunction()對應(yīng)type拓展,避免改動太多底層代碼。


    SDK大體分為兩類:
    1、Project_JuHe為聚合SDK項目,主要業(yè)務(wù)用于封裝三方渠道。

    2、Project_Custom為自定義SDK項目,主要是做自己的渠道,每個公司可能會有不同的名稱,
       主要業(yè)務(wù)是,實現(xiàn)用戶入口、支付邏輯、數(shù)據(jù)統(tǒng)計等功能。

       可能根據(jù)不同的游戲運(yùn)營需求會有不同的登錄邏輯、綁定邏輯、支付方式、數(shù)據(jù)上報等
       會細(xì)分為公版項目、海外項目(針對地區(qū))、根據(jù)游戲的定制化項目等??筛鶕?jù)不同的項目,拓展Project


    該層是做業(yè)務(wù)邏輯處理的,希望能有清晰的架構(gòu)思路。
    
----------------------------------------------------------------------------------------------

SYSDK_Channel: SDK項目渠道層

   該層負(fù)責(zé)對接渠道接入的業(yè)務(wù),不同的渠道都會有對應(yīng)的Module實現(xiàn)具體的代碼調(diào)用和邏輯處理,以及資源配置
   通過配置文件 Channel_config.txt 管理。

   而且每個渠道Module都會有對應(yīng)的開發(fā)者說明文件,方便后續(xù)的開發(fā)同事維護(hù)更新。詳見開發(fā)者說明。

   開發(fā)者格式說明:

      格式: 渠道名 -- 版本號 -- 開發(fā)人員 (如果渠道沒有版本就按V1.0.0來)    日期

                相關(guān)注意事項說明:
                1、.... xxx ....
                2、.... xxx ....


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

SYSDK_Manager:

   SDK邏輯管理層:

      該層為整個SDK功能邏輯的實現(xiàn):初始化、賬號、支付、獲取道具信息、補(bǔ)單邏輯、退出
      為避免邏輯層因業(yè)務(wù)太亂導(dǎo)致代碼過多,及后續(xù)的功能模塊抽離.
      采用模塊化化思想進(jìn)行模塊管理.

      初始化:SDK初始化(全局參數(shù)緩存、環(huán)境切換、權(quán)限問題等) >- 項目初始化(默認(rèn)初始化 和 項目初始化)

      登陸:設(shè)備登陸、游客登錄、賬號登陸、三方登陸(google/facebook/微信)

      支付:三方支付:google、支付寶、微信、及特有的項目支付、補(bǔ)單

      退出:

      當(dāng)有額外的功能添加的時候,在該層實現(xiàn)即可。

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

GameSDK_Manager_Impl:

   SDK邏輯功能拓展反射層,通過反射解耦插件:

        該層更多的是針對后續(xù)Plugin插件做不同的功能反射,具體調(diào)用Plugin層插件。
        只負(fù)責(zé)對接GameSDK_Manager_Impl 和 Plugin插件層。

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

GameSDK_Plugin:

    SDK功能插件層,與渠道層有點(diǎn)類似,通過配置文件 Plugin_config.txt 管理。
    通過邏輯控制層 Manager_Logic_Impl 反射調(diào)用實現(xiàn),把插件拔除不影響。
    可以是三方的功能插件,也可以是自己實現(xiàn)的插件。

    目前為說明功能,用支付寶和微信說明
    Alipay功能插件層:實現(xiàn)Alipay功能,封裝Alipay相關(guān)接口
    Wechat功能插件層:實現(xiàn)Wechat功能,封裝Wechat相關(guān)接口

   當(dāng)有額外的功能添加的時候,在該層實現(xiàn)即可。
   
----------------------------------------------------------------------------------------------

GameSDK_Utils:

   GameSDK_Utils層:該層為整個SDK功能基礎(chǔ)庫:目前分為業(yè)務(wù)基礎(chǔ)庫 和 功能基礎(chǔ)庫,方便后續(xù)將業(yè)務(wù)分離。

      基礎(chǔ)組件:

         1、數(shù)據(jù)緩存、域名配置、項目/插件/渠道管理
            注意:將項目、渠道、插件分別加載的目的:

            是為了快速替換項目Project的入口類

            一個項目Project 可以對應(yīng)多個渠道、多個插件。后續(xù)可以在多渠道、多插件上
            進(jìn)行快速的插拔和后臺開關(guān)的切換渠道。

            不過正常的需求都是一個項目,對應(yīng)零個或一個渠道、一個或多個功能插件


         2、網(wǎng)絡(luò)請求、日志輸出、Gson解析

            注意:網(wǎng)絡(luò)請求,目前封裝的volly (volly是比較輕量級的,主要是為減少SDK包體)
                 日志輸出,目前封裝的logger(logger比較輕量級,主要用于開發(fā)日志信息輸出)

                 第三方庫快速替換方案思路:(方便后續(xù)維護(hù)替換成更好的庫)
                 接口解耦封裝,實現(xiàn)上層業(yè)務(wù)網(wǎng)絡(luò)請求接口不改動,只做封裝層的api調(diào)用即可


   基礎(chǔ)庫不要輕易修改! 不要輕易修改! 不要輕易修改! 不要輕易修改! 不要輕易修改!

---------------------------------------------------------------------------------------
結(jié)語:

關(guān)于手游SDK的客戶端的實現(xiàn)就大體介紹到這里。Demo的地址: 手游SDK框架Demo
聲明下,該Demo只是框架的講解,不具備任何商業(yè)價值。如涉及到糾紛,可不能賴我呀。

關(guān)于打包篇,可移步:
手游SDK — 第五篇(游戲打包篇(上)- 打包系統(tǒng)設(shè)計)
手游SDK — 第六篇(游戲打包篇(中)- 自動化打包)
手游SDK — 第七篇(游戲打包篇(下)- 自動化打包踩坑記錄)

如果覺得我的文章對你有幫助,請隨意贊賞。您的支持將鼓勵我繼續(xù)創(chuàng)作!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容