1.问题概述

设计一个通信录app

2.课程设计基本要求

(1)添加联系人

(2)查看所有联系人

(3)查看联系人的详情

(4)对联系人信息进行编辑修改、删除

(5)拨打联系人电话、发送短信

3.通信录设计需求分析

3.1可行性研究

在我们生活中,有如下四个通讯需求:

1.基础通话与通话记录

2.查看、管理联系人

3.收发短信

4.基于数据流量的社交与通讯工具

因此一个安卓通讯录app,对于手机来说是一项较为重要的功能。

3.1.1 技术可行性

随着计算机技术的发展,网络环境不断提升,各种通信工具技术也得到提高,同时,计算机软件开发越来越人性化,门槛更低,使用当前主流的JAVA语言完全可以开发出功能完善、简单易用的互联网程序,Android具备多种存储方式,对于通信录这样的轻量级数据,可以使用sharepreference或者sqlite和文件存储,技术上是可行的。

3.1.2 经济可行性

现在,计算机使用已经相当普遍,网络费用不断降低,开发通信录可以大大的方便管理联系人,同时开发此应用的周期短,Android也是开源免费,因此,此系统在经济方面是可行的。

3.1.3 操作可行性

目前是信息高速发展的时代,几乎人人都配备着手机,并且都会简单的使用手机,通信录无需复杂的操作,会用手机的人都能轻轻松松使用,因此操作上是可行的。

3.2 系统功能需求分析

通信录的主要功能是对联系人进行添加。浏览,修改和删除,同时具备快捷拨打电话。发送短信等功能。

3.2.1 系统功能模块概述

现在对此系统的功能模块进行概述。

1、功能主页模块:该模块用来为用户提供功能入口,用户可以选择添加联系人或者查看所有联系人

2、添加联系人模块:该模块下用户可以进行添加联系人。

3、查看所有联系人模块:该模块可以查看所有已添加的联系人信息。

4、联系人详情模块:该模块可以对联系人进行修改、删除或快捷拨打电话、发送信息

4.通信录概要分析

4.1通信录开发环境

本应用的开发语言是Java,所使用的IDE开发工具是Android Studio

4.2项目所需技术技术

本项目使用当前比较流行的MVC架构设计技术,利用Activity配合Fragment进行界面的切换显示,利用MMKV做数据存储读取

4.2.1 MMKV介绍

MMKV 是由腾讯开发的基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强,便于轻量级数据的存储。

4.2.2 Activity介绍

Activity是Android组件中最基本也是最为常见用的四大组件之一,Activity是一个应用程序组件,提供一个界面,用户可以用来交互为了完成某项任务。Activity中所有操作都与用户密切相关,是一个负责与用户交互的组件,可以通过setContentView(View)来显示指定控件。在一个android应用中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。Activity之间通过Intent进行通信。

4.2.3 Fragment介绍

Fragment是Android3.0后引入的一个新的API,他出现的初衷是为了适应大屏幕的平板电脑,当前普通手机开发也会加入这个Fragment,可以把他看成一个小型的Activity,又称Activity片段,使用Fragment可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理,从而可以更加方便的在 运行过程中动态地更新Activity的用户界面。Fragment并不能单独使用,他需要嵌套在Activity 中使用,尽管他拥有自己的生命周期,但是还是会受到宿主Activity的生命周期的影响,比如Activity 被destory销毁了,他也会跟着销毁。

4.3通信录应用的总体结构设计

通信录应用主要包括四大功能模块,主页菜单、添加联系人、浏览联系人、联系人详情等功能模块。总体模块如下所示。

待补充

5.通信录应用的详细设计

5.1功能主页设计

应用提供功能菜单,包括两个按钮菜单项,用来跳转到相应界面

5.1.1界面布局

包括两个按钮。用普通的线性布局包裹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<Button
android:textSize="18sp"
android:text="添加联系人"
android:id="@+id/addUser"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:textSize="18sp"
android:text="查看所有联系人"
android:id="@+id/allUser"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

点击添加按钮跳转到添加联系人的fragment:
1
2
3
4
5
6
view.findViewById(R.id.addUser).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((MainActivity)getActivity()).jumpToAddPage();
}
});

实际上的跳转逻辑在Activity里:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void jumpToAddPage() {
AddUserFragment addUserFragment = (AddUserFragment) getSupportFragmentManager().findFragmentByTag("add");
if (addUserFragment == null) {
addUserFragment = new AddUserFragment();
}
MainFragment fragment = (MainFragment) getSupportFragmentManager().findFragmentByTag("main");
if (fragment != null) {
getSupportFragmentManager().beginTransaction().hide(fragment)
.add(R.id.layout_container, addUserFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
} else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.layout_container, addUserFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
}
}

点击查看所有联系人按钮跳转到所有联系人列表界面:
1
2
3
4
5
6
view.findViewById(R.id.allUser).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((MainActivity)getActivity()).jumpToAllPage();
}
});

实际上的跳转逻辑在Activity里:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void jumpToAllPage() {
AllUserFragment allUserFragment = (AllUserFragment) getSupportFragmentManager().findFragmentByTag("all");
if (allUserFragment == null) {
allUserFragment = new AllUserFragment();
}
MainFragment fragment = (MainFragment) getSupportFragmentManager().findFragmentByTag("main");
if (fragment != null) {
getSupportFragmentManager().beginTransaction().hide(fragment)
.add(R.id.layout_container, allUserFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
} else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.layout_container, allUserFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
}
}

5.2添加联系人设计

点击添加联系人后跳转到添加联系人界面,布局如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:text="姓名:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<EditText
android:maxLength="7"
android:id="@+id/et_user"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:text="电话:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<EditText
android:maxLength="11"
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

<Button
android:textSize="18sp"
android:text="添加电话"
android:id="@+id/add"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:textSize="18sp"
android:text="返回"
android:id="@+id/back"
android:layout_width="match_parent"
android:layout_height="wrap_content" />


</LinearLayout>

这个界面包括两个TextView和两个EditTextView还有两个Button。TextView起提示作用,EditText接收用户的输入,Button进行数据的保存和返回。点击保存的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
view.findViewById(R.id.add).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = etName.getText().toString();
String phone = etPhone.getText().toString();
if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(phone)) {
CommonManager.getInstance().addUserInfo(new UserInfo(name, phone));
etName.setText("");
etPhone.setText("");
Toast.makeText(getActivity(),"添加成功!",Toast.LENGTH_LONG).show();
}else {
Toast.makeText(getActivity(),"姓名或电话不能为空!",Toast.LENGTH_LONG).show();
}
}
});

5.3查看所有联系人设计

布局如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<Button
android:id="@+id/back"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="回到首页"
android:textSize="18sp" />


<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">

<TextView
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="姓名"
android:textColor="@color/black"
android:textSize="18sp" />

<TextView
android:id="@+id/phone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="号码"
android:textColor="@color/black"
android:textSize="18sp" />
</LinearLayout>


<androidx.recyclerview.widget.RecyclerView
android:layout_marginTop="8dp"
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>


</LinearLayout>

其中最主要是用了RecyclerView进行用户信息的列表显示,RecycerView的适配器如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class RvAdapter extends RecyclerView.Adapter<RvAdapter.ItemHolder> {
private Context mContext;
private List<UserInfo> mList;
private ItemClickListener mItemClickListener;

public RvAdapter(Context context, List<UserInfo> list) {
mContext = context;
mList = list;
}

public interface ItemClickListener {
void onItemClick(UserInfo userInfo);
}

public void setOnItemClickListener(ItemClickListener itemClickListener) {
mItemClickListener = itemClickListener;
}

@NonNull
@Override
public ItemHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.rv_item, parent, false);

return new ItemHolder(itemView);
}

@Override
public void onBindViewHolder(@NonNull ItemHolder holder, int position) {
UserInfo data = mList.get(position);
holder.name.setText(data.name);
holder.phone.setText(data.phone);
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mItemClickListener!=null){
mItemClickListener.onItemClick(data);
}
}
});

}

@Override
public int getItemCount() {
return mList.size();
}


class ItemHolder extends RecyclerView.ViewHolder {
private TextView name;
private TextView phone;

public ItemHolder(@NonNull View itemView) {
super(itemView);
name = itemView.findViewById(R.id.name);
phone = itemView.findViewById(R.id.phone);
}

}
}

点击某一项,跳转到联系人详情页:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
  rvAdapter.setOnItemClickListener(new RvAdapter.ItemClickListener() {
@Override
public void onItemClick(UserInfo userInfo) {
((MainActivity)getActivity()).jumpToDetailPage(userInfo);
}
});
真正跳转逻辑在activity里:
public void jumpToDetailPage(UserInfo userInfo) {
UserDetailFragment userDetailFragment = (UserDetailFragment) getSupportFragmentManager().findFragmentByTag("detail");
if (userDetailFragment == null) {
userDetailFragment = new UserDetailFragment();
Bundle bundle = new Bundle();
bundle.putParcelable("userinfo",userInfo);
userDetailFragment.setArguments(bundle);
}
AllUserFragment fragment = (AllUserFragment) getSupportFragmentManager().findFragmentByTag("all");
if (fragment != null) {
getSupportFragmentManager().beginTransaction().hide(fragment)
.add(R.id.layout_container, userDetailFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
} else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.layout_container, userDetailFragment)
.addToBackStack(null)
.commitAllowingStateLoss();
}
}

5.4联系人详情页设计

联系人详情页不仅要显示联系人的姓名和名称,还要能对联系人的信息进行修改、删除,也可以拨打电话和发送短信,布局如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:text="姓名:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<EditText
android:maxLength="7"
android:id="@+id/et_user"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:text="电话:"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<EditText
android:maxLength="11"
android:id="@+id/et_phone"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

<LinearLayout
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<Button
android:layout_weight="1"
android:textSize="18sp"
android:text="删除"
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:layout_marginStart="16dp"
android:layout_weight="1"
android:textSize="18sp"
android:text="修改"
android:id="@+id/modify"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

<EditText
android:padding="8dp"
android:hint="请输入信息内容"
android:gravity="left"
android:layout_marginTop="8dp"
android:inputType="textMultiLine"
android:layout_weight="1"
android:id="@+id/et_msg"
android:layout_width="match_parent"
android:layout_height="0dp" />

<LinearLayout
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<Button
android:layout_weight="1"
android:textSize="18sp"
android:text="拨打电话"
android:id="@+id/call"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:layout_marginStart="16dp"
android:layout_weight="1"
android:textSize="18sp"
android:text="发送短信"
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

<Button
android:layout_marginTop="8dp"
android:textSize="18sp"
android:text="返回"
android:id="@+id/back"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

</LinearLayout>

有三个EditText,前两个是显示和编辑联系人的名字和号码,第三个是用来编辑发送的信息内容,删除按钮可以对信息进行删除,修改按钮对信息进行修改,拨打电话会立刻调动手机进行拨号,发送短信也会直接读取内容发送到相应号码上。删除代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  case R.id.delete:
Toast.makeText(getActivity(), "删除成功!", Toast.LENGTH_LONG).show();
CommonManager.getInstance().removeUserInfo(userInfo);
getActivity().getSupportFragmentManager().popBackStack();
break;
会显示一个删除成功的提示,并且对全局里的数据进行删除,然后返回上一页。
修改代码如下:
case R.id.modify:
String name = etName.getText().toString();
String phone = etPhone.getText().toString();
if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(phone)) {
userInfo.name = name;
userInfo.phone = phone;
Toast.makeText(getActivity(), "修改成功!", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(getActivity(), "姓名或电话不能为空!", Toast.LENGTH_LONG).show();
}
break;
把存储的数据替换为当前修改的信息,如果为空则提示并且不做修改。
拨打电话如下:
private void callPhone() {
String phoneNumber = etPhone.getText().toString();
if (TextUtils.isEmpty(phoneNumber)) {
Toast.makeText(getActivity(), "电话为空无法拨打电话!", Toast.LENGTH_LONG).show();
} else {
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneNumber));
startActivity(intent);
}
}

调用系统的action进行拨号,拨号前需要申请系统权限。
发送信息如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void sendSMS() {
String content = etMsg.getText().toString().trim();
String phone = etPhone.getText().toString().trim();
if (!TextUtils.isEmpty(content) && !TextUtils.isEmpty(phone)) {
SmsManager manager = SmsManager.getDefault();
ArrayList<String> strings = manager.divideMessage(content);
for (int i = 0; i < strings.size(); i++) {
manager.sendTextMessage(phone, null, content, null, null);
}
Toast.makeText(getContext(), "发送成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "手机号或内容不能为空", Toast.LENGTH_SHORT).show();
return;
}

}

调用系统的信息管理器发送信息,信息内容为编辑框里编辑的内容,如果内容为空则提示并且不发送

5.5数据的保存和恢复设计

在每次启动应用的时候会读取存储的数据,保存为全局数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
String jsonStr = MMKV.defaultMMKV().decodeString("userinfolist");
if (!TextUtils.isEmpty(jsonStr)) {
List<UserInfo> userInfoList = CommonManager.getInstance().getUserInfoList();
userInfoList.clear();
try {
Gson gson = new Gson();
JsonArray array = JsonParser.parseString(jsonStr).getAsJsonArray();
for (JsonElement jsonElement : array) {
userInfoList.add(gson.fromJson(jsonElement, UserInfo.class));
}
} catch (Exception e) {
e.printStackTrace();
}
}

用MMKV读取存储的数据,然后用Gson进行解析,放到数据集合中。
在关闭应用的时候进行数据保存:
1
2
3
4
5
6
7
@Override
protected void onDestroy() {
super.onDestroy();
Gson gson = new Gson();
String strJson = gson.toJson(CommonManager.getInstance().getUserInfoList());
MMKV.defaultMMKV().encode("userinfolist", strJson);
}

把数据集合转换json,用MMKV进行保存

6.系统的调试分析

6.1运行截图

功能主页:

添加用户:

所有联系人:

联系人详情页:

7.总结

经过这段开发,Android移动应用开发工作基本完成,相应的功能也基本实现,但系统的功能完善性还不是很好,由于本次系统开发受到我的编码能力的限制,因此本系统还存在很多方面的不足。

通过开发这个系统,我掌握了基本的过程,学习了知识的同时还掌握了开发系统的基本的相关操作,同时,对于java的操作也更加熟练了。