2024秋软件工程结对作业(第二次之程序实现)
这个作业属于哪个课程 | 软件工程2024 |
---|---|
这个作业要求在哪里 | 2024秋软件工程结对作业(第二次之程序实现) |
这个作业的目标 | 将第一次结对作业中的原型Find Your Team进行程序实现,并打包为apk文件 |
学号 | 102201427 |
结对成员姓名 | 学号 |
---|---|
侯丽珂 | 102201427 |
郑嘉祺 | 102201426 |
项目开发博客: Find Your Team (FYT)
欢迎阅读我们的项目博客,本篇将详细介绍我们开发的APP——Find Your Team(FYT)。FYT的目的是帮助校园内不同身份的用户找到合适的项目团队成员,促进合作与交流。以下是项目的详细信息和开发过程展示。
一、项目链接与结对信息
- 结对同学博客链接: 102201426郑嘉祺的博客
- 我的博客链接: 102201427侯丽珂的博客
- GitHub项目地址: FYT项目仓库
二、分工说明
侯丽珂 102201427
- 界面设计与页面美化
- 负责首页、我的、人才林、项目总库等UI组件的设计。
- 查找图片等资源并进行页面设计美化。
- 前端实现
- 部分前端代码实现,包括按钮点击、动态页面跳转等交互效果。
- 打包及撰写博客
- 负责项目的打包和发布流程,撰写博客。
郑嘉祺 102201426
- 前端实现
- 负责注册登入、认证、创建项目等UI组件的前端实现。
- 实现对应页面的按钮点击、动态页面跳转等交互效果。
- 后端开发
- 负责后端逻辑和API的开发,包括用户认证和项目管理。
- 数据库管理
- 管理项目总库和人才的数据库,确保数据的一致性和安全性。
共同完成
- 项目集成
- 集成前端和后端,确保所有功能模块的无缝衔接。
- 功能测试
- 进行全面的单元测试和集成测试,确保应用的稳定性和可靠性。
三、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 100 | 90 |
Estimate | 估计这个任务需要多少时间 | 10 | 20 |
Development | 开发 | 4500 | 4100 |
- Analysis | 需求分析 (包括学习新技术) | 600 | 1500 |
- Design Spec | 生成设计文档 | 400 | 200 |
- Design Review | 设计复审 | 200 | 30 |
- Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 100 | 70 |
- Design | 具体设计 | 600 | 100 |
- Coding | 具体编码 | 1800 | 2000 |
- Code Review | 代码复审 | 300 | 100 |
- Test | 测试(自我测试,修改代码,提交修改) | 500 | 100 |
Reporting | 报告 | 200 | 240 |
- Test Report | 测试报告 | 130 | 90 |
- Size Measurement | 计算工作量 | 10 | 10 |
- Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 140 |
合计 | 4810 | 4450 |
四、解题思路与设计实现说明
解题思路
1 流程分析
-
需求分析
- 本项目FYT的核心功能旨在为用户提供全方位的人才搜索服务,同时支持用户查看与创建项目、浏览现有项目以及个人信息管理。这些功能将为用户在寻找合适的人才及项目管理方面提供便捷的解决方案。
-
设计阶段
- 在设计阶段,我们将规划应用程序的整体功能结构和用户体验,着重设计用户界面和导航布局。具体考虑如下:
- 用户项目管理:设计合理的项目管理模块,允许用户轻松进行项目的创建、修改和删除操作。
- 个人仓库与项目总库的增删查找:实现用户个人仓库与项目总库之间的信息增、删、查操作,确保用户能够便捷地管理其项目和数据。
- 身份认证机制:设计不同用户的身份认证流程,通过安全的认证机制确保用户信息的安全性与隐私保护。
- 个性化标签设置:允许用户根据个人喜好设置个性化标签,以便于对项目和人才进行分类和快速检索。
- 在设计阶段,我们将规划应用程序的整体功能结构和用户体验,着重设计用户界面和导航布局。具体考虑如下:
-
实现阶段
- 在实现阶段,我们将使用Android Studio作为开发环境,并采用内置的Room数据库进行数据管理。此设计选择确保了应用程序的跨平台兼容性,提升了用户的使用体验,并保证应用在不同设备上的稳定性。
-
测试阶段
- 在测试阶段,将进行全面的单元测试,以确保各个功能模块顺利运行。测试将覆盖所有核心功能,以验证其稳定性和可靠性,并确保在实际使用过程中无重大问题。
2 结构设计
- 前端结构:通过Android Studio的布局文件.xml搭建,分为登录、首页、项目总库、人才林、聊天、我的、设置等模块。
- 后端设计:目录结构如下,展示了后端数据库的设计,包括数据实体、数据访问对象、数据库配置文件,以及业务逻辑层和数据展示层的类文件。这种设计模式遵循了分层架构的原则,使得代码结构清晰,逻辑分离,便于维护和扩展。数据库层与业务逻辑层的分离,确保了数据库操作的可测试性和可复用性。Room库的使用进一步简化了SQLite的操作,提高了开发效率。
数据库目录结构
com/example/fyt/
├── adapters/
│ └── ProjectAdapter.java
├── database/
│ ├── Project.java
│ ├── ProjectDao.java
│ └── AppDatabase.java
├── CreateprojectActivity.java
├── StoreActivity.java
└── ProjectDetailActivity.java
详细说明
-
数据库层(
database
目录)-
Project.java
:- 类型: 数据实体类(Entity)
- 描述: 定义了项目(Project)的属性及其与数据库表的映射关系。项目类通常包含项目的名称、描述、创建时间、截止日期等字段,用于表示数据库中的一个表。
-
ProjectDao.java
:- 类型: 数据访问对象(DAO)
- 描述: 定义了对项目数据的增删改查操作(CRUD操作)的接口。DAO层负责与数据库进行交互,封装了具体的SQL操作,使得业务逻辑层(Activity)无需直接操作数据库。
-
AppDatabase.java
:- 类型: 数据库配置类
- 描述: 定义了应用程序的数据库配置,包括数据库的名称、版本号、数据表的创建与更新语句等。AppDatabase类通常使用Room库来创建和管理数据库实例。
-
-
Room库简介
- Room库:
- 类型: Android数据库库(ORM库)
- 描述: Room是Android官方提供的SQLite对象关系映射库(ORM),它简化了SQLite数据库的操作。通过Room,开发者可以更轻松地定义数据实体、数据访问对象和数据库配置,从而减少手动编写SQL语句的工作量。
- 主要组件:
- Entity: 用于定义数据库表的结构,通常通过注解(如
@Entity
)来标记Java类。 - DAO: 定义数据库操作的接口,使用注解(如
@Insert
、@Query
、@Delete
)来标记方法。 - Database: 定义数据库的配置,使用
@Database
注解来标记类,并指定包含的Entity和DAO。
- Entity: 用于定义数据库表的结构,通常通过注解(如
- Room库:
-
业务逻辑层(Activity)
-
CreateprojectActivity.java
:- 类型: 创建项目业务逻辑类
- 描述: 负责处理创建新项目的业务逻辑。该类通常包含用户输入的获取、数据验证、以及调用
ProjectDao
接口将新项目数据保存到数据库中的逻辑。
-
StoreActivity.java
:- 类型: 存储业务逻辑类
- 描述: 负责处理与项目存储相关的业务逻辑。可能涉及将项目数据从本地数据库或网络存储中读取、保存、更新等操作。
-
ProjectDetailActivity.java
:- 类型: 项目详情业务逻辑类
- 描述: 负责处理项目详情的展示与操作。该类通常根据项目ID从数据库中读取项目信息,展示给用户,并处理用户对项目详情的编辑、删除等操作。
-
-
数据展示层(
adapters
目录)ProjectAdapter.java
:- 类型: 适配器类
- 描述: 负责将项目数据适配到RecyclerView或其他UI组件中进行展示。ProjectAdapter类通常会与
Project
对象关联,将数据绑定到UI元素上,并处理用户的交互事件。
3 关键实现的流程图
设计实现说明
1 关键代码
代码展示
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.store), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "projects-database").build();
projectDao = db.projectDao();
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
new Thread(() -> {
List<Project> projects = projectDao.getAllProjects();
runOnUiThread(() -> {
projectAdapter = new ProjectAdapter(projects, this::openProjectDetails);
recyclerView.setAdapter(projectAdapter);
});
}).start();
}
private void openProjectDetails(Project project) {
Intent intent = new Intent(this, ProjectDetailActivity.class);
intent.putExtra("projectName", project.name);
intent.putExtra("projectMember", project.members);
intent.putExtra("projectDescription", project.description);
intent.putExtra("imageUri", project.imagePath);
startActivity(intent);
}
}
功能分析
这段代码实现了项目列表页面,用户可以在列表中看到数据库中的所有项目,并且可以通过点击列表项跳转到项目的详细信息页面。代码中还包括了对系统栏变化的响应处理,以确保内容在不同设备和不同状态栏配置下都能正确显示。主要包括以下几个部分:
-
设置视图的Padding:
- 使用了
ViewCompat.setOnApplyWindowInsetsListener
来监听窗口插入事件。当系统栏(如状态栏和导航栏)发生变化时,代码会自动调整视图的padding
,以确保内容不会被系统栏遮挡。Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
获取系统栏的插入边距,然后通过v.setPadding
方法将这些边距应用到视图的padding
上。
- 使用了
-
数据库初始化:
- 使用了 Room 数据库框架来创建一个数据库实例
AppDatabase
,并通过Room.databaseBuilder
方法构建数据库。AppDatabase
是一个抽象类,定义了数据库的结构和操作。projectDao = db.projectDao();
获取了数据库访问对象(DAO),用于执行数据库操作。
- 使用了 Room 数据库框架来创建一个数据库实例
-
RecyclerView 初始化:
- 通过
findViewById(R.id.recyclerView);
获取RecyclerView
实例,并设置其布局管理器为线性布局管理器LinearLayoutManager
。RecyclerView
是一个用于高效显示大量数据集合的控件,适合用于显示项目列表。
- 通过
-
异步加载数据并更新UI:
- 在后台线程中,通过
projectDao.getAllProjects()
从数据库中获取所有的项目数据,然后切换到主线程(UI线程),使用这些数据来创建一个ProjectAdapter
对象,并将其设置为RecyclerView
的适配器。ProjectAdapter
是一个自定义的适配器类,负责将项目数据绑定到RecyclerView
的每个条目上。
- 在后台线程中,通过
-
项目详情页面跳转:
openProjectDetails
方法定义了如何处理用户点击RecyclerView
中的某个项目时的行为。当用户点击某个项目时,会创建一个Intent
,并传递项目的名称、成员、描述和图片路径等信息,然后启动ProjectDetailActivity
来显示项目的详细信息。
代码展示
// 设置保存修改按钮的功能
editConfirmButton.setOnClickListener(v -> {
String updatedName = editProjectNameInput.getText().toString();
String updatedMember = editProjectMemberInput.getText().toString();
String updatedDescription = editProjectDescriptionInput.getText().toString();
// 更新项目
new Thread(() -> {
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "projects-database").build();
ProjectDao projectDao = db.projectDao();
Project project = projectDao.findById(projectId);
if (project != null) {
project.name = updatedName;
project.members = updatedMember;
project.description = updatedDescription;
project.imagePath = selectedImageUri != null ? selectedImageUri.toString() : null;
project.lastModified = System.currentTimeMillis();
projectDao.updateProject(project);
runOnUiThread(() -> {
Toast.makeText(EditProjectActivity.this, "项目已更新", Toast.LENGTH_SHORT).show();
Intent intent1 = new Intent(EditProjectActivity.this, ProjectDetailActivity.class);
intent1.putExtra("projectId", projectId); // 传递项目 ID
startActivity(intent1);
finish();
});
}
}).start();
});
// 设置删除项目按钮的功能
deleteProjectButton.setOnClickListener(v -> {
new AlertDialog.Builder(EditProjectActivity.this)
.setTitle("删除项目")
.setMessage("确定要删除此项目吗?")
.setPositiveButton("删除", (dialog, which) -> {
new Thread(() -> {
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "projects-database").build();
ProjectDao projectDao = db.projectDao();
Project project = projectDao.findById(projectId);
if (project != null) {
projectDao.deleteProject(project);
runOnUiThread(() -> {
Toast.makeText(EditProjectActivity.this, "项目已删除", Toast.LENGTH_SHORT).show();
Intent intent2 = new Intent(EditProjectActivity.this, StoreActivity.class);
startActivity(intent2);
finish();
});
}
}).start();
})
.setNegativeButton("取消", null)
.show();
});
功能分析
这段代码实现了两个主要功能:
- 保存项目修改:允许用户更新项目的名称、成员、描述和图片路径,并在数据库中更新项目信息。
- 删除项目:允许用户通过确认对话框删除项目,并在数据库中删除项目信息。
通过这些功能,用户可以在应用中编辑和删除项目,并确保这些操作在数据库中得到正确的反映。
-
获取用户输入:
- 通过
editProjectNameInput.getText().toString()
、editProjectMemberInput.getText().toString()
和editProjectDescriptionInput.getText().toString()
分别获取用户在输入框中输入的项目名称、成员和描述。
- 通过
-
更新项目:
- 在后台线程中,初始化 Room 数据库并获取
ProjectDao
对象。 - 使用
projectDao.findById(projectId)
查找要更新的项目。 - 如果项目存在,更新其名称、成员、描述、图片路径和最后修改时间。
- 调用
projectDao.updateProject(project)
更新数据库中的项目信息。
- 在后台线程中,初始化 Room 数据库并获取
-
更新UI:
- 切换到 UI 线程,显示一个
Toast
消息提示用户项目已更新。 - 创建一个
Intent
跳转到ProjectDetailActivity
,并传递项目 ID。 - 启动
ProjectDetailActivity
并结束当前的EditProjectActivity
。
- 切换到 UI 线程,显示一个
-
显示确认对话框:
- 使用
AlertDialog.Builder
创建一个确认对话框,用户可以选择删除项目或取消操作。
- 使用
-
删除项目:
- 如果用户确认删除,在后台线程中初始化 Room 数据库并获取
ProjectDao
对象。 - 使用
projectDao.findById(projectId)
查找要删除的项目。 - 如果项目存在,调用
projectDao.deleteProject(project)
删除数据库中的项目。
- 如果用户确认删除,在后台线程中初始化 Room 数据库并获取
-
更新UI:
- 切换到 UI 线程,显示一个
Toast
消息提示用户项目已删除。 - 创建一个
Intent
跳转到StoreActivity
。 - 启动
StoreActivity
并结束当前的EditProjectActivity
。
- 切换到 UI 线程,显示一个
代码展示
// 创建新项目并保存到数据库
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "projects-database").build();
ProjectDao projectDao = db.projectDao();
Project newProject = new Project();
newProject.name = projectName;
newProject.members = projectMember;
newProject.description = projectDescription;
newProject.imagePath = selectedImageUri != null ? selectedImageUri.toString() : null;
newProject.lastModified = System.currentTimeMillis();
new Thread(() -> projectDao.insertProject(newProject)).start();
// 创建新项目后跳转到项目详情页面
Intent intent = new Intent(CreateprojectActivity.this, ProjectDetailActivity.class);
intent.putExtra("projectName", projectName);
intent.putExtra("projectMember", projectMember);
intent.putExtra("projectDescription", projectDescription);
if (selectedFileUri != null) {
intent.putExtra("fileUri", selectedFileUri.toString()); // 传递文件URI
}
if (selectedImageUri != null) {
intent.putExtra("imageUri", selectedImageUri.toString()); // 传递图片URI
}
startActivity(intent);
功能分析
这段代码的主要功能是创建一个新项目并将其保存到数据库中,然后在保存完成后跳转到项目详情页面,并将项目相关的信息传递给详情页面。通过使用新线程进行数据库操作,确保了界面的流畅性。
详细功能如下:
-
创建并配置数据库:
AppDatabase db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "projects-database").build(); ProjectDao projectDao = db.projectDao();
使用
Room.databaseBuilder
创建一个名为projects-database
的数据库实例,并通过db.projectDao()
获取ProjectDao
接口的实例,用于后续的数据库操作。 -
创建新项目对象:
Project newProject = new Project(); newProject.name = projectName; newProject.members = projectMember; newProject.description = projectDescription; newProject.imagePath = selectedImageUri != null ? selectedImageUri.toString() : null; newProject.lastModified = System.currentTimeMillis();
创建一个新的
Project
对象,并设置其属性,包括项目名称、成员、描述、图片路径和最后修改时间。 -
异步插入新项目到数据库:
new Thread(() -> projectDao.insertProject(newProject)).start();
使用新线程异步插入新项目到数据库。这是为了避免在主线程中执行耗时的数据库操作,从而防止界面卡顿。
-
跳转到项目详情页面:
Intent intent = new Intent(CreateprojectActivity.this, ProjectDetailActivity.class); intent.putExtra("projectName", projectName); intent.putExtra("projectMember", projectMember); intent.putExtra("projectDescription", projectDescription); if (selectedFileUri != null) { intent.putExtra("fileUri", selectedFileUri.toString()); // 传递文件URI } if (selectedImageUri != null) { intent.putExtra("imageUri", selectedImageUri.toString()); // 传递图片URI } startActivity(intent);
创建一个
Intent
,将项目相关的信息(如项目名称、成员、描述、文件URI和图片URI)传递给ProjectDetailActivity
,然后启动该活动,跳转到项目详情页面。
代码展示
// 1. 在 EditForestActivity 中保存数据到 SharedPreferences
public class EditForestActivity extends AppCompatActivity {
private EditText nicknameEdit, tagsEdit, introductionEdit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_forest);
nicknameEdit = findViewById(R.id.nickname_edit);
tagsEdit = findViewById(R.id.tags_edit);
introductionEdit = findViewById(R.id.introduction_edit);
Button saveButton = findViewById(R.id.button_save);
saveButton.setOnClickListener(view -> {
String nickname = nicknameEdit.getText().toString();
String tags = tagsEdit.getText().toString();
String introduction = introductionEdit.getText().toString();
// 保存数据到 SharedPreferences
SharedPreferences sharedPreferences = getSharedPreferences("ForestDetails", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("nickname", nickname);
editor.putString("tags", tags);
editor.putString("introduction", introduction);
editor.apply();
Intent intent = new Intent();
intent.putExtra("nickname", nickname);
intent.putExtra("tags", tags);
intent.putExtra("introduction", introduction);
setResult(RESULT_OK, intent);
finish();
});
}
}
// 2. 在 ForestDetailsActivity 中从 SharedPreferences 恢复数据
public class ForestDetailsActivity extends AppCompatActivity {
private TextView nicknameText, tagsText, introductionText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_forest_details);
// 初始化视图
nicknameText = findViewById(R.id.nickname_text);
tagsText = findViewById(R.id.tags_text);
introductionText = findViewById(R.id.introduction_text);
// 从 SharedPreferences 中恢复数据
SharedPreferences sharedPreferences = getSharedPreferences("ForestDetails", MODE_PRIVATE);
String nickname = sharedPreferences.getString("nickname", "默认昵称");
String tags = sharedPreferences.getString("tags", "默认标签");
String introduction = sharedPreferences.getString("introduction", "默认简介");
// 更新 UI 显示
nicknameText.setText("昵称:" + nickname);
tagsText.setText("标签:" + tags);
introductionText.setText("简介:" + introduction);
// 处理编辑按钮点击事件
Button editButton = findViewById(R.id.button_edit);
editButton.setOnClickListener(view -> {
Intent intent = new Intent(ForestDetailsActivity.this, EditForestActivity.class);
startActivityForResult(intent, 1);
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == RESULT_OK) {
String nickname = data.getStringExtra("nickname");
String introduction = data.getStringExtra("introduction");
String tags = data.getStringExtra("tags");
// 更新 UI 显示
nicknameText.setText("昵称:" + nickname);
tagsText.setText("标签:" + tags);
introductionText.setText("简介:" + introduction);
// 保存数据到 SharedPreferences
SharedPreferences sharedPreferences = getSharedPreferences("ForestDetails", MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("nickname", nickname);
editor.putString("tags", tags);
editor.putString("introduction", introduction);
editor.apply();
}
}
}
功能分析
这段代码实现了一个简单的数据存储和恢复功能:
- 用户可以在
EditForestActivity
中编辑数据,并保存到SharedPreferences
中。 - 在
ForestDetailsActivity
中,程序会从SharedPreferences
中读取数据并显示在 UI 上。 - 当用户完成编辑并返回到
ForestDetailsActivity
时,数据会被更新并再次保存到SharedPreferences
中。
这样,即使应用关闭或重新启动,用户之前编辑的数据也能够被正确恢复。
详细解释如下:
-
保存数据到 SharedPreferences:
- 在
EditForestActivity
中,用户输入了昵称、标签和简介后,点击保存按钮。 saveButton.setOnClickListener
监听器会获取用户输入的内容,并将其保存到名为"ForestDetails"
的SharedPreferences
中。- 保存的键值对包括:
"nickname"
-> 用户输入的昵称"tags"
-> 用户输入的标签"introduction"
-> 用户输入的简介
- 保存完成后,通过
Intent
将数据传递回ForestDetailsActivity
,并结束当前活动。
- 在
-
从 SharedPreferences 恢复数据:
- 在
ForestDetailsActivity
的onCreate
方法中,程序会从SharedPreferences
中读取之前保存的数据。 - 如果
SharedPreferences
中没有对应的数据(例如首次启动应用),则使用默认值。 - 读取的数据会显示在 UI 上的
nicknameText
、tagsText
、introductionText
文本视图中。
- 在
-
更新数据:
- 当用户在
ForestDetailsActivity
中点击编辑按钮,并返回到ForestDetailsActivity
时,onActivityResult
方法会被调用。 onActivityResult
方法会接收从EditForestActivity
传递回来的数据,并更新 UI 显示。- 更新后的数据还会再次保存到
SharedPreferences
中,以确保下次启动应用时能够正确恢复数据。
- 当用户在
2 页面展示
若视频无法显示,请点击这个链接,即可加载进入新标签页观看视频。
实机演示视频链接
五、附加特点设计与展示
创意
-
动态人才林显示:认证通过的用户加入人才林,人才林自动展示用户身份,用户可以自行编辑标签,让他人更好地认识自己,也可以在人才林中寻找适合自己要求的同伴。
-
项目总库展示:项目总库展示最近热门的项目,方便新用户了解,老司机则可以通过查找来搜寻心仪的项目。
-
创意设计意义:FYT通过分类和标记用户技能,帮助用户快速找到匹配的合作伙伴,极大地提高了团队组建的效率和质量。
思路
-
动态人才林显示:
- 使用数据库来存储用户的信息,包括认证状态和用户标签。
- 在
GeniusForestActivity
中动态加载已认证用户的数据,并展示在界面上。 - 提供编辑功能,让用户能够添加或修改自己的标签,这可以通过弹出对话框实现。
-
项目总库展示:
- 在
AllStoreActivity
中可以定义一个项目展示列表,然后从数据库获取最近热门项目的列表并展示在界面上。 - 可以设置筛选和分类功能,让用户方便查找感兴趣的项目。
- 在
代码
代码展示
// GeniusForestActivity.java
public class GeniusForestActivity extends AppCompatActivity {
// 声明 RecyclerView 和 Adapter
private RecyclerView recyclerView;
private UserAdapter userAdapter;
private List<User> userList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_genius_forest);
recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
// 初始化用户列表
userList = new ArrayList<>();
loadCertifiedUsers();
userAdapter = new UserAdapter(userList);
recyclerView.setAdapter(userAdapter);
}
// 从数据库加载认证用户(示例方法,具体实现依需数据库结构)
private void loadCertifiedUsers() {
// 这里应该是从数据库获取已认证用户的逻辑
// 例如使用 Firebase, SQLite 等
}
}
// AllStoreActivity.java
public class AllStoreActivity extends AppCompatActivity {
// 声明 RecyclerView 和 Adapter
private RecyclerView projectsRecyclerView;
private ProjectAdapter projectAdapter;
private List<Project> projectList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_all_store);
projectsRecyclerView = findViewById(R.id.projects_recycler_view);
projectsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 初始化项目列表
projectList = new ArrayList<>();
loadPopularProjects();
projectAdapter = new ProjectAdapter(projectList);
projectsRecyclerView.setAdapter(projectAdapter);
}
// 从数据库加载热门项目(示例方法,具体实现依需数据库结构)
private void loadPopularProjects() {
// 这里应该是从数据库获取热门项目的逻辑
}
}
分析解释
-
在
GeniusForestActivity
类中,我们创建了一个RecyclerView
用于展示已认证用户。通过loadCertifiedUsers
方法从数据库加载用户数据,使用UserAdapter
来处理用户列表。 -
在
AllStoreActivity
类中,使用RecyclerView
展示最近热门的项目,loadPopularProjects
方法用来从数据库加载项目数据,ProjectAdapter
用于处理项目列表。
成果
-
首页展示界面
-
人才林展示界面
-
项目总库展示界面
六、目录说明与使用说明
目录组织
- app
- manifests
- AndroidManifest.xml
- java
- com.example.fyt
- adapters
- data
- database
- models
- ui
- AllStoreActivity
- CertifyActivity
- CertifyDetailsActivity
- ChatActivity1
- CreateprojectActivity
- EditForestActivity
- EditProjectActivity
- FirstActivity
- ForestDetailsActivity
- GeniusForestActivity
- HomeActivity
- ProjectDetailActivity
- RegisterActivity
- SetpasswordActivity
- SettingsHelpActivity
- StoreActivity
- TeamActivity
- com.example.fyt (androidTest)
- com.example.fyt (test)
- java (generated)
- res
- drawable
- layout
- menu
- mipmap
- navigation
- values
- xml
- res (generated)
- Gradle Scripts
目录说明:
-
app
- manifests
- AndroidManifest.xml:应用的配置文件,定义应用的包名、组件(如活动、服务)、权限等。
- java
- com.example.fyt:包含应用的源代码。
- adapters:适配器类,用于将数据绑定到视图组件。
- data:数据处理相关的类。
- database:数据库操作相关的类。
- models:数据模型类。
- ui:界面相关的类,包括各个Activity。
- 各种Activity类:定义应用的不同界面和交互逻辑。
- com.example.fyt (androidTest):存放用于Android测试的代码。
- com.example.fyt (test):存放单元测试代码。
- com.example.fyt:包含应用的源代码。
- java (generated):自动生成的代码,如视图绑定代码。
- res
- drawable:图片资源。
- layout:布局文件,定义界面的结构。
- menu:菜单资源文件。
- mipmap:图标资源。
- navigation:导航资源文件,用于支持导航组件。
- values:存放资源文件,如字符串、颜色定义。
- xml:通用XML文件资源。
- res (generated):自动生成的资源文件。
- manifests
-
Gradle Scripts:构建脚本,通常包含
build.gradle
等文件,定义项目的构建配置和依赖。
使用说明
-
安装应用:
- 在安卓手机上下载并安装APK文件。
-
注册与认证:
- 打开应用,进入登录页面并选择注册。
- 注册成功后,返回登录页面并登录。
- 进入认证页面,如实填写个人信息和身份信息,完成认证。
-
探索首页:
- 认证完成后,进入应用首页。
-
编辑个人资料:
- 在首页下方导航栏中点击“我的”。
- 进入“我的”页面后,选择“编辑我的人才林”。
- 填写相关个人信息。
-
探索功能:
- 在“我的”页面中,您可以查看以下功能:
- 设置:管理应用设置。
- 好友聊天:与好友进行聊天。
- 修改身份认证:更新您的身份认证信息。
- 在“我的”页面中,您可以查看以下功能:
-
创建项目:
- 通过下方导航栏进入“仓库”。
- 在“仓库”页面中,点击“创建项目”。
- 填写项目相关信息,完成项目创建。
-
浏览项目和人才:
- 返回首页,浏览推荐的项目和人才。
- 点击“人才林”或“项目总库”,分别进入对应的页面,查找您感兴趣的项目和人才。
-
查看您的项目:
- 再次进入“仓库”,您将看到自己创建的项目已经出现在列表中。
七、单元测试
在本项目中,我们选用了 Android 测试框架中的 Espresso 和 JUnit 库进行单元测试。
选用的测试工具及学习单元测试的方法
选用的测试工具:
- JUnit:用于编写和运行单元测试。它是一个开源的测试框架,支持 Java 应用的广泛测试,包括 Android 应用的业务逻辑单元测试。
- Espresso:用于UI测试,支持模拟用户界面元素的交互,便于编写可靠的界面测试。
- IntentsTestRule:专门用于测试应用中的活动切换,通过验证启动的Intent来确保活动正常启动。
学习单元测试的方法:
- 官方文档学习:阅读 Android Espresso 和 JUnit 的官方文档,了解框架提供的测试能力和最佳实践。
- 在线教程和资源:利用各种博客、GitHub 项目和 YouTube 教程学习如何在 Android 上编写单元测试。
- 实践:在小规模项目中实践单元测试技术,总结常见问题和解决方案,以掌握编写有效测试用例的经验。
简易教程:
-
设置环境:
-
在项目的
app/build.gradle
文件中加入以下依赖,以支持 Espresso 和 JUnit 测试:androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0'
-
-
创建测试类:
- 在
src/androidTest/java
目录下创建测试类,例如HomeActivityTest
,并使用@RunWith(AndroidJUnit4.class)
注解指定测试运行器。
- 在
-
编写测试方法:
- 使用
@Test
注解来编写具体测试方法,结合 Espresso 的 API(如onView()
,perform()
,intended()
)模拟用户交互和验证结果。
- 使用
-
运行测试:
- 在 Android Studio 中,通过右键单击测试类或测试方法,选择“Run”来执行测试,确保程序按预期运行。
展示项目部分单元测试代码,并说明测试的函数
以下是一个示例测试代码,测试主页上四个按钮的点击操作:
package com.example.fyt;
import android.content.Context;
import android.content.Intent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.junit.Assert.*;
@RunWith(AndroidJUnit4.class)
public class HomeActivityTest {
@Rule
public IntentsTestRule<HomeActivity> intentsTestRule =
new IntentsTestRule<>(HomeActivity.class);
@Test
public void testFirstButtonLaunchesEditForestActivity() {
onView(withId(R.id.first)).perform(click());
intended(hasComponent(EditForestActivity.class.getName()));
}
@Test
public void testSecondButtonLaunchesSettingsHelpActivity() {
onView(withId(R.id.second)).perform(click());
intended(hasComponent(SettingsHelpActivity.class.getName()));
}
@Test
public void testThirdButtonLaunchesChatActivity1() {
onView(withId(R.id.third)).perform(click());
intended(hasComponent(ChatActivity1.class.getName()));
}
@Test
public void testFourthButtonLaunchesCertifyActivity() {
onView(withId(R.id.fourth)).perform(click());
intended(hasComponent(CertifyActivity.class.getName()));
}
}
测试函数说明:
testThirdButtonLaunchesChatActivity1
测试了主页上ID为third
的按钮。该函数模拟了用户点击thirdButton
的操作,然后验证是否正确启动了ChatActivity1
。通过这种测试,确保当用户点击按钮时,应用启动了正确的活动。
构造测试数据的思路及考虑各种情况的方法
构造测试数据的思路:
- 正常路径测试:首先,针对预期操作,如单个按钮点击,测试正常情况下的功能表现。
- 边界测试:在输入可能的边界条件(如无效输入、网络中断)下,确保应用不会崩溃。
- 压力测试:模拟快速或重复点击,以验证应用在高负载时的响应能力。
考虑将来测试人员可能面临的挑战:
- 边界情况:确保测试覆盖到极端场景,如异常输入,会话超时等。
- 意外用户行为:测试应用如何处理用户可能的误操作,确保程序健壮。
- 动态变化:考虑日后的代码变更或功能新增,编写测试时注重可维护性和可扩展性。
通过这些方法,确保我们的测试用例能够全面覆盖应用的各个功能和极端情况,以构建一个健壮、可靠的应用程序。
八、GitHub提交记录截图
九、遇到的代码模块异常或结对困难及解决方法
问题描述:
在开发过程中,我们遇到了一个关键的代码模块异常:在实现主页按钮导航功能时,多个按钮点击事件跳转的目标活动不一致,导致应用程序在点击某些按钮时崩溃。
做过的尝试:
- 日志调试:首先,我们通过在按钮的点击事件中添加日志信息(
Log.d
)来确认点击事件是否正确触发,并跟踪跳转的活动。 - 代码审查:我和队友一起逐行检查相关代码,包括按钮的ID、活动的Intent创建逻辑,以及目标活动在AndroidManifest.xml中的注册。
- 单元测试:利用之前编写的单元测试,对每个按钮点击事件重新运行测试,以帮助确认问题是否出现在特定的点击逻辑上。
- 文档查阅:查阅了官方文档,尤其是关于Intent和活动生命周期的部分,确保在活动之间准确地传递信息。
是否解决:
最终,通过检测发现是由于某些活动未在AndroidManifest.xml
中正确注册导致的。经过修正后,所有导航功能恢复正常。
有何收获:
- 调试能力提升:通过使用日志以及单元测试进行问题定位,提高了我们的调试技巧。
- 团队交流的重要性:与队友的合作检查确保了问题能够更快地被发现和解决。
- 文档重要性:重温官方文档帮助我们加深了对Intent及活动管理的理解,避免了后续类似问题。
十、评价你的队友
值得学习的地方:
- 严谨的代码风格:队友在编写代码的过程中总是注重代码的可读性和维护性,这不仅使得合作更加顺畅,也让我意识到优秀代码风格的重要性。
- 积极的学习态度:面对问题时,队友会主动去查阅文档和学习新知识,这种自我驱动的学习态度使我们能够迅速解决问题并提升项目质量。
需要改进的地方:
- 时间管理:有时在解决某个问题或实现新功能时,队友可能会花费过多时间在细节优化上。更好的时间管理和优先级设定可以帮助我们在满足项目要求的前提下,合理分配精力。
- 沟通频率:偶尔会在问题反馈时产生信息不对称,建议增加定期同步进展的频率,以确保双方始终对项目的当前状态有一致的理解。
通过这次合作,我不仅仅在技术上得到了提升,也体会到了队友间相互支持与学习的重要性。希望通过相互学习和改进,在未来的项目中能有更高效的合作。