技术文章
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 }