一、系统需求分析
1.1 引言
1.1.1 开发目的
看点新闻App的开发是为了实时查看最新消息以了解社会动态,增长知识,增广见闻,顺便娱乐一下内心世界来放松自己。
1.1.2 开发背景
随着新媒体的崛起,纸媒遭受到重大打击,纸媒发展之路愈走愈窄,面临着前所未有的挑战。由于互联网的发展,人们获得信息的来源越来越多。为了满足人们对信息的渴望追求,各种类型的新闻App层出不穷,新闻App凭借其丰富的资讯资源,实时的信息推送和方便的社交互动被越来越多的用户认可。其具有许多报纸所没有的优势,它所提供的新闻信息打破了读报所需的线性时间和空间模式,慢慢改变了受众对世界的认识习惯。因此,开发一个新闻App显得尤其重要!
1.2 项目概述
1.2.1 产品功能描述
看点新闻App是基于Android Studio编辑器上开发的一款可以查看、添加、删除新闻,登录,编辑个人资料,查看个人收藏,清除缓存的安卓App。
1.2.2 运行环境
操作系统:Window10
开发工具:Android Studio 3.4.1
数据库:LitePal,mysql
硬件环境:
CPU:Intel® Core™i5-7200U CPU @ 2.50GHz 2.71 GHz
内存:12.0GB
1.3 系统功能描述
1.3.1 基本描述
看点新闻App通过获取互联网专业数据科技服务商—聚合数据的新闻接口来实现新闻数据的展示。由于每日限制100次请求,故每次请求时就缓存新闻数据到本地数据库中。当每次下拉刷新就分页查询10条新闻数据重新显示在对应的tab碎片中。用户可以根据自己的兴趣点击对应的tab标签来阅读新闻,清除本地缓存,登录后(取消)收藏新闻,查看个人收藏,添加、查看、删除个人发布的文章等。
1.3.2 系统功能描述
登录、注册模块:注册过的用户登录后才能(取消)收藏新闻,查看个人收藏,添加、查看、删除个人发布的文章等。
个人信息模块:用户登录后可以查看、修改个人资料。
个人文章模块:用户登录后可以发布、查看、删除文章。
个人收藏模块:用户登录后可以(取消)收藏新闻接口数据子项,并且查看自己收藏的所有新闻。
清除缓存模块:该功能主要清除浏览网页时在本地留下的离线内容和图片缓存。
展示新闻列表模块:该功能主要在用户打开App时,就请求聚合数据提供的新闻接口来显示在tab页面并将请求的数据缓存到本地数据库中。若当日请求次数已用完,则分页查询本地数据库。
二、系统总体设计
2.1 系统整体结构
系统结构图
2.2 数据存储设计
用户表:
字段名 数据类型 长度 是否为null 字段描述
userAccount varchar 20 否 账号
nickname varchar 20 是 昵称
userPwd varchar 20 否 登录密码
userSex varchar 6 是 性别
userBirthDay varchar 20 是 生日
userSignature varchar 50 是 个性签名
imagePath varchar 50 是 保存头像的路径
新闻表:
字段名 数据类型 长度 是否为null 字段描述
reason varchar 20 否 数据响应理由
result varchar 20 否 结果集
stat int 4 否 响应状态
uniquekey varchar 100 否 新闻id
title varchar 100 否 新闻标题
date varchar 50 否 请求日期
category varchar 10 否 新闻标签
author_name varchar 30 否 新闻作者
url varchar 200 否 新闻链接
thumbnail_pic_s varchar 200 否 附图1的地址
thumbnail_pic_s02 varchar 200 否 附图2的地址
thumbnail_pic_s03 varchar 200 否 附图3的地址
新闻收藏表:
字段名 数据类型 长度 是否为null 字段描述
userIdNumer varchar 20 否 用户账号
newsId varchar 100 否 新闻id
newSTitle varchar 100 否 新闻标题
newsUrl varchar 200 否 新闻链接
个人文章表:
字段名 数据类型 长度 是否为null 字段描述
userId varchar 20 否 用户账号
articleTitle Varchar 50 否 文章标题
articleAuthor varchar 20 否 用户昵称
articleTime varchar 50 否 发布时间
articleImagePath varchar 100 是 文章图片地址
articleContent varchar 100 否 文章内容
三、系统详细设计
3.1 登录、注册功能
(1) 用户第一次启动App时,可以查看新闻列表和具体的新闻内容,但是不能查看和编辑个人资料,(取消)收藏一条新闻内容,查看个人收藏和我的文章等。只有用户登录后才可实现这些被限制的功能。
(2) 当用户没有账号时,他可以选择注册并登录使用。注册过程中,因为LitePal数据库底层已内置了一个索引,所以我将用户账号设置为唯一标识的字段。若有用户输入相同的账号,系统将提醒用户重新输入账号。当输入密码和确认密码相同即可注册成功!注意,以上所有输入均不为空。
3.2 编辑资料功能
用户登录后点击编辑资料时,其根据需要修改对应的信息后,点击左上角的返回按钮,再点击主界面的抽屉弹出按钮,即可看到刚才修改的个人信息。
3.3 个人收藏功能
用户登录后在主界面新闻列表中点击某个新闻子项后进入webview展示新闻内容,点击右下角的收藏按钮即可进行收藏,再次点击则取消收藏!回到主界面后,点击左上角抽屉按钮弹出导航,点击个人收藏即可看到刚才收藏的新闻标题。
3.4 文章管理功能
用户登录后在主界面点击左上角的按钮,弹出抽屉导航栏,点击”我的文章”即可查看自己发布的新闻列表,点击右下角的悬浮按钮即可进入编辑文章的界面。在编辑文章界面输入文章标题和内容,同时可以添加显示文章的一张图片。点击发布按钮之后返回到“我的文章”界面即可看到刚才添加的文章!点击某个卡片即可查看该文章的具体内容。在文章详情界面,可以点击红色垃圾箱按钮删除该文章!
3.5 清除缓存功能
用户点击某个新闻子项进入浏览时,新闻内容和图片将会缓存到本地,此时用户可以在抽屉导航中点击“清除缓存”即可清除本地缓存。
四、系统实现
4.1 登录、注册功能
4.2 编辑资料功能
4.3 个人收藏功能
4.4 文章管理功能
4.5 清除缓存功能
4.6 部分主要代码
4.6.1异步消息处理机制来填充新闻列表
private void getDataFromNet(final String data) {
@SuppressLint("StaticFieldLeak")
AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
//子线程
@Override //储备key:547ee75ef186fc55a8f015e38dcfdb9a
protected String doInBackground(Void... params) { // 自己的key:af2d37d2ed31f7a074f1d49b5460a0b5,可以替换下面请求中的key
String path = "http://v.juhe.cn/toutiao/index?type=" + data + "&key=af2d37d2ed31f7a074f1d49b5460a0b5";
URL url = null;
try {
url = new URL(path);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
//设置请求方式
connection.setRequestMethod("GET");
//设置读取超时的毫秒数
connection.setReadTimeout(5000);
//设置连接超时时间
connection.setConnectTimeout(5000);
//获取状态码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { // 200
//获取服务器返回的输入流
InputStream inputStream = connection.getInputStream();
String json = streamToString(inputStream, "utf-8");
//返回任务的执行结果
return json;
} else {
//返回的状态码不是200
System.out.println(responseCode);
return 404 + data;
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return 404 + data;
}
//当给后台任务执行完毕并通过return语句返回时,此方法将被调用,返回来的数据可以进行一些UI操作,并将处理的参数传入
protected void onPostExecute(final String result) {
new Thread(new Runnable() {
@Override
public void run() {
//查看状态码是否为200,若不是(开子线程),然后从本地加载相应的数据
NewsBean newsBean = null;
//不包括endIndex
Log.d("后台处理的数据为:", "run: " + result);
if (!result.substring(0, 3).equals("404")) {
newsBean = new Gson().fromJson(result, NewsBean.class);
System.out.println(newsBean.getError_code());
if ("0".equals("" + newsBean.getError_code())) {
//obtainmessage()方法是从消息池中拿来一个msg,不需要另开辟空间new,new需要重新申请,效率低,obtianmessage可以循环利用;
Message msg = newsHandler.obtainMessage();
msg.what = UPNEWS_INSERT;
msg.obj = newsBean;
//发送一个通知来填充数据,因为安卓不允许在子线程中进行UI操作
newsHandler.sendMessage(msg);
} else {
//{"resultcode":"112","reason":"超过每日可允许请求次数!","result":null,"error_code":10012}
//实现从数据库加载数据
Log.d("超过请求次数或者其他原因", "run: 现在从本地数据库中获取");
Log.d("当前tab名字为:", "run: " + currentTabName);
threadLoaderData(currentTabName);
}
} else {
threadLoaderData(result.substring(3));
}
}
}).start();
}
//当后台任务中调用了publishProgress(Progress...)方法后,onProgressUpdate方法很快被执行
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
}
};
//启动异步加载任务
task.execute();
}
4.6.2加载数据,实现从本地数据库中读取数据刷新到newsListView的适配器中,其中用到了分页查询和多线程
private void threadLoaderData(final String category) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//沉睡1.5s,本地刷新很快,以防看不到刷新效果
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//若快速点击tab,则会出现getActivity()为空的情况,但是第一次加载肯定不会出错,所以将要拦截,以防app崩溃
if (getActivity() == null)
return;
//此处的用法:runOnUiThread必须是在主线程中调用,getActivity()获取主线程所在的活动,切换子线程到主线程
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
//重新生成数据,传入tab条目
loaderRefreshData(category);
//表示刷新事件结束,并隐藏刷新进度条
swipeRefreshLayout.setRefreshing(false);
}
});
}
}).start();
}
private void loaderRefreshData(final String category) {
//top,shehui,guonei,guoji,yule,tiyu,junshi,keji,caijing,shishang
String categoryName = "头条";
if (category.equals("top")) {
categoryName = "头条";
} else if (category.equals("shehui")) {
categoryName = "社会";
} else if (category.equals("guonei")) {
categoryName = "国内";
} else if (category.equals("guoji")) {
categoryName = "国际";
} else if (category.equals("yule")) {
categoryName = "娱乐";
} else if (category.equals("tiyu")) {
categoryName = "体育";
} else if (category.equals("junshi")) {
categoryName = "军事";
} else if (category.equals("keji")) {
categoryName = "科技";
} else if (category.equals("caijing")) {
categoryName = "财经";
} else if (category.equals("shishang")) {
categoryName = "时尚";
}
//页数加1
++pageNo;
List<NewsBean.ResultBean.DataBean> dataBeanList = new ArrayList<>();
NewsBean.ResultBean.DataBean bean = null;
int offsetV = (pageNo - 1) * pageSize;
Log.d("pageNo", "页数为: " + pageNo);
Log.d("offsetV", "偏移量为: " + offsetV);
Log.d("offsetV", "以下开始查询");
List<NewsInfoBean> beanList = LitePal.where("category = ?", categoryName).limit(pageSize).offset(offsetV).find(NewsInfoBean.class);
Log.d("TAG", "查询的数量为:" + beanList.size());
//若查询的结果为0,则重新定位页数为1
if (beanList.size() == 0) {
pageNo = 1;
offsetV = (pageNo - 1) * pageSize;
beanList = LitePal.where("category = ?", categoryName).limit(pageSize).offset(offsetV).find(NewsInfoBean.class);
Log.d("分页查询", "loaderRefreshData: 已经超过最大页数,归零并重新查询!");
}
Log.d("刷新查到的数据大小", "run: " + beanList.size());
for (int i = 0, len = beanList.size(); i < len; ++i) {
bean = new NewsBean.ResultBean.DataBean();
bean.setDataBean(beanList.get(i));
dataBeanList.add(bean);
Log.d("刷新id:", "run: " + beanList.get(i));
}
contentItems = dataBeanList;
//将dataBeanList赋值给全局的contentItems,否则点击新闻子项会出错,并且contentItems之前要清空,不然起不到更新视图的作用
TabAdapter adapter = new TabAdapter(getActivity(), contentItems);
newsListView.setAdapter(adapter);
//当adapter中的数据被更改后必须马上调用notifyDataSetChanged予以更新。
adapter.notifyDataSetChanged();
}
}
五、联系与交流
q:969060742 完整代码、报告、apk