2020年闹钟提醒备忘录移动开发技术实验报告
时间:2020-11-24 17:00:46 来源:勤学考试网 本文已影响 人
闹钟提醒备忘录移动开发技术实验报告 目录 第一部分APP 简介 ................................................................................ 2
(1)
需求分析 .................................................................................. 2
(2)
系统目标 .................................................................................. 2
(3)
开发及运行环境 ...................................................................... 2
第二部分开发技术 ................................................................................ 3
(1)SharedPrefenrences 技术 ........................................................... 3
(2)数据库 ...................................................................................... 11
(3)CRUD 操作 ............................................................................. 12
(4)时钟提醒 .................................................................................. 13
第三部分功能介绍 .............................................................................. 15
(1)登录模块 .................................................................................. 15
(2)注册模块 .................................................................................. 16
(3)主界面模块 .............................................................................. 16
(4)新建备忘录模块 ...................................................................... 17
(5)删除备忘录模块 ...................................................................... 18
第四部分遇到的问题和解决办法 ...................................................... 2
(1)无法刷新 UI ............................................................................ 2
(1)无法使用某些 API .................................................................. 2
第五部分总结 ...................................................................................... 21
第一部分APP 简介 (1 1 )
需求分析
随着时代的发展,我们每天需要处理的事务也呈现了爆炸式的增长。记住亲朋好友的生日并及时送上生日的祝福;记住上司布置的任务;记住几天后的出行车票时间等,这些纷繁的信息记忆是不是正在让您深陷其中?目前,手机上已经有了备忘录功能和闹钟功能,但二者之间并没有实现消息的互通(苹果手机用户可以设置事件的闹钟提醒,但是安卓用户却还没有使用这样的功能)。用户在备忘录中记录下需要设置时间提醒事件后,还需要再打开闹钟app,并设置闹钟提醒,这在无形中增加了用户的负担,并且产生了糟糕的用户体验。
现在,这些都不再是问题,在本次的课程设计中,使用我的软件,用户可以十分方便的新建备忘录,根据标题查找备忘录事件,删除已过期的备忘录并可以随意修改备忘录内容。除此之外,用户还可以为每条事件添加重要程度,并在主界面按重要程度展示所有的已建备忘录列表本软件致力于帮助用户时刻记录下生活中的重要信息并在设置的时间给用户发送提醒,让您不缺席生活中的重要时刻。
(2 2 )
系统目标
1,友好的操作界面和良好的人机互动。
2,软件的登录界面保证了信息的保密性。
3,随时对重要信息的记录并设置到时间提醒。
4,备忘录内容的修改,删除和查询。
5,也可将其用作记录本使用,代替原有的单一备忘录 app。
6,系统可靠运行,安全有效。
(3 3 )
开发及运行环境
(1)
开发工具Android Studio (2)
系统环境Windows 1 家庭中文版 (3)
开发语言Java,xml
第二部分开发技术 (1 1 )
SharedPrefenrences s 技术
在用户第一次进入软件时,可以注册个人账号,包括账号和密码;密码须二次输入同样的字符,否则无法注册成功;账号和密码使用 SharedPrefenrence 技术保存。保存用户的账号密码,属于用户的偏好参数,若使用数据库来存储这些数据,未免有些大材小用,此时 SharedPreferences 技术就派上用场。SharedPreferences 使用键-值的形式来存储数据。在我们的实例中,账号为键,密码为值,具有唯一匹配性,我们只需要调用 SharedPreferences 的getXxx(name), 就可以根据键获得对应的值。使用起来很方便!
登录界面源码
package com.example.uilayouttest.Activity;
import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.support.vapp.ActionBar; import android.support.vapp.AppCompatActivity; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast;
import com.example.uilayouttest.Adapter.MD5Utils; import com.example.uilayouttest.R;
public class LoginActivity extends AppCompatActivity {
private TextView tv_main_title;//标题
private TextView tv_back,tv_register,tv_find_psw;//返回键,显示的注册,找回密码
private Button btn_login;//登录按钮
private String userName,psw,spPsw;//获取的用户名,密码,加密密码
private EditText et_user_name,et_psw;//编辑框
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//设置此界面为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
init();
ActionBar actionbar=getSupportActionBar();
if(actionbar!=null)
actionbar.hide();
}
//获取界面控件
private void init() {
//从 main_title_bar 中获取的 id
tv_main_title=findViewById(R.id.tv_main_title);
tv_main_title.setText("登录");
tv_back=findViewById(R.id.tv_back);
//从 activity_login.xml 中获取的
tv_register=findViewById(R.id.tv_register);
tv_find_psw=findViewById(R.id.tv_find_psw);
btn_login=findViewById(R.id.btn_login);
et_user_name=findViewById(R.id.et_user_name);
et_psw=findViewById(R.id.et_psw);
//返回键的点击事件
tv_back.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//登录界面销毁
LoginActivity.this.finish();
}
});
//立即注册控件的点击事件
tv_register.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//为了跳转到注册界面,并实现注册功能
Intent intent=new Intent(LoginActivity.this,RegisterActivity.class);
startActivityForResult(intent, 1);
}
});
//找回密码控件的点击事件
tv_find_psw.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//跳转到找回密码界面(此页面暂未创建)
}
});
//登录按钮的点击事件
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//开始登录,获取用户名和密码 getText().toString().trim();
userName=et_user_name.getText().toString().trim();
psw=et_psw.getText().toString().trim();
//对当前用户输入的密码进行 MD5 加密再进行比对判断, MD5Utils.md5( ); psw 进行加密判断是否一致
String md5Psw=MD5Utils.md5(psw);
// md5Psw ; spPsw 为 根据从 SharedPreferences 中用户名读取密码
// 定义方法 readPsw 为了读取用户名,得到密码
spPsw=readPsw(userName);
// TextUtils.isEmpty
if(TextUtils.isEmpty(userName)){
Toast.makeText(LoginActivity.this, "请输入用户名", Toast.LENGTH_SHORT).show();
return;
}else if(TextUtils.isEmpty(psw)){
Toast.makeText(LoginActivity.this, "请输入密码", Toast.LENGTH_SHORT).show();
return;
// md5Psw.equals(); 判断,输入的密码加密后,是否与保存在 SharedPreferences 中一致
}else if(md5Psw.equals(spPsw)){
//一致登录成功
Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
//保存登录状态,在界面保存登录的用户名 定义个方法 saveLoginStatus boolean 状态 , userName 用户名;
saveLoginStatus(true, userName);
//登录成功后关闭此页面进入主页
Intent data=new Intent();
//datad.putExtra( ); name , value ;
data.putExtra("isLogin",true);
//RESULT_OK 为 Activity 系统常量,状态码为-1
// 表示此页面下的内容操作成功将 data 返回到上一页面,如果是用 back 返回过去的则不存在用 setResult 传递 data 值
setResult(RESULT_OK,data);
//销毁登录界面
LoginActivity.this.finish();
//跳转到主界面,登录成功的状态传递到 MainActivity 中
startActivity(new Intent(LoginActivity.this, MainActivity.class));
return;
}else if((spPsw!=null&&!TextUtils.isEmpty(spPsw)&&!md5Psw.equals(spPsw))){
Toast.makeText(LoginActivity.this, "输入的用户名和密码不一致", Toast.LENGTH_SHORT).show();
return;
}else{
Toast.makeText(LoginActivity.this, "此用户名不存在", Toast.LENGTH_SHORT).show();
}
}
});
}
private String readPsw(String userName){
//getSharedPreferences("loginInfo",MODE_PRIVATE);
//"loginInfo",mode_private; MODE_PRIVATE 表示可以继续写入
SharedPreferences sp=getSharedPreferences("loginInfo", MODE_PRIVATE);
//sp.getString() userName, "";
return sp.getString(userName , "");
}
private void saveLoginStatus(boolean status,String userName){
//saveLoginStatus(true, userName);
//loginInfo 表示文件名
SharedPreferences sp=getSharedPreferences("loginInfo", MODE_PRIVATE);
SharedPreferences sp=getSharedPreferences("loginInfo", MODE_PRIVATE);
//获取编辑器
SharedPreferences.Editor editor=sp.edit();
//存入 boolean 类型的登录状态
editor.putBoolean("isLogin", status);
//存入登录状态时的用户名
editor.putString("loginUserName", userName);
//提交修改
mit();
}
@Override
//显示数据, onActivityResult
//startActivityForResult(intent, 1); 从注册界面中获取数据
//int requestCode , int resultCode , Intent data
// LoginActivity -> startActivityForResult -> onActivityResult();
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//super.onActivityResult(requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
if(data!=null){
//是获取注册界面回传过来的用户名
// getExtra().getString("***");
String userName=data.getStringExtra("userName");
if(!TextUtils.isEmpty(userName)){
//设置用户名到 et_user_name 控件
et_user_name.setText(userName);
//et_user_name 控件的 setSelection()方法来设置光标位置
et_user_name.setSelection(userName.length());
}
}
} }
注册界面源码如下
package com.example.uilayouttest.Activity;
import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.graphics.Color; import android.support.vapp.ActionBar; import android.support.vapp.AppCompatActivity; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.Button;
import android.widget.EditText; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast;
import com.example.uilayouttest.Adapter.MD5Utils; import com.example.uilayouttest.R;
public class RegisterActivity extends AppCompatActivity {
private TextView tv_main_title;//标题
private TextView tv_back;//返回按钮
private Button btn_register;//注册按钮
//用户名,密码,再次输入的密码的控件
private EditText et_user_name,et_psw,et_psw_again;
//用户名,密码,再次输入的密码的控件的获取值
private String userName,psw,pswAgain;
//标题布局
private RelativeLayout rl_title_bar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//设置页面布局 ,注册界面
setContentView(R.layout.activity_register);
//设置此界面为竖屏 //
ActionBar actionbar=getSupportActionBar(); //
if (actionbar !=null) //
actionbar.hide();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
init();
}
private void init() {
//从 main_title_bar.xml 页面布局中获取对应的 UI 控件
tv_main_title=findViewById(R.id.tv_main_title);
// tv_main_title.setText("注册");
tv_back=findViewById(R.id.tv_back);
//布局根元素
rl_title_bar=findViewById(R.id.title_bar); //
rl_title_bar.setBackgroundColor(Color.TRANSPARENT);
//从 activity_register.xml 页面中获取对应的 UI 控件
btn_register=findViewById(R.id.btn_register);
et_user_name=findViewById(R.id.et_user_name);
et_psw=findViewById(R.id.et_psw);
et_psw_again=findViewById(R.id.et_psw_again);
Button btnback=findViewById(R.id.btn_backtologin);
btnback.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
startActivity(new Intent(RegisterActivity.this,LoginActivity.class));
}
}); //
tv_back.setOnClickListener(new View.OnClickListener() { //
@Override //
public void onClick(View v) { //
//返回键 //
RegisterActivity.this.finish(); //
} //
});
//注册按钮
btn_register.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取输入在相应控件中的字符串
getEditString();
//判断输入框内容
if(TextUtils.isEmpty(userName)){
Toast.makeText(RegisterActivity.this, "请输入用户名", Toast.LENGTH_SHORT).show();
return;
}else if(TextUtils.isEmpty(psw)){
Toast.makeText(RegisterActivity.this, "请输入密码", Toast.LENGTH_SHORT).show();
return;
}else if(TextUtils.isEmpty(pswAgain)){
Toast.makeText(RegisterActivity.this, "请再次输入密码", Toast.LENGTH_SHORT).show();
return;
}else if(!psw.equals(pswAgain)){
Toast.makeText(RegisterActivity.this, "输入两次的密码不一样", Toast.LENGTH_SHORT).show();
return;
}else if(isExistUserName(userName)){
Toast.makeText(RegisterActivity.this, "此账户名已经存在", Toast.LENGTH_SHORT).show();
return;
}else{
Toast.makeText(RegisterActivity.this, "注册成功", Toast.LENGTH_SHORT).show();
//把账号、密码和账号标识保存到 sp 里面
saveRegisterInfo(userName, psw);
//注册成功后把账号传递到 LoginActivity.java 中
// 返回值到 loginActivity 显示
Intent data=new Intent();
data.putExtra("userName", userName);
setResult(RESULT_OK, data);
//RESULT_OK 为 Activity 系统常量,状态码为-1,
// 表示此页面下的内容操作成功将 data 返回到上一页面,如果是用 back 返回过去的则不存在用 setResult 传递 data 值
RegisterActivity.this.finish();
}
}
});
}
private void getEditString(){
userName=et_user_name.getText().toString().trim();
psw=et_psw.getText().toString().trim();
pswAgain=et_psw_again.getText().toString().trim();
}
private boolean isExistUserName(String userName){
boolean has_userName=false;
//mode_private SharedPreferences sp=getSharedPreferences( );
// "loginInfo", MODE_PRIVATE
SharedPreferences sp=getSharedPreferences("loginInfo", MODE_PRIVATE);
//获取密码
String spPsw=sp.getString(userName, "");//传入用户名获取密码
//如果密码不为空则确实保存过这个用户名
if(!TextUtils.isEmpty(spPsw)) {
has_userName=true;
}
return has_userName;
}
private void saveRegisterInfo(String userName,String psw){
String md5Psw=MD5Utils.md5(psw);//把密码用 MD5 加密
//loginInfo 表示文件名, mode_private SharedPreferences sp=getSharedPreferences( );
SharedPreferences sp=getSharedPreferences("loginInfo", MODE_PRIVATE);
//获取编辑器, SharedPreferences.Editor
editor -> sp.edit();
SharedPreferences.Editor editor=sp.edit();
//以用户名为 key,密码为 value 保存在 SharedPreferences 中
//key,value,如键值对,editor.putString(用户名,密码);
editor.putString(userName, md5Psw);
//提交修改 mit();
mit();
} } (2 2 )数据库
本软件使用 SQLite 轻量级数据库,在创建数据库时,是通过使用SQLiteOpenHelper 类的构造函数实现 代码如下
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String TAG=DBOpenHelper.class.getSimpleName();
private static final int VERSION=1;
private static final String DB_NAME="memo.db";
public DBOpenHelper() {
super(MemoApplication.getContext(), DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE IF NOT EXISTS " + ColumnContacts.EVENT_TABLE_NAME + "( "
+ BaseColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ColumnContacts.EVENT_TITLE_COLUMN + " text, "
+ ColumnContacts.EVENT_CONTENT_COLUMN + " text, "
+ ColumnContacts.EVENT_CREATED_TIME_COLUMN + " datetime, "
+ ColumnContacts.EVENT_UPDATED_TIME_COLUMN + " datetime, "
+ ColumnContacts.EVENT_REMIND_TIME_COLUMN + " datetime, "
+ ColumnContacts.EVENT_IS_IMPORTANT_COLUMN + " INTEGER, "
+ ColumnContacts.EVENT_IS_CLOCKED + " INTEGER"
+ ")");
(3 3 )C C RUD 操作
使用 EventDao 类实现添加,查询,删除等操作 代码如下
1,查询操作
public List<Event> findAll() {
//降序排列,按重要性和创建时间 String sql="SELECT * FROM " + ColumnContacts.EVENT_TABLE_NAME + " ORDER BY " + ColumnContacts.EVENT_IS_IMPORTANT_COLUMN + " DESC, " + ColumnContacts.EVENT_CREATED_TIME_COLUMN + " DESC";
return mTemplate.query(sql, mCallback);
} public List<Event> findAllWithNOClocked() { String sql="SELECT * FROM " + ColumnContacts.EVENT_TABLE_NAME + " WHERE " + ColumnContacts.EVENT_IS_CLOCKED + "=" + Constants.EventClockFlag.NONE; return mTemplate.query(sql, mCallback); }
2,添加操作
public int create(Event event) { return (int) mTemplate.create(ColumnContacts.EVENT_TABLE_NAME, generateContentValues(event, false));
}
3,更新操作
public int update(Event event) {
return mTemplate.update(ColumnContacts.EVENT_TABLE_NAME, generateContentValues(event, true), BaseColumns._ID + "
=", Integer.toString(event.getmId())); }
4,删除操作
public int remove(List<Integer> ids) {
StringBuilder whereConditions=new StringBuilder(BaseColumns._ID + " IN(");
for (Integer id : ids) {
whereConditions.append(id).append(",");
}
whereConditions.deleteCharAt(whereConditions.length() - 1).append(")");
return mTemplate.remove(ColumnContacts.EVENT_TABLE_NAME, whereConditions.toString());
}
(4 4 )时钟提醒
在退出软件后,为了让我们的提醒服务在后台保活,编写 ClockService 类在后台运行。
代码如下
public class ClockService extends Service {
private static final String TAG="ClockService";
public static final String EXTRA_EVENT_ID="extra.event.id";
public static final String EXTRA_EVENT_REMIND_TIME="extra.event.remind.time";
public static final String EXTRA_EVENT="extra.event";
private EventDao mEventDao=EventDao.getInstance();
public ClockService() {
Log.d(TAG, "ClockService: Constructor");
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: onStartCommand");
WakeLockUtil.wakeUpAndUnlock();
postToClockActivity(getApplicationContext(), intent);
return super.onStartCommand(intent, flags, startId);
}
private void postToClockActivity(Context context, Intent intent) {
Intent i=new Intent();
i.setClass(context, ClockActivity.class);
i.putExtra(EXTRA_EVENT_ID, intent.getIntExtra(EXTRA_EVENT_ID, -1));
Event event=mEventDao.findById(intent.getIntExtra(EXTRA_EVENT_ID, -1));
if (event==null) {
return;
}
i.putExtra(EXTRA_EVENT_REMIND_TIME, intent.getStringExtra(EXTRA_EVENT_REMIND_TIME));
i.putExtra(EXTRA_EVENT, event);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i); }
第三部分功能介绍 (1 1 )登录模块
为了保证用户的信息安全,在登录界面实现使用账号,密码的登录功能。存储技术使用了 SharedPreference 键值对实现持久性存储。在登录界面将输入的账号与密码与已注册的账号密码进行比对。若账号为空,则提示“请输入用户名”;若密码为空,则提示“请输入密码”;若账号与密码不匹配,则提示“请输入密码”;若账号密码完全匹配,则提示“登录成功”,并跳转到主界面。
(2 2 )注册模块
用户第一次进入软件,可在登录界面选择“立即注册”,进入注册界面,输入自定义用户名,密码并再次输入密码,确保输入无误,点击注册按钮完成注册;若二次输入密码不一致,则提示“两次输入密码不一致”,并可以再次输入;注册成功自动跳转到登录界面。
(3 3 )主界面模块
用户登录成功,,进入主界面,可以查看所有已建立的备忘录,并选择操作,包括新建备忘录;浏览所有已建立备忘录;查询指定备忘录;修改备忘录内容;批量删除备忘录等。
(4 4 )新建备忘录模块
在主界面选择控件加号,进入新建备忘录界面。可选择以下内容输入为该备忘录建立标题,并选择提醒时间,选择该备忘录是否是重要事件,最后在备注中输入事件的详细信息,点击确定,提示备忘录成功建立,并可在主界面查看。
(5 5 )删除备忘录模块
在主界面点击垃圾桶按钮,进入删除备忘录功能,选择零个或多个备忘录,再次点击“删除”,提示删除成功。
第四部分遇到的问题和解决办法 (1 1 )无法刷新 UI
问题在测试过程中发现,当删除某备忘录时,MainActivity 的RecyclerView 不能及时刷新,后面经过仔细排查发现,Android 不允许在子线程中对 UI 进行刷新,对 UI 的刷新只能在主线程中实现。
解决办法Android 提供了一套异步消息处理机制,完美的解决了在子线程中进行 UI 操作的问题。首先在主线程中创建一个 Handler 对象,并重写他的handlerMessage()方法。然后当我们的子线程需要进行 UI 操作时,就创建一个 Message 对象,并通过 Handler 把这条消息发出去,之后这条消息会被添加到 MessageQueue 中等待被处理,而 Looper 一直尝试在 MessageQueue 中取出消息,最后回到 Handler 的 handlerMessage()中。这样,就可以安心的进行 UI刷新了。
(1 1 )
无法使用某些 API
问题某些功能的实现方法可以在网上搜索到,但实际使用时却无法使用。
解决办法问题主要时 Android 的 高版本移除了一些 API,导致使用时报错,后面通过查询发现,Android 在移除 API 的同时会发布替换的 API,所以问题也完美解决。
在测试过程中也遇到了各种各样的报错,大多经过百度搜索解决方案都可以解决,此处便不再一一赘述!
第五部分总结 通过本次的课程设计,我获益颇多,从之前从未接触过安卓开发到现在可以简单的开发一个安卓小程序,实现简单的增、删、改、查功能。备忘录是一个相对较简单的 app,上手难度不高,但我觉得在我的本次实验中还是有一些可圈可点之处。首先是功能上,备忘录和闹钟的搭配使用,抛开了传统备忘录只能单一记录事件的功能,做到了在记录事件的同时还可以为事件设置闹钟;在设计上,软件大的登录注册模块使用了持久化技术,具体使用了SharedPreference 键值对存储用户的账号和密码,并创新性的使用键来存储账号,值存储密码,具体做法是使用用户输入的账户名作为键值来取出对应存储的值,并将取出的值与用户输入的密码比对,如果相同,则判断登录成功,跳转到主界面;否则提醒用户账号或密码出错。
对软件的未来展望,在设计时疏忽了将用户的 id 加入我的备忘录数据库存储,这导致了所有用户共享自己的备忘录,即不同用户登录不同账号,看到的依然是相同的备忘录列表。具体解决方案应是在新建备忘录时将用户登录使用的账号作为主键和备忘录一并存储到数据库。其次在主界面刷新备忘录列表时进行判断,将用户账号作为判断条件,只有存储了该用户账号的备忘录内容才被从数据库取出;闹钟的提示声音目前还只是 Android Studio 内置的,后面应该可以丰富,比如播放歌曲或自定义声音。
通过这门课程以及实验,使我对安卓开发有了初步的理解和认识,在以后的学习过程中,我还会再对安卓开发进行深入的了解,使自己不断提升。