Java客户端代码架构的设计

客户端类需要与服务器端进行通信,也需要操作sqlite数据库,我们应该定义不同的类型的类完完成这些职责,降低耦合度。本文将介绍以下内容:“Activity类(界面类)”、“fragment类(界面类)”、“HttpBO类(向服务器发送请求的类)”、“HttpEvent类(封装向服务器发送请求的事件类型)”、“SQLiteBO类(向客户端数据库发送请求的类)”、“SQLiteEvent类(封装向客户端数据库发送请求的事件类型)”、“Presenter类(操作客户端数据库的类)”、“BaseEvent(httpEven和sqliteEvent的父类)”。

1、Activity类(界面类)

Activity类继承了android.support.v7.app.AppCompatActivity类:

public abstract class BaseActivity extends AppCompatActivity {

与前端xml文件绑定,显示组件:

@BindView(R.id.forget_password)
TextView forgetPassword;
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.login1);
	…… 

监听组件事件:

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.login: //点击登录按钮
            Date date = new Date(System.currentTimeMillis() + NtpHttpBO.TimeDifference);

初始化Presenter、HttpBO、HttpEvent、SQLiteBO、SQLiteEvent对象:

private static CompanySQLiteEvent companySQLiteEvent = null;
private static CompanyHttpEvent companyHttpEvent = null;
private static CompanySQLiteBO companySQLiteBO = null;
private static CompanyHttpBO companyHttpBO = null;

private StaffPresenter staffPresenter;
…… 

if (companySQLiteEvent == null) {
    companySQLiteEvent = new CompanySQLiteEvent();
    companySQLiteEvent.setId(BaseEvent.EVENT_ID_LoginActivity);
}
if (companyHttpEvent == null) {
    companyHttpEvent = new CompanyHttpEvent();
    companyHttpEvent.setId(BaseEvent.EVENT_ID_LoginActivity);
}
if (companySQLiteBO == null) {
    companySQLiteBO = new CompanySQLiteBO(GlobalController.getInstance().getContext(), companySQLiteEvent, companyHttpEvent);
}
if (companyHttpBO == null) {
    companyHttpBO = new CompanyHttpBO(GlobalController.getInstance().getContext(), companySQLiteEvent, companyHttpEvent);
}
companySQLiteEvent.setSqliteBO(companySQLiteBO);
companySQLiteEvent.setHttpBO(companyHttpBO);
companyHttpEvent.setSqliteBO(companySQLiteBO);
companyHttpEvent.setHttpBO(companyHttpBO);
……

监听EventBus发送的消息,关于httpEvent和sqliteEvent事件的:

@Subscribe(threadMode = ThreadMode.MAIN)
public void onPosHttpEvent(PosHttpEvent event) {
    if (event.getId() == BaseEvent.EVENT_ID_LoginActivity) {
	…… 

@Subscribe(threadMode = ThreadMode.MAIN)
public void onPosSQLiteEvent(PosSQLiteEvent event) {
    if (event.getId() == BaseEvent.EVENT_ID_LoginActivity) {
	……

发送Handler消息:

Message message1 = new Message();
message1.what = 1;
handler.sendMessage(message1);

private Handler handler = new Handler() {
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what) {
            case 1:
                closeLoadingDialog(loadingDailog);
                if (inputCompanySNDialog != null) {
                    inputCompanySNDialog.dismiss();
                }
			……

2、fragment类(界面类)

fragment类继承了android.support.v4.app.Fragment:

public class BaseFragment1 extends Fragment 

可以使用fragment来创建和组合界面,来满足UI界面的需求。

它的主要组成和activity类似,可以查看activity类介绍。

3、HttpBO类(向服务器发送请求的类)

BaseHttpBO是抽象类,是其它HttpBO类的父类,主要存放一些HttpBO的公共方法和常量:

public abstract class BaseHttpBO {
    private Logger log = Logger.getLogger(this.getClass());
    protected final long TIME_OUT = GlobalController.HTTP_REQ_Timeout * 1000;

    public static final String ERROR_MSG_Network = "网络错误,请检查网络连接!";
public BaseHttpEvent getHttpEvent() {
    return httpEvent;
}

public void setHttpEvent(BaseHttpEvent httpEvent) {
    this.httpEvent = httpEvent;
}

protected BaseHttpEvent httpEvent;

发送请求前,参数的格式检查和session的有效期检查:

public boolean createNSync(int iUseCaseID, List<BaseModel> list) {
    if (list != null){
        for(BaseModel bm : list){
            if (!checkCreate(iUseCaseID, bm)){
                return false;
            }
        }
    }
    // 判断SessionID是否为null,如果为null,返回false
    if (GlobalController.getInstance().getSessionID() == null) {
        httpEvent.setLastErrorCode(ErrorInfo.EnumErrorCode.EC_InvalidSession);
        return false;
    }
    return doCreateNSync(iUseCaseID, list);
}

具体的ModelHttpBO负责向服务器发送Http请求:

@Override
protected boolean doRetrieveNAsync(int iUseCaseID, BaseModel bm) {
    log.info("正在执行CommodityHttpBO的retrieveNAsync,bm=" + (bm == null ? null : bm.toString()));

    httpEvent.setEventProcessed(false);

    Request req = new Request.Builder()
            .url(Configuration.HTTP_IP + Commodity.HTTP_Commodity_RETRIEVEN)
            .addHeader(BaseHttpBO.COOKIE, GlobalController.getInstance().getSessionID())
            .build();
    HttpRequestUnit hru = new RetrieveNCommodity();
    hru.setRequest(req);
    hru.setTimeout(TIME_OUT);
    hru.setbPostEventToUI(true);
    httpEvent.setStatus(BaseEvent.EnumEventStatus.EES_Http_ToDo);
    hru.setEvent(httpEvent);
    HttpRequestManager.getCache(HttpRequestManager.EnumDomainType.EDT_Communication).pushHttpRequest(hru);

    log.info("正在请求需要同步的Commodity");

    return true;
}

4、HttpEvent类(封装向服务器发送请求的事件类型)

BaseHttpEvent是其它HttpEvent的父类:

public class BaseHttpEvent extends BaseEvent

封装服务器返回的数据:

public String getResponseData() {
    return responseData;
}

public void setResponseData(String responseData) {
    this.responseData = responseData;
}

protected String responseData;


设置向服务器请求的类型:

public void setRequestType(HttpRequestUnit.EnumRequestType requestType) {
    this.requestType = requestType;
}

public HttpRequestUnit.EnumRequestType getRequestType() {
    return requestType;
}

protected HttpRequestUnit.EnumRequestType requestType;

/**
 * RetrieveNC,后面带C的请求的是普通Action。不带C请求的是SyncAction
 */
public enum EnumRequestType {
    ERT_RetailTrade_Create("ERT_RetailTrade_Create", 0), //
    ERT_Barcodes_RetrieveN("ERT_Barcodes_RetrieveN", 1),
	…… 

设置错误码和错误信息:

public ErrorInfo.EnumErrorCode getLastErrorCode() {
    return lastErrorCode;
}

public void setLastErrorCode(ErrorInfo.EnumErrorCode lastErrorCode) {
    this.lastErrorCode = lastErrorCode;
}

public String getLastErrorMessage() {
    return lastErrorMessage;
}

public void setLastErrorMessage(String lastErrorMessage) {
    this.lastErrorMessage = lastErrorMessage;
}

解析服务器返回的数据:

public JSONObject parseError(String responseData) {
    try {
        log.info("服务器返回的数据是:" + responseData);
        JSONObject jsonObject = new JSONObject(responseData);
        String sErrCode = jsonObject.getString(BaseModel.JSON_ERROR_KEY);
        setLastErrorCode(ErrorInfo.EnumErrorCode.valueOf(sErrCode));
        if (getLastErrorCode() != ErrorInfo.EnumErrorCode.EC_NoError) {
            String sErrorMsg = (String) jsonObject.get(BaseModel.KEY_HTMLTable_Parameter_msg);
            setLastErrorMessage(sErrorMsg);
        }
        if (getLastErrorCode() == ErrorInfo.EnumErrorCode.EC_DuplicatedSession) {
            HttpRequestStatus = 1;
            HttpRequestWarnMsg = WARN_Msg_DuplicatedSession;
        }
        return jsonObject;
    } catch (Exception e) {
        e.printStackTrace();

        // 会话过期时,服务器返回的是一个URL,POS端解析会失败。但可从其头部的sessionStatus知道是不是会话过期
        String header = getResponse().header("sessionStatus");
        if (header != null && header.equals("timeOut")) {
            lastErrorCode = ErrorInfo.EnumErrorCode.EC_SessionTimeout;
            HttpRequestStatus = 1;
            HttpRequestWarnMsg = WARN_Msg_SessionTimeOut;
        } else {
            setLastErrorCode(ErrorInfo.EnumErrorCode.EC_OtherError);
        }

        return null;
    }
}

具体model的HttpEvent的关键方法是event,它负责根据不同的请求类型做不同的处理:

@Override
public void onEvent() {
    StringUtils.printCurrentFunction("getRequestType", getRequestType());

    do {
        if (lastErrorCode != ErrorInfo.EnumErrorCode.EC_NoError) {
            log.info("网络请求错误," + lastErrorCode);
            break;
        }
        //
        JSONObject jsonObject = parseError(getResponseData());
        if (jsonObject == null || getLastErrorCode() == ErrorInfo.EnumErrorCode.EC_DuplicatedSession || getLastErrorCode() != ErrorInfo.EnumErrorCode.EC_NoError) {
            break;
        }

        //
        switch (getRequestType()) {
            case ERT_Commodity_Create:
                handleCreate(jsonObject);
                break;
            case ERT_Commodity_RetrieveN:
                handleRetrieveN(jsonObject);
                break;
			……	

5、SQLiteBO类(向客户端数据库发送请求的类)

BaseSQLiteBO是其它SQLiteBO的父类,存放公共的常量和方法:

public abstract class BaseSQLiteBO {
    private Logger log = Logger.getLogger(this.getClass());

    public static int INVALID_INT_ID = -1;
    public static long INVALID_ID = -1l;
    public static int INVALID_CASE_ID = -1;
    public static int INVALID_STATUS = -1;
    public static final int INVALID_Type = -1;
    public static final int INVALID_NO = -1;
	……
/**
 * 向SQLite发送异步DB操作请求:创建对象
 */
public abstract boolean createAsync(int iUseCaseID, final BaseModel bm);

/**
 * 向SQLite发送同步DB操作请求:创建对象
 */
public BaseModel createSync(int iUseCaseID, final BaseModel bm) {
    throw new RuntimeException("Not yet implemented!");
}

具体Model类的SQLiteBO调用Presenter类的接口,操作数据库:

  @Override
    public boolean createAsync(int iUseCaseID, BaseModel bm) {
        log.info("正在执行CommoditySQLiteBO的createAsync,bm=" + (bm == null ? null : bm.toString()));

        switch (sqLiteEvent.getEventTypeSQLite()) {
            case ESET_Commodity_CreateAsync:
//                sqLiteEvent.setEventTypeSQLite(BaseSQLiteEvent.EnumSQLiteEventType.ESET_Commodity_CreateAsync);
                if (GlobalController.getInstance().getCommodityPresenter().createObjectAsync(iUseCaseID, bm, sqLiteEvent)) {
                    return true;
                } else {
                    log.info("创建commodity失败!");
                }
                break;
            default:
                log.info("未定义的事件!");
                throw new RuntimeException("未定义的事件!");
        }
        return false;
    }

6、SQLiteEvent类(封装向客户端数据库发送请求的事件类型)

BaseSQLiteEvent是其它SQLiteEvent的父类,存放一些公共常量和方法。

定义枚举sqlite的事件类型:

public class BaseSQLiteEvent extends BaseEvent {
    private static int INDEX = 0;

    public enum EnumSQLiteEventType {
        ESET_Commodity_CreateAsync("ESET_Commodity_CreateAsync", INDEX++), //
        ESET_Commodity_CreateNAsync("ESET_Commodity_CreateNAsync", INDEX++), //
        ESET_Commodity_UpdateAsync("ESET_Commodity_UpdateAsync", INDEX++), //
        ESET_Commodity_Retrieve1Async("ESET_Commodity_Retrieve1Async", INDEX++), //
        ESET_Commodity_RetrieveNAsync("ESET_Commodity_RetrieveNAsync", INDEX++),
		……
public EnumSQLiteEventType getEventTypeSQLite() {
    return eventTypeSQLite;
}

public void setEventTypeSQLite(EnumSQLiteEventType eventTypeSQLite) {
    this.eventTypeSQLite = eventTypeSQLite;
}

protected EnumSQLiteEventType eventTypeSQLite;

具体Model的SQLiteEvent,主要方法也是event,根据不同sqlite事件类型做不同处理:

public class CommoditySQLiteEvent extends BaseSQLiteEvent {
    private Logger log = Logger.getLogger(this.getClass());

    public CommoditySQLiteEvent() {
    }

    @Override
    public void onEvent() {
        StringUtils.printCurrentFunction("eventTypeSQLite", eventTypeSQLite);

        switch (eventTypeSQLite) {
            case ESET_Commodity_CreateNAsync:
                if (lastErrorCode == ErrorInfo.EnumErrorCode.EC_NoError) {
                    log.info("成功插入commodity的List!");
                } else {
                    log.info("插入commodity的List失败!");
                    lastErrorCode = ErrorInfo.EnumErrorCode.EC_OtherError;
                }
                status = EnumEventStatus.EES_SQLite_Done;
                setEventProcessed(true);
                break;
            case ESET_Commodity_CreateAsync:
                if (lastErrorCode == ErrorInfo.EnumErrorCode.EC_NoError) {
                    log.info("成功插入commodity");
                } else {
                    log.info("插入commodity失败!");
                    lastErrorCode = ErrorInfo.EnumErrorCode.EC_OtherError;
                }
                status = EnumEventStatus.EES_SQLite_Done;
                setEventProcessed(true);
                break;

7、Presenter类(操作客户端数据库的类)

BasePresenter类是其它Presenter类的父类,存放一些公共方法和常量:

/**
 * 本类提供同步接口和异步接口。<br />
 * 提供异步接口是为了保证Android界面的流畅性。<br />
 * 同步接口大部分在测试代码中使用。异步接口大部分在非测试代码中使用。<br />
 */
public class BasePresenter {
    private Logger log = Logger.getLogger(this.getClass());

    public static final String SYNC_Type_C = "C";    //创建型同步块
    public static final String SYNC_Type_U = "U";    //更新型同步块
    public static final String SYNC_Type_D = "D";    //删除型同步块
	……

定义错误码和错误信息:

public ErrorInfo.EnumErrorCode getLastErrorCode() {
    return lastErrorCode;
}

protected ErrorInfo.EnumErrorCode lastErrorCode;

public String getLastErrorMessage() {
    return lastErrorMessage;
}

protected String lastErrorMessage;

定义由子类实现的方法:

protected List<?> createNSync(int iUseCaseID, final List<?> list) {
    throw new RuntimeException("Not yet implemented!");
}

参数格式的检查:

public BaseModel createObjectSync(int iUseCaseID, final BaseModel bm) {
    String err = bm.checkCreate(iUseCaseID);
    if (err.length() > 0) {
        Constants.checkModelLog(log, bm, err);
        lastErrorCode = ErrorInfo.EnumErrorCode.EC_WrongFormatForInputField;
        lastErrorMessage = err;
        return null;
    }
    return createSync(iUseCaseID, bm);
}

具体Model类的Presenter负责操作对应的数据库表:

@PerActivity
public class CommodityPresenter extends BasePresenter {
    private Logger log = Logger.getLogger(this.getClass());

    @Inject
    public CommodityPresenter(final DaoSession dao) {
        super(dao);
    }

    @Override
    protected String getTableName() {
        return dao.getCommodityDao().getTablename();
    }

    @Override
    protected List<BaseModel> createNSync(int iUseCaseID, final List<?> list) {
        log.info("正在进行CommodityPresenter的createNSync,list=" + (list == null ? null : list.toString()));

        switch (iUseCaseID) {
            default:
                try {
                    for (int i = 0; i < list.size(); i++) {
                        ((Commodity) list.get(i)).setSyncDatetime(new Date(System.currentTimeMillis() + NtpHttpBO.TimeDifference));
                        ((Commodity) list.get(i)).setSyncType(BasePresenter.SYNC_Type_C);
                        long id = dao.getCommodityDao().insert((Commodity) list.get(i));
                        ((Commodity) list.get(i)).setID(id);
                    }
                    lastErrorCode = ErrorInfo.EnumErrorCode.EC_NoError;
                } catch (Exception e) {
                    log.info("执行createNSync失败,错误信息=" + e.getMessage());
                    e.printStackTrace();
                    lastErrorCode = ErrorInfo.EnumErrorCode.EC_OtherError;
                }
                return (List<BaseModel>) list;
        }
    }

8、BaseEvent(httpEven和sqliteEvent的父类)

BaseEvent是BaseHttpEvent和BaseSQLiteEvent的父类,定义一些公共常量和方法:

public class BaseEvent {
    private static int INDEX = 0;

    /**
     * 不同的事件源/界面发出的事件应该作不同的处理。用以下变量区分不同的事件源
     * 比如SynThread收到同步后服务器发回的零售单,而MainActivity上用户查旧单服务器也返回了零售单,双方的事件处理都是onRetailTradeSQLiteEvent(),会引起混乱。
     * 混乱表现在:
     * 1、是拿前者的数据还是后者的数据显示在查单界面上?
     * 2、前者可能有串行的事件1和2。1在修改查单界面的列表时,2也可能的修改查单界面的列表,这会引起ConcurrentModificationException。
     * 目前所有的Event默认的ID都是0,所以EVENT_ID_SyncThread必定不能从0计起,这里从1计起。
     */
    public static final int EVENT_ID_SyncThread = 1;
    public static final int EVENT_ID_MainActivity = EVENT_ID_SyncThread + 1;
    public static final int EVENT_ID_SyncDataActivity = EVENT_ID_SyncThread + 2;

定义枚举Event的状态:

public enum EnumEventStatus {
    /**
     * 准备操作SQLite
     */
    EES_SQLite_ToDo("EES_SQLite_ToDo", INDEX++),
    /**
     * 操作SQLite完毕。操作成功与否未知
     */
    EES_SQLite_Done("EES_SQLite_Done", INDEX++),
	……

定义常用属性:

/**
 * 事件处理后,记录的错误码
 */
protected ErrorInfo.EnumErrorCode lastErrorCode;

protected String lastErrorMessage;
/**
 * 事件处理完后,用来存储主表信息
 */
protected List<?> listMasterTable;
/**
 * 事件处理完后,用来存储从表信息
 */
protected List<?> listSlaveTable;
/**
 * 事件处理完后,用来存储一个对象
 */
protected BaseModel baseModel1;
/**
 * 职责:UI收到底层或其它地方post的event后,会在@Subcribe函数中处理事件。
 * 其中,onEvent处理所有和UI无关的事情,比如设置status/lastErrorCode/eventProcessed/触发httpBO发起网络请求,和UI有关的全部留给@Subcribe函数的其它代码处理
 */
public void onEvent() {
    throw new RuntimeException("Not yet implemented!");
}
/**
 * 当事件被UI层处理完后,此值为true,否则为false。
 */
protected boolean isEventProcessed;
/**
 * 临时主表对象。其内有从表指针。用于异步事件流处理:异步插入主表时,在插入事件中可以再触发事件插入从表信息,这时需要知道要插入的从表的信息。从本变量中就能得知
 */
protected BaseModel tmpMasterTableObj;
/**
 * 用于异步事件流处理。异步成功插入主表后,需要再插从表。这时需要知道相应的BO
 */
protected BaseSQLiteBO sqliteBO;
/**
 * 用于异步事件流处理。异步成功插入主表从表后,需要再发出http请求。这时需要知道相应的BO
 */
protected BaseHttpBO httpBO;
/**
 * 在Event post给UI之前,有的信息需要带给@Subcribe函数,用来提示用户一些注意事项或下一步动作。这个信息可以记在本变量中。
 */
protected String messageBeforeEventPosted;
/**
 * 本地同步服务器相应的某个类型数据。有C、U、D三种类型
 */
protected String syncType;
/**
 * 实体类的查询或同步时的分页页码,代表开始页或结束页,不代表其它页码
 */
protected String pageIndex;

public String printErrorInfo() {
    return "错误码=" + lastErrorCode + "\t错误信息=" + lastErrorMessage;
}

public String printErrorInfo(Object param) {
    return "错误码=" + lastErrorCode + "\t错误信息=" + lastErrorMessage + "\t参数:" + param;
}
……
posted @ 2021-12-22 09:52  Boxin-kim  阅读(162)  评论(0)    收藏  举报
Web Analytics
Guang Zhou Boxin