技术文章
android源码主要开发语言是kotlin。
导入项目代码后,在Android Studio上面看到根目录有两个模块:

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

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

主模块如下:

O2App这个类是Android程序入口类Application。app这个包目录下的都是显示层,包含了所有的Activity和Fragment,按照功能模块来划分目录。中间那些包是一些工具类、服务类、自定义View等等内容。
在app->o2目录下
程序的主显示Activity是LaunchAcitivty,这里执行一些比如连接服务器,自动登录等过程。
然后进入MainActivity,这个就是app的主界面,这个界面里面包含了IndexFragment、NewsFragment、NewContactFragment、AppFragment、SettingsFragment,分别就是主界面中看到的首页、消息页、通讯录页、应用页和设置页。

整个项目主要是用MVP模式进行开发的,打开某一个模块,把里面的页面目录打开就会看到如下结构:

都是由一个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 目录下:

这里主要看下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模块的时候会讲到。
上面的BasePresenterImpl中可以看到我们的服务都从RetrofitClient中生成的。那是一个所有请求封装的实例,用到了一个非常有名的retrofit框架 。具体可以查看下o2_auth_sdk这个模块中的net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.RetrofitClient类。
服务:

在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中是否能找到对应的方法,有就直接用,没有就参考其他方法添加一个。
目前android源码中没有查询模块,这里以这个为例看看如何添加服务,查询的服务名:x_query_assemble_surface ,现在需要做如下步骤:
添加service接口,如前面介绍在net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.service包下添加一个服务接口,比如QueryAssembleSurfaceService :
interface QueryAssembleSurfaceService {
/**
* 执行视图
* 返回视图结果
* @param id:视图标识
*/
@PUT("jaxrs/view/{id}/execute")
fun excuteView(@Path("id") id: String) : Observable<ApiResponse<TestObject>>
}到APIDistributeTypeEnum和APIAssemblesData 分别添加新服务模块
![]()


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

Ok , 这样就完成了后台模块的添加,就可以到对应页面的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
}