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

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

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

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

Manager模塊是SDK的核心模塊,主要負責業(yè)務的功能實現(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;
        //將當前狀態(tài)存儲到全局變量供其他模塊插件使用
        BaseCache.getInstance().put(KeyConfig.IS_INIT,initState);
    }

    public boolean getInitState() {
        return initState;
    }
}
賬號Manager:管理賬號的各個功能接口:登錄、切換賬號掌栅、注銷賬號秩仆、綁定賬號。登錄可細分為設(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; //當前登陸的登陸信息
    private boolean isSwitchAccount = false; //通過標記位來判斷是否是切換賬號按鈕的登錄回調(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; //當前登陸失敗就置為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);
    }


    /**
     * 獲取當前登陸狀態(tài),默認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());

            //將當前狀態(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,"");

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

    }

}
支付Manager:購買管理類枚钓,管理SDK的各個購買功能接口:創(chuàng)建訂單铅搓、三方支付、運營商支付搀捷、渠道支付星掰、補單邏輯、包月嫩舟、訂閱等氢烘。注意可能還會有各個復雜的支付邏輯: 可能會先短代支付、然后渠道支付家厌、三方支付播玖,還有后臺切換支付開關(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資費說明:" + "2元";
        builder.setMessage(message);
        builder.setTitle("請確認充值信息");
        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ū)墓┎寮馈lugin插件是具體實現(xiàn)和封裝對應的功能層果覆。額外拓展新的功能插件時,繼承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ù)當前的生命周期
     * @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è)務層

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

主要是面向聚合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資費說明:" + 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);
    }
}

項目代碼總結(jié)

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

GameSDK_API層:

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

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

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

GameSDK_BeginProject層:

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

   注意:

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


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

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

       可能根據(jù)不同的游戲運營需求會有不同的登錄邏輯裹刮、綁定邏輯、支付方式庞瘸、數(shù)據(jù)上報等
       會細分為公版項目捧弃、海外項目(針對地區(qū))、根據(jù)游戲的定制化項目等擦囊∥ハ迹可根據(jù)不同的項目,拓展Project


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

SYSDK_Channel: SDK項目渠道層

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

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

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

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

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


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

SYSDK_Manager:

   SDK邏輯管理層:

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

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

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

      支付:三方支付:google摆寄、支付寶失暴、微信、及特有的項目支付微饥、補單

      退出:

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

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

GameSDK_Manager_Impl:

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

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

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

GameSDK_Plugin:

    SDK功能插件層黍檩,與渠道層有點類似,通過配置文件 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)接口

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

GameSDK_Utils:

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

      基礎(chǔ)組件:

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

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

            一個項目Project 可以對應多個渠道侧纯、多個插件。后續(xù)可以在多渠道甲脏、多插件上
            進行快速的插拔和后臺開關(guān)的切換渠道眶熬。

            不過正常的需求都是一個項目,對應零個或一個渠道块请、一個或多個功能插件


         2娜氏、網(wǎng)絡請求、日志輸出墩新、Gson解析

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

                 第三方庫快速替換方案思路:(方便后續(xù)維護替換成更好的庫)
                 接口解耦封裝,實現(xiàn)上層業(yè)務網(wǎng)絡請求接口不改動臣疑,只做封裝層的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)系作者
禁止轉(zhuǎn)載驮樊,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者薇正。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市囚衔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雕沿,老刑警劉巖练湿,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異审轮,居然都是意外死亡肥哎,警方通過查閱死者的電腦和手機辽俗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篡诽,“玉大人崖飘,你說我怎么就攤上這事¤九” “怎么了朱浴?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長达椰。 經(jīng)常有香客問我翰蠢,道長,這世上最難降的妖魔是什么啰劲? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任梁沧,我火速辦了婚禮,結(jié)果婚禮上蝇裤,老公的妹妹穿的比我還像新娘廷支。我一直安慰自己,他們只是感情好栓辜,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布酥泞。 她就那樣靜靜地躺著,像睡著了一般啃憎。 火紅的嫁衣襯著肌膚如雪芝囤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天辛萍,我揣著相機與錄音悯姊,去河邊找鬼。 笑死贩毕,一個胖子當著我的面吹牛悯许,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播辉阶,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼先壕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谆甜?” 一聲冷哼從身側(cè)響起垃僚,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎规辱,沒想到半個月后谆棺,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡罕袋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年改淑,在試婚紗的時候發(fā)現(xiàn)自己被綠了碍岔。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡朵夏,死狀恐怖蔼啦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仰猖,我是刑警寧澤捏肢,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站亮元,受9級特大地震影響猛计,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爆捞,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一奉瘤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧煮甥,春花似錦盗温、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至双霍,卻和暖如春砚偶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洒闸。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工染坯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丘逸。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓单鹿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親深纲。 傳聞我的和親對象是個殘疾皇子仲锄,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容