技术文章

当前位置:首页>帮助手册>技术文章

O2OA安卓客户端源码说明

时间:2022-03-02   
内容导读

本篇说明 O2OA 安卓客户端源码的目录结构与扩展方式。项目以 kotlin 编写,主模块负责界面,独立模块封装后端接口请求,改以源码依赖后改动即可生效。文中介绍程序入口、启动页与主界面的组成,以及首页、消息、通讯录、应用与设置等页面的划分。代码采用界面与业务分离的分层模式,页面由界面、契约与业务处理者构成,公共能力在基类。添加接口时在服务声明中补上路径注解,再到页面业务层调用取数。

android 源码主要开发语言为 kotlin。

目录介绍

导入项目代码后,可在 Android Studio 中看到根目录包含两个模块:

手机源码开发目录.png

app 是项目主目录,o2_auth_sdk是把所有 o2oa 后端的 api 请求都封装到了这个模块中。o2_auth_sdk模块因为开发需要目前没有直接引入到 app 中,而是打成 aar 包放到了 app 模块的 libs 目录下引入的。为了开发方便客户可以修改 app 的 build.gradle 文件中引入外部包的地方,把这个o2_auth_sdk以模块方式引入,这样修改了这个模块中的代码就能实时生效,如下图:

o2_auth_sdk

这个模块内容很少,前面说了主要是封装了 o2oa 后台的一些 api。所以主要关心下,api 包下的代码和一个最外层的O2SDKManager类。O2SDKManager是一个全局对象,里面保存了一些比如当前用户信息啥的全局用到的对象。

1574668413377-7d39fc16-cc0f-4920-954b-2c965ed44944.png

app

主模块如下:

1574668413291-bbb695a3-c7eb-487a-a55f-cffb1ef789a9.png

O2App这个类是 Android 程序入口类 Application。app这个包目录下的都是显示层,包含了所有的 Activity 和 Fragment,按照功能模块来划分目录。中间那些包是一些工具类、服务类、自定义 View 等等内容。

位于 app->o2 目录下

    程序的主显示Activity是LaunchAcitivty,这里执行一些比如连接服务器,自动登录等过程。

    然后进入MainActivity,这个就是app的主界面,这个界面里面包含了IndexFragment、NewsFragment、NewContactFragment、AppFragment、SettingsFragment,分别就是主界面中看到的首页、消息页、通讯录页、应用页和设置页。

1574685520282-92543412-3332-4642-a63e-5080c6c426e0.png

代码结构与功能开发说明

整个项目主要采用 MVP 模式开发。打开任一模块的页面目录,可以看到如下结构:

1574668413400-b887b5aa-30b1-4594-8b38-c47437ddf054.png

每个页面由一个 Activity、一个 Contract、一个 Presenter 组成。Activity 负责页面显示,Contract 定义接口,Presenter 编写业务方法,例如后端请求数据处理等。

Contract 样例:

object PersonContract {
    interface View : BaseView {
        //把查询结果反馈给View
        fun loadPersonInfo(personInfo: PersonJson)
        fun loadPersonInfoFail()
    }

    interface Presenter : BasePresenter<View> {
      //后台请求个人信息
      fun loadPersonInfo(name:String)
    }
}

Presenter 样例:

//实现 PersonContract.Presenter
class PersonPresenter : BasePresenterImpl<PersonContract.View>(), PersonContract.Presenter {
    //后台请求个人信息
    override fun loadPersonInfo(name: String) {
        //人员组织API服务
        getOrganizationAssembleControlApi(mView?.getContext())?.let { service ->
           //人员信息请求
           service.person(name)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(ResponseHandler<PersonJson>({ person ->
                        //请求结果返回给view
                        mView?.loadPersonInfo(person) }),
                     ExceptionHandler(mView?.getContext(), { e -> 
												//错误处理  
                        mView?.loadPersonInfoFail() 
                                                                  }))
        }
    }

}

Activity 样例代码:

//实现PersonContract.View
class PersonActivity : BaseMVPActivity<PersonContract.View, PersonContract.Presenter>(), PersonContract.View {
    //presenter的实现
    override var mPresenter: PersonContract.Presenter = PersonPresenter()
    //activity的view的layout文件
    override fun layoutResId(): Int = R.layout.activity_person_info

    var personId = ""
   //这里初始化View
    override fun afterSetContentView(savedInstanceState: Bundle?) {
        personId = intent.extras?.getString(PERSON_NAME_KEY, "")?:""
        if (TextUtils.isEmpty(personId)) {
            XToast.toastShort(this, "没有传入人员帐号,无法获取人员信息!")
            finish()
            return
        }

        showLoadingDialog()
        //请求人员信息
        mPresenter.loadPersonInfo(personId)
    }
    //返回成功的实现
    override fun loadPersonInfo(personInfo: PersonJson) {
        hideLoadingDialog()
      	tv_person_mobile.text = personInfo.mobile
        tv_person_email.text = personInfo.mail
      .....
  	}
    //返回失败的实现
    override fun loadPersonInfoFail() {
        hideLoadingDialog()
    }
  
}

整体开发结构基本就是上面样例代码的样子,首先 Contract 定义两个接口对象 View 和 Presenter,这两个接口分别对应PersonActivity和PersonPresenter来实现。

这里还会发现PersonActivity和PersonPresenter除了实现两个接口,还有继承了对应的 Base 类。这些 Base 类把一些公共的方法和用法封装了一下,方便使用。这些 Base 类在 app -> base 目录下:

1574668413598-10991ecb-c8a3-4318-9122-dd966b959bab.png

这里主要看下BasePresenterImpl 这里面有所有后台模块获取的封装方法:

/**
     * 人员组织服务
     */
    fun getOrganizationAssembleControlApi(context: Context?): OrganizationAssembleControlAlphaService? {
        return try {
            RetrofitClient.instance().organizationAssembleControlApi()
        }catch (e: Exception) {
            XLog.error("", e)
            if (context!=null) {
                XToast.toastLong(context, "人员组织模块服务异常,请联系管理员!!")
            }
            null
        }
    }

    /**
     * 认证服务
     */
    fun getAssembleAuthenticationService(context: Context?): OrgAssembleAuthenticationService? {
        return try {
            RetrofitClient.instance().assembleAuthenticationApi()
        }catch (e:Exception){
            XLog.error("", e)
            if (context!=null) {
                XToast.toastLong(context, "权限认证模块服务异常,请联系管理员!!")
            }
            null
        }
    }

    /**
     * 热图服务
     */
    fun getHotPicAssembleControlServiceApi(context: Context?): HotpicAssembleControlService? {
        return try {
            RetrofitClient.instance().hotpicAssembleControlServiceApi()
        }catch (e:Exception){
            XLog.error("", e)
            if (context!=null){
                XToast.toastLong(context, "热点图片新闻服务模块异常,请联系管理员!")
            }
            null
        }
    }
.............

所以前面样例代码中获取人员信息的PersonPresenter中直接使用 getOrganizationAssembleControlApi 方法就能获取到人员组织模块,这个模块里面都是人员组织相关的接口,其实就是后台模块http://xxx:20020/x_organization_assemble_control/jest/index.html 这里面的那些接口请求方法。不过没有那么全,只是把用到的一些 api 写进去了。这些请求方法和后台模块那些请求连接怎么联系起来的,后面讲o2_auth_sdk模块的时候会讲到。

o2_auth_sdk 中 API 服务的介绍与开发

上面的BasePresenterImpl中可以看到我们的服务都从RetrofitClient中生成的。那是一个所有请求封装的实例,用到了一个非常有名的retrofit框架。具体可以查看下 o2_auth_sdk 这个模块中的net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient类。

服务:

1574668413623-93215bae-5f01-448d-b095-21c98bc0270c.png

在RetrofitClient中看到生成一个服务对象的代码如下,里面的x_organization_assemble_control对应的就是服务端提供的 api 模块名称:

/**
     * 人员组织管理
     */
    fun organizationAssembleControlApi(): OrganizationAssembleControlAlphaService {
        val url = helper.getAPIDistribute(APIDistributeTypeEnum.x_organization_assemble_control)
        val retrofit = Retrofit.Builder()
                .baseUrl(url)
                .client(o2HttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build()
        return retrofit.create(OrganizationAssembleControlAlphaService::class.java)

    }

获得该服务对象后,即可在 Presenter 中执行服务中定义的请求:

interface OrganizationAssembleControlAlphaService {

    /**
     * 列示顶层组织
     */
    @GET("jaxrs/unit/list/top")
    fun unitListTop() : Observable<ApiResponse<List<UnitJson>>>

    /**
     * 列示某组织下的子组织列表
     * @param unit 组织
     */
    @GET("jaxrs/unit/list/{unit}/sub/direct")
    fun unitSubDirectList(@Path("unit") unit: String): Observable<ApiResponse<List<UnitJson>>>

    
    /**
     * 个人详细信息
     */
    @GET("jaxrs/person/{person}")
    fun person(@Path("person") person: String): Observable<ApiResponse<PersonJson>>
    
    }

如上面定义的 person 这个方法,就是前面样例代码PersonPresenter中的人员信息请求:

         //人员信息请求

          service.person(name)

因此如需添加请求,只需在对应的 Service 中添加方法,在后端 api 文档中找到对应的请求路径,写入方法的相应注解,例如 @GET("jaxrs/person/{person}")。返回对象格式固定为 Observable<ApiResponse>,其中的 xxx 即返回 json 中的 data 对象

如上面举例的x_organization_assemble_control 服务,后台的服务 api 说明地址是: http://xxx:20020/x_organization_assemble_control/jest/index.html 在这里你找到你需要使用的 api,然后看看这个 api 的 path 在OrganizationAssembleControlAlphaService中是否能找到对应的方法,有就直接用,没有就参考其他方法添加一个。

如何在 o2_auth_sdk 模块中添加服务:以视图查询为例

目前 android 源码中没有查询模块,此处以其为例说明如何添加服务。查询的服务名为 x_query_assemble_surface,需要执行以下步骤:

interface QueryAssembleSurfaceService {
    /**
     * 执行视图
     * 返回视图结果
     * @param id:视图标识
     */
    @PUT("jaxrs/view/{id}/execute")
    fun excuteView(@Path("id") id: String) : Observable<ApiResponse<TestObject>>
}
  1. 分别在 APIDistributeTypeEnum 和 APIAssemblesData 中添加新服务模块 

1574668413617-9db78859-cf0a-4806-8a9a-43d81e4b22cc.png1574668413617-9db78859-cf0a-4806-8a9a-43d81e4b22cc.png

1574668413450-ffaa5459-36e1-481a-a183-ebff38d0efc9.png

到RetrofitClient类中添加查询模块的服务生成方法:

1574668414392-715c0de3-2dcf-45a3-af66-cdfe4e1f3291.png

最后把这个服务提供给BasePresenterImpl,让Presenter都可以调用到。

1574668413589-fe1bd5a6-fb7b-4a10-a29c-308638369e18.png

至此完成后台模块的添加,即可在对应页面的 Presenter 中编写业务请求和数据处理逻辑。

这里面关于视图查询返回的结果 json 如下,这里主要关注 data 里面的selectList和grid,selectList是字段列表,grid是展现结果。selectList字段中 column 对应grid结果中的字段名

结果样例:

{
    "type": "success",
    "data": {
        "where": {
            "accessible": false,
            "scope": "all",
            "appInfoList": [
                {
                    "name": "理论文章",
                    "id": "cc9e881f-27e6-47b0-96c0-27c7ec7d05cb"
                }
            ],
            "categoryInfoList": [],
            "dateRange": {
                "year": "",
                "month": "",
                "date": "",
                "season": 0,
                "week": 0,
                "adjust": 0,
                "start": "2018-11-25 15:03:48",
                "completed": "2019-11-25 15:03:48",
                "dateRangeType": "none"
            },
            "creatorPersonList": [],
            "creatorUnitList": [],
            "creatorIdentityList": []
        },
        "runtime": {
            "person": "xadmin",
            "unitList": [],
            "groupList": [],
            "roleList": [],
            "unitAllList": [],
            "identityList": [],
            "filterList": [
                {}
            ],
            "parameter": {},
            "count": 2000,
            "bundleList": []
        },
        "selectList": [
            {
                "orderType": "original",
                "column": "C88679060A600001E5DB1460D330DF40",
                "displayName": "标题",
                "path": "$document.title",
                "id": "C88679060A600001E5DB1460D330DF40",
                "hideColumn": false,
                "code": "",
                "allowOpen": false,
                "isName": false,
                "groupEntry": false
            },
            {
                "orderType": "desc",
                "column": "C8867917216000013819FEB090101705",
                "displayName": "发布时间",
                "path": "$document.publishTime",
                "id": "C8867917216000013819FEB090101705",
                "hideColumn": false,
                "code": "",
                "allowOpen": false,
                "isName": false,
                "groupEntry": false
            },
            {
                "orderType": "original",
                "column": "C886791B4590000139A81E41125010B7",
                "displayName": "拟稿人",
                "path": "$document.creatorPerson",
                "id": "C886791B4590000139A81E41125010B7",
                "hideColumn": false,
                "code": "",
                "allowOpen": false,
                "isName": true,
                "groupEntry": false
            }
        ],
        "filterList": [],
        "customFilterList": [
            {
                "title": "标题",
                "value": "",
                "otherValue": "",
                "path": "$document.title",
                "formatType": "textValue",
                "logic": "and",
                "comparison": "equals"
            }
        ],
        "orderList": [
            {
                "orderType": "desc",
                "column": "C8867917216000013819FEB090101705",
                "displayName": "发布时间",
                "path": "$document.publishTime",
                "id": "C8867917216000013819FEB090101705",
                "hideColumn": false,
                "code": "",
                "allowOpen": false,
                "isName": false,
                "groupEntry": false
            }
        ],
        "columnList": [],
        "grid": [
            {
                "bundle": "f407c1a4-d9b6-44c8-bc73-2fd33b5f1e39",
                "data": {
                    "C88679060A600001E5DB1460D330DF40": "这是一个测试",
                    "C8867917216000013819FEB090101705": "2019-07-20 16:28:29",
                    "C886791B4590000139A81E41125010B7": "金飞"
                }
            },
            {
                "bundle": "db13db1e-03a7-4e73-9847-a262af60d186",
                "data": {
                    "C88679060A600001E5DB1460D330DF40": "习近平:增强推进党的政治建设的自觉性和坚定性",
                    "C8867917216000013819FEB090101705": "2019-07-17 20:22:26",
                    "C886791B4590000139A81E41125010B7": "金飞"
                }
            },
            {
                "bundle": "5f09c684-e8d3-4890-a64d-2ab3c31de494",
                "data": {
                    "C88679060A600001E5DB1460D330DF40": "他爬坡穿林 就是为了看看这道屏障",
                    "C8867917216000013819FEB090101705": "2019-07-17 19:10:06",
                    "C886791B4590000139A81E41125010B7": "金飞"
                }
            }
        ],
        "exportGrid": true
    },
    "message": "",
    "date": "2019-11-25 15:03:48",
    "spent": 674,
    "size": -1,
    "count": 0,
    "position": 0
}

相关问题讨论

关于 O2OA 作为 OAUTH2.0客户端对接 OAUTH2.0服务端配置的问题

这个模块内容很少,前面说了主要是封装了 o2oa 后台的一些 api。

查看原帖讨论 · 9,610 次浏览

o2oa 安装 sqlserver2008r2后无法启动

这个模块内容很少,前面说了主要是封装了 o2oa 后台的一些 api。

查看原帖讨论 · 10,075 次浏览

相关内容

O2OA 翱途 企业应用开发平台

兰德网络O2OA平台软件

开源、自主可控的协同办公底座

研发与运营

纵横翱途(杭州)科技有限公司

企业级协同应用 · 工作流引擎 · AI 智能体

www.o2oa.net | 400-888-0545

上一篇:启动OpenJPA日志

下一篇:在O2OA 平台使用自定义应用开发webservice服务