Skip to content

yibaikuai/band-sys-app

Repository files navigation

校标大图   

hhu1

移动应用开发实验报告

学年学期: 2022-2023-02

指导教师: 陈鹏

团队成员: 2062410132黄强、2062410134吴海嘉、2062410115易博坤、2062410110祝星原

目录

1 系统整体设计

2 前端界面设计

2.1 登录界面

2.2 注册界面

2.3 主界面

2.4 实现fragment之间的相互切换

2.5 fragment界面

2.6 详细信息界面

3 模型模块

3.1 整体模块架构

3.2 model功能类编写

3.3 定义后端返回数据字段

3.4 问题与收获

4 控制器模块

4.1 整体模块架构

4.2 网络请求数据处理

4.3 页面更新

4.4 组件传值

4.5 本地存值

4.6 问题与收获

5 后端模块

5.1 api接口

5.2 接口测试

5.3 代码及部署

系统整体设计

易博坤经过前期技术调研,网络请求部分使用okHttp库,并验证可行性,封装简易的okHttpUtils类,配置AppConfig,设置网络请求的BaseUrl,自定义JsonUtils类,封装Gson插件,方便处理数据。明确MVC架构,分配团队任务。

黄强经过前期技术学习,网络请求选择使用okHttp库,编写通用get和post请求封装在OkHttpUtils类里,并负责设计编写model里的各个功能类,设计后端返回数据格式与方法,方便控制器进行数据实例化和获取返回数据。

吴海嘉负责UI界面设计,经过前期的需求分析,认为需要有以下页面:登录界面、注册界面、带底部导航栏的主界面以及可以显示选中项目细节的详情界面,下面是使用绘图工具制作的概念图:

登录界面

注册界面

主界面

前端界面设计

吴海嘉负责前端界面的设计。

登录界面

登录界面如下图所示

使用到的组件有三个textview文本,二个edittext文本框和两个按钮,采用linerlayout布局。当密码输入不符合基本要求时,会在登录界面弹出警告,点击登录按钮,若账户和密码正确,则会跳转到主界面,点击注册按钮,则会跳转至注册界面。

以下是它的布局

注册界面

布局思路大致与登录界面相同,点击注册后会执行注册相关操作并跳转到登录界面。

主界面

在主界面中,使用了fragment进行开发,Fragment 表示应用界面中可重复使用的一部分。fragment 定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。fragment 不能独立存在。它们必须由 activity 或其他 fragment 托管。fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构。

在主界面中,将屏幕区域划分为三块:最上面的工具栏显示一些提示信息,如现在选中的页面的名称,中间的container容器中存放着fragment组件,可以根据不同的选择进行切换,下面的导航栏放置了四个页面选项,对应了四种不同类型的数据:乐队、成员、演唱会以及用户个人信息。

实现fragment之间的相互切换

关于导航栏具体功能的实现,这一部分主要在mainpage.activity文件中,当登录成功后,便开始生成页面,默认进入乐队界面。

下图所示的监听器监听的对象是导航栏上的四个按钮,根据监听到的值的不同可以确定用户按下了哪个按钮,然后根据按下的按钮切换到对应的fragment,实现导航栏的功能。

fragment界面

fragment是一个相对独立的组件,它拥有着自己的布局文件,这里以乐队界面的fragment为例介绍一下。

本软件的fragment组件中布局比较简单,考虑到要实现的功能,里面只有一个listview组件,用于存放查询到的乐队的简略信息,当然,在activity文件中也定义了点击listview中某一个item后的具体反应:会跳转到一个显示选中的乐队的具体信息的页面,相关的代码如下所示:

详细信息界面

不同类型的数据自然对应不同的详细信息,但实际上它们大同小异,还是以乐队的详细信息为例。

这个界面实现了展示选中的对象的详细信息的功能,同时还显示了已有的评论,评论是以scrollview的形式实现的,对于过多的评论,可以在一定的区域内上下滑动查看。

同时还实现了添加新的评论和关注与取关的功能。

模型模块

黄强负责模型部分代码编写。

整体模块架构

Utils部分建立OkHttpUtils类,引入OkHttp,定义其中的get和post请求方法,提供给模型使用,使model中不同功能类只用向后端发送不同url,不需要重复编写重复代码。其中get方法需要提供对应功能的url,创建request对象,传入url,再将request对象传入newcall方法构建回调call对象,最后将call对象加入队列,达到异步get请求效果。因为post需要发送数据给后端,而get只是从后端拉取数据,所以post请求比get请求多了携带信息的一步,不止url,还要传诸如“username”“bandname”的字符串,所以使用HashMap储存要发送的数据,规定储存的数据键名可以自定义,但值只能是String,将数据Json化之后构建request对象并发送。

model部分设计各种功能方法供控制模块调用,api中定义不同请求后端返回相应的数据的格式的类,并提供提取数据方法。

model功能类编写

针对登录、注册、评论、关注、三种信息(乐队、成员、演唱会)的相关操作编写功能,这里以登录和乐队相关操作为例。

首先定义通用的类成员变量和方法

设置监听器

乐队相关操作有获取所有乐队名称,查看某个乐队详情,关注、取关乐队,获取所有关注的乐队。

显然获取所有乐队名称不需要传数据(乐队名)给后端,所有用get请求。BASE_URL代表固定前缀,封装在AppConfig里,不同的只有后面的“/getAllBand”字符串,这样后端收到整个url读取后部的字符串可以知道这个请求是何操作。后面是关于监听器的函数,存储了返回数据。

查看某个乐队详情需要用到post请求,发送乐队名字和用户名字给后端,这两个数据储存在hashmap里,作为参数传进post方法里。

获取用户所关注的乐队只用发送用户名即可。乐队model其他操作类似。

再看登录操作,同样先规定基本成员变量和方法。

传用户名和密码给后端,后端看到url中的login.do知道是登录操作。

其他功能代码类似,不过多赘述。

定义后端返回数据字段

因为控制模块要对返回数据进行Json化方便逻辑操作,这样就要求对返回数据每个字段的类型都知道,所以在api中定义每个功能函数后端返回数据的类型。

比如定义获取所有乐队名称返回的数据类型,封装在BandListResponse里,除了固定的msg和status(后端返回文本和状态等),我们只要定义中间data里是String数组,因为返回多个bandName,储存在数组里。

查询乐队详细信息data数据格式则不同,因为含多个不同类型数据(String和int都有),需要自定义数据结构Band,Band里有成员变量bandName、bandMember、isLove等,还有获取他们的方法getBandName()等等。

简单的返回数据则直接使用String类型即可,如登录请求返回的只有成功还是失败。

其他功能返回的数据字段类似,都进行了相似的定义,不多赘述。

问题与收获

由于多个功能都用到get和post请求,所以后来想到把他封装在OkHttpUtils类里,提高代码复用率,并且一开始并没有使用Response来规定后端返回数据的格式,导致控制模块无法将数据转化为Json格式并获取,之后通过将response数据存入XXXXXResponse对象里,方便读取处理,model功能类里起初回调函数并不起作用,通过查询别人关于Http请求中回调函数的用法代码,发现创建callback对象代码出现错误后排除错误,通过此次移动应用的开发,我了解了MVC架构各个模块的联系与交互,并学到了很多方便快捷的操作,如引入各种现成的库,使用它们定义好的方法来完成自己的功能。

控制器模块

易博坤负责控制器部分代码编写。

整体模块架构

activity和fragment部分分别创建BasicActivity和BasicFragment,内部封装showToast、showToastSync和nagivateTo函数,提供给其他activity和fragment继承,提高代码复用性。

登录页后进入主页,主页下方有四个导航栏,乐队信息,成员信息,演唱会信息和关于我的,都用fragment编写,所有信息都通过ListView排列下来,点击每个item项可进入详细信息页面,这里用activity编写,如乐队详细信息BandDetailActivity、演唱会详细信息ConcertDetailActivity。

网络请求数据处理

网络请求成功获取到的原数据是String类型,不经过处理无法使用,以获取乐队列表的网络请求为例。

这段代码使用JsonUtils类的fromJson()方法将服务器的响应(response)转换成一个BandResponse对象。

JsonUtils类是一个自定义的工具类,用于解析和转化数据格式。根据传入的字符串和类,使用Gson库将字符串转换为一个Java对象。

fromJson()方法接受两个参数:第一个参数是要解析的JSON字符串(response),第二个参数是要将JSON数据解析为的类类型(BandResponse.class)。此处的BandResponse是一个自定义的Java类,用于表示从服务器获取到的乐队响应数据。将解析后的BandResponse对象赋值给变量bandResponse。

之后,就可以调用bandResponse的内部方法获取到响应数据。

页面更新

网络请求获取到数据后,需要将数据更新到页面上。

对于TextView设置值,根据对应xml文件对应TextView组件的id,通过findViewById获取到TextView实例。

使用setText方法设置值。

需要注意的是,需要在网络请求的回调中设置文本值,而在Android应用程序中,非UI线程不允许直接更新UI组件,如果多个线程同时更新UI组件,可能会导致应用程序崩溃或发生其他异常。网络请求运行在子线程,在回调函数中直接使用setText会导致程序崩溃,需要使用Handler类发布到UI线程上执行,handler.post(() -> {…}) 是在UI线程上更新UI组件的操作,使得内部的更新UI组件的操作在UI线程上异步执行。

对于ListView类型的列表设置文本,使用适配器更新页面。

这段代码是将从服务器获取到的乐队评论数据(Data.getBandComment())存储在一个List集合中,然后将该集合作为适配器(ArrayAdapter)的数据源,更新在列表视图(ListView)中展示评论数据。

首先,将乐队评论数据(Data.getBandComment())存储在一个List对象(commentList)中,Arrays.asList()是将数组转换为集合的方法。接着,将适配器(ArrayAdapter)的构造函数中传入:getApplication()作为上下文,android.R.layout.simplelistitem_1是内置的列表项布局,commentList是存储在适配器中的数据源。然后,使用commentLayout.setAdapter(adapter)方法,将适配器设置到列表视图中(commentLayout)。

适配器是一个将数据显示在视图上的桥梁,用于将数据和UI组件进行绑定。以上代码,适配器会将存储在commentList中的乐队评论数据填充到列表视图中,展示出乐队评论的内容。

组件传值

当点击列表项的具体值,进入这个列表项的详细页面,即从fragment组件跳转到activity页面,以点击乐队列表项跳转到其详细页面为例,是BandFragment跳转到BandDetailActivity,当跳转到BandDetailActivity,这个组件在创建时需要根据传递过来的bandName发送网络请求,获取该乐队的详细信息,更新到页面上。需要监控ListView项的点击事件,通过intent进行传值。

创建一个Intent对象,并通过参数传入当前Fragment所属的Activity(getActivity())和目标Activity(BandDetailActivity.class)的Class类型。将乐队名字(bandName)通过putExtra()方法添加为额外参数,然后调用startActivity()方法启动目标Activity(BandDetailActivity)。

在BandDetailActivity中,可以通过getIntent().getStringExtra(“bandName”)获取传递过来的乐队名字,发送网络请求。

本地存值

使用SharedPreferences将用户登录成功后的username保存在本地,获取我喜欢的乐队需要发送用户的username。

登录成功后以键值对的形式保存,可以在应用程序中方便地读取和写入数据。

需要获取时,使用getString(),第一个参数是存储的键名,第二个参数为默认值,如果找不到指定键名的值,getString() 方法将返回默认值。

问题与收获

在本地验证网络请求时,搭建了本地服务在http://localhost:5000/api/xxx,将BaseUrl设置成http://localhost:5000,但是发送网络请求时却无效,后来了解到localhost 是一个特殊的域名,指代的是本地主机,而 Android 设备并不是在本地主机上运行的,而是在移动设备上运行的,可以使用特殊的 IP 地址 10.0.2.2 来代替 localhost。这是因为 Android 模拟器的网络堆栈会将访问这个 IP 地址的请求路由到本地主机上,而不是发到网络。

在Android应用程序中,非UI线程不允许直接更新UI组件,这是因为UI组件不是线程安全的,如果多个线程同时更新UI组件,可能会导致应用程序崩溃或发生其他异常。

在Android中,更新UI组件的操作必须在UI线程(也称为主线程)上执行。如果在非UI线程上执行更新UI组件的操作,需要将该操作发布到UI线程上执行,通常有以下两种方式:

一种方式是使用Handler类或者Looper类将更新UI组件的操作封装成一个Runnable对象,然后将该对象发布到UI线程的消息队列中,使其在UI线程上执行。

另一种方式是使用AsyncTask类,该类提供了方便的方法用于在异步任务后台线程中执行耗时操作,并在UI线程上更新UI组件。AsyncTask内部会自动将UI更新操作发布到UI线程的消息队列中。

后端模块

祝星原负责后端接口部分的编写。

后端部分采用Springboot框架整合Mybatis,使用MySQL数据库,部署在阿里云ECS服务器上。

api接口

http://47.115.216.16:8080

查询所有乐队

/getAllBand

查询喜欢的乐队

/getLoveBand

{"username":"user1"}

查询乐队详情

/getBandInfo

{"bandName":"Beyond"}

关注乐队

/followBand

{"username":"user1","bandName":"Beyond"}

取消关注乐队

/unfollowBand

{"username":"user1","bandName":"Beyond"}

评论乐队

/commentBand

{"bandName":"Beyond","comment":"2"}

评论乐队成员

/commentMember

{"memberName":"黄家驹","comment":"2"}

评论演唱会

/commentPerform

{"performName":"2018冬CM","comment":"2"}

查询成员详情

/getMemberInfo

{"memberName":"黄家驹"}

查询演唱会详情

/getPerformInfo

{"performName":"2018冬CM"}

查询演唱会列表

/getAllPerform

查询歌手列表

/getAllMember

接口测试

登陆接口 注册接口

查询喜欢的乐队

查询乐队详情

关注乐队

取消关注乐队

查询所有乐队

评论乐队 评论乐队成员

评论演唱会

查询成员详情 查询演唱会详情

查询演唱会列表 查询歌手列表

代码及部署

在服务器上启动springboot项目

Navicat远程连接到mysql数据库

整体架构

UserController.java

Dao层

Service层

About

乐队系统app

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages