20212313-吴剑标-移动平台开发与实践大作业
一、实验目的
1.掌握Android平台的基本开发技能,包括界面设计、数据处理和用户交互。
2.理解并实践用户注册、登录流程,以及用户信息的存储和会话管理。
3.学习SQLite数据库在Android应用中的运用,实现数据的增删查改操作。
4.熟悉使用Android Studio开发环境,包括模拟器的使用和调试技巧。
5.增强解决实际问题的能力,根据本学期所学内容完成一个大作业
二、实验环境
1.操作系统:Windows 10 (家庭版)
2.开发工具:Android Studio,包括必要的SDK和API。
3.编程语言:Java
4.测试设备:模拟器
三、实验内容与设计思路
这次的大作业我打算做的是一款电影购票的App,主要是在csdn上看到各种各样的电影购票系统,所以就看了他们写的,然后主要的就是对代码进行学习与理解,最后进行修改与完善。
主要能够实现的功能与流程为:
1.用户首先需要注册用户名填写密码。
2.注册成功之后,用户可以用之前注册的用户名和密码进行登录。
3.登录成功之后进入到首页,其中包括电影、零食、我的三个部分。
4.电影页面展示电影列表,通过点击列表项中的加号图标,将电影票加入购物车。
5.零食页面展示零食列表,通过点击列表项中的加号图表,将零食加入购物车。
6.我的页面展示当前登录用户名称,购物车入口。
7.点击电影票或零食购物车,进入详情页,展示已选择的电影或零食列表,点击列表项中的删除,可移除该项。(本来想着自己写出来,但是最后没写出来。。。。。。。)
四、实验过程与分析
(一)框架设计
下图是我根据设计思路,然后总结大致结构,用wps画出了电影购票系统的简单结构:
(二)功能模块设计
1.系统功能设计
①登录注册功能:用户信息存储在SQLite中,登录后当前登录用户信息存储在Sharedpreferences中。
②购物车功能:购物车信息存储在SQLite中,存储购物车信息时,包含用户ID,使得每个用户的购物车内容独立分开。
③使用RecyclerView控件展示商品信息。
2.系统界面设计
采用了一些富有电影元素的颜色作为主色调,和电影购票的主题相符合。注册和登录的页面参考了csdn上的许多样例,为了简洁美观,所以登录注册页面都用到了自定义按钮样式和自定义输入框样式。
(三)核心代码
1.Loginactivity
在onCreate方法中,首先调用setContentView来加载登录界面的布局文件activity_login。然后,通过initTitle、initView和initListener三个私有方法分别初始化界面的标题、视图组件和事件监听器。
initTitle方法设置界面标题为"登录",并隐藏返回按钮,这提供一个更简洁的登录界面。initView方法通过findViewById获取界面上的按钮和文本输入框的引用,为后续的事件绑定做准备。initListener方法为登录按钮和注册按钮分别设置了点击事件监听器。当用户点击登录按钮时,会获取用户输入的用户名和密码,然后调用UserService的login方法尝试登录。如果登录成功,将使用CurrentUserUtils类的方法保存当前用户信息,并跳转到主界面MainActivity。如果登录失败,则会显示错误信息。通过点击注册按钮的事件监听器,用户可以跳转到注册界面,这为用户提供了创建新账户的途径。
kotlin
public class LoginActivity extends AppCompatActivity {
private Button btnLogin, btnRegister;
private EditText etUsername, etPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// 初始化标题
initTitle();
// 初始化视图
initView();
// 初始化监听器
initListener();
}
private void initTitle() {
TextView tvTitle = findViewById(R.id.tv_title);
tvTitle.setText("登录");
TextView tvBack = findViewById(R.id.tv_back);
tvBack.setVisibility(View.GONE);
}
private void initView() {
btnLogin = findViewById(R.id.btn_login);
btnRegister = findViewById(R.id.btn_register);
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
}
private void initListener() {
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String username = etUsername.getText().toString();
String password = etPassword.getText().toString();
BusinessResult<User> login = UserService.login(username, password);
if (login.isSuccess()) {
CurrentUserUtils.setCurrentUser(login.getData());
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);
finish();
}else {
// 登录失败
Toast.makeText(LoginActivity.this, login.getMessage(), Toast.LENGTH_SHORT).show();
}
}
});
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
startActivity(intent);
}
});
}
}
2.RegisterActivity
在onCreate方法中,首先通过setContentView加载注册界面的布局,然后依次初始化标题、视图组件和事件监听器。initTitle方法设置了界面标题为"注册",并为返回按钮添加了点击事件,允许用户在需要时返回上一个活动。initView方法通过findViewById获取注册界面上的按钮和文本输入框的引用,为绑定事件监听器做准备。initListener方法为注册按钮设置了点击事件监听器,当用户点击注册按钮时,会从输入框中获取用户名、密码和确认密码,然后调用UserService的register方法进行注册。注册成功后,会通过Toast显示成功消息,并关闭当前注册界面;如果注册失败,则显示失败原因。
kotlin
public class RegisterActivity extends AppCompatActivity {
private Button btnRegister;
private EditText etUsername, etPassword, etPasswordAgain;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
initTitle();
initView();
initListener();
}
private void initTitle() {
TextView tvTitle = findViewById(R.id.tv_title);
tvTitle.setText("注册");
TextView tvBack = findViewById(R.id.tv_back);
tvBack.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
}
private void initView() {
btnRegister = findViewById(R.id.btn_register);
etUsername = findViewById(R.id.et_username);
etPassword = findViewById(R.id.et_password);
etPasswordAgain = findViewById(R.id.et_password_again);
}
private void initListener() {
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String username = etUsername.getText().toString();
String password = etPassword.getText().toString();
String passwordAgain = etPasswordAgain.getText().toString();
BusinessResult<User> result = UserService.register(username, password, passwordAgain);
Toast.makeText(RegisterActivity.this, result.getMessage(), Toast.LENGTH_SHORT).show();
if (result.isSuccess()) {
finish();
}
}
});
}
}
3.MainActivity
onCreate方法首先设置活动的内容视图为activity_main布局。然后,创建一个fragmentList列表,用于存储三个不同的片段:HotFragment、SnackFragment和UserFragment,分别代表热门电影、零食和用户信息的界面部分。
接下来,代码通过findViewById获取到ViewPager2和BottomNavigationView的实例。ViewPager2用于展示不同的片段,而BottomNavigationView提供了底部导航栏,允许用户在不同的片段间切换。通过创建一个FragmentStateAdapter的匿名内部类,ViewPager2的适配器被设置,它负责根据用户在ViewPager2上的位置来实例化和展示相应的片段。
kotlin
public class MainActivity extends AppCompatActivity {
private List<Fragment> fragmentList;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentList = new ArrayList<>();
fragmentList.add(new HotFragment());
fragmentList.add(new SnackFragment());
fragmentList.add(new UserFragment());
ViewPager2 pager = findViewById(R.id.pager);
BottomNavigationView bottomNavigation = findViewById(R.id.bottom_navigation);
pager.setAdapter(new FragmentStateAdapter(this) {
@NonNull
@Override
public Fragment createFragment(int position) {
return fragmentList.get(position);
}
@Override
public int getItemCount() {
return fragmentList.size();
}
});
pager.setOffscreenPageLimit(fragmentList.size());
pager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
bottomNavigation.getMenu().getItem(position).setChecked(true);
}
});
bottomNavigation.setOnItemSelectedListener(new NavigationBarView.OnItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
pager.setCurrentItem(item.getOrder(), false);
return true;
}
});
}
}
4.UserDatabaseHelper
init方法用于初始化数据库帮助类实例,确保在应用程序中全局只存在一个UserDatabaseHelper对象。insert方法允许将用户信息插入到数据库中,使用预编译的SQL语句来防止SQL注入攻击。isExistByUsername方法用于检查数据库中是否已存在具有相同用户名的用户,而getUserByUsername方法则用于根据用户名查询用户信息。
onCreate方法在数据库首次创建时被调用,用于定义用户表的结构,包含自增主键_id、用户名username和密码password字段。onUpgrade方法则用于处理数据库版本升级时的逻辑,虽然在此代码示例中该方法为空,但它在实际应用中用于处理数据迁移或版本兼容问题。整个UserDatabaseHelper类的操作包括数据库的创建、用户数据的增删查以及数据库版本的管理。
kotlin
public class UserDatabaseHelper extends SQLiteOpenHelper {
private static UserDatabaseHelper instance;
private UserDatabaseHelper(Context context) {
super(context, "user.db", null, 1);
}
public static void init(Context context) {
if (instance == null) {
instance = new UserDatabaseHelper(context);
}
}
public static void insert(User user) {
SQLiteDatabase db = instance.getWritableDatabase();
String sql = "INSERT INTO user(username,password) VALUES(?,?)";
String[] args = new String[]{user.getUsername(), user.getPassword()};
db.execSQL(sql, args);
db.close();
}
public static boolean isExistByUsername(String username) {
SQLiteDatabase db = instance.getWritableDatabase();
String sql = "SELECT * FROM user WHERE username=?";
String[] args = new String[]{username};
Cursor cursor = db.rawQuery(sql, args);
boolean result = cursor.getCount() > 0;
cursor.close();
db.close();
return result;
}
public static User getUserByUsername(String username) {
SQLiteDatabase db = instance.getWritableDatabase();
String sql = "SELECT * FROM user WHERE username=?";
String[] args = new String[]{username};
Cursor cursor = db.rawQuery(sql, args);
User user = null;
if (cursor.moveToNext()) {
user = new User();
user.setId(cursor.getInt(0));
user.setUsername(cursor.getString(1));
user.setPassword(cursor.getString(2));
}
cursor.close();
db.close();
return user;
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL("CREATE TABLE user(_id INTEGER PRIMARY KEY AUTOINCREMENT,username VARCHAR(20) ,password VARCHAR(20))");
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
(四)实验成果
以下为我的实验成果的视频录制:
CineFlow --
1.登入页面
2.注册页面
3.电影主页面
4.零食页面
5.我的页面
五、实验问题与解决
问题1:导入项目时,无法运行
解决方法:修改项目路径,将路径修改成自己电脑上的路径。
问题2:修改项目路径后仍然无法运行,查找资料后显示需要"Sync project with Gradle Files"。但是我发现我没有"Sync project with Gradle Files"按钮。
解决方法:根据csdn上的教程一步一步完成的。
问题3:在我编译过程中,Android Studio报错,报了大量的错,错误信息大致意思是找不到类或方法、缺少依赖。
解决方法:在网络上查询了特别久,才发现是导入的库与我使用的版本不一致,不兼容。然后花了特别大的时间又去网站上下载了一个对应版本的库。最后重建项目,使用Build -> Clean Project和Build -> Rebuild Project。
问题4:SQLite数据库查询错误,在尝试从SQLite数据库查询用户信息时,无法正确查询到数据。
解决方法:将代码发给了gpt,和gpt交流了特别久都没有找到问题。原来是我在学习数据库查询时,重新设计查询语句的时候sqk语句用的是中文引号,最后SQL查询语句修改成英文引号就可以。
六、实验结果总结
此次移动平台课程的大作业过程中,我深入体验了从理论到实践的转变,这个过程让我受益匪浅。我深刻认识到理论知识与实际操作之间的差距。在设计电影购票系统的过程中,我本以为已经掌握了足够的理论知识,但实际操作时却发现理论与现实之间存在诸多差异。例如,在实现用户登录和注册功能时,我需要考虑如何安全地存储用户信息,如何有效管理用户的会话状态,这些都是书本和课上上不曾深入讨论的问题。这让我意识到,理论知识是基础,但只有通过实践,才能真正理解并掌握技术的应用。
在开发过程中,我遇到了各种预料之外的技术难题,比如数据库的设计与优化、用户界面的友好性设计等。面对这些问题,我学会了查阅资料、分析问题、尝试不同的解决方案,并最终找到问题的答案。这个过程锻炼了我的问题解决能力,也让我更加自信地面对未知的挑战。在开发过程中,离不开舍友和同学的帮助,我与同学们进行了多次讨论和交流。我们分享了彼此的经验和遇到的难题,这种交流极大地拓宽了我的视野,也帮助我更快地解决了问题。通过这次大作业,我不仅提升了自己的技术能力,也锻炼了自己的思维和解决问题的能力。
这学期的课程已经圆满结束,我有幸参加了王志强老师教授的两门课程,它们不仅内容丰富,而且深刻地影响了我对专业知识的理解和应用。在网络攻防课程中,我仿佛化身为一名黑客,深入探索了网络安全的奥秘,学习了如何识别和防范潜在的网络威胁,这些经历极大地拓宽了我的视野,让我对网络安全有了更加深刻的认识。而在移动平台课程中,我更是亲手实践,从零开始学习如何开发一款手机应用,这个过程不仅锻炼了我的编程能力,也让我体会到了创造的乐趣和成就感,我发现自己也能成为一名开发者,这无疑大大增强了我的自信心。
尽管在本学期中,我因私事在移动平台课程上请了两次假,都是去见我的女朋友,但我始终对课程保持着极高的热情和重视。在此,我特别感谢王志强老师的理解和支持,他的和蔼与善良让我感到温暖,也激励我更加努力学习。老师的教学方法生动有趣,能够深入浅出地讲解复杂的知识点,使我能够在轻松愉快的氛围中掌握知识。未来我将不辜负老师的期望,继续保持学习的积极性!最后再次感谢王志强老师辛勤教导!