简介:这份资源是一套基于Android开发的焦点新闻App完整项目,面向Android入门开发者、课程设计学生及希望提升项目实战经验的程序员。资源包共包含3859个文件,体积约61.78MB,涵盖Java源码、SQLite数据库、演示视频、APK安装包以及大量图片、XML布局、CSS等前端展示文件,从功能实现到界面资源一应俱全。目前已有323人学习浏览,是课程设计、毕业设计或自学练手的可靠参考。项目完整覆盖Android生命周期、网络请求与JSON解析、SQLite存储、RecyclerView列表加载、Fragment多屏适配、AsyncTask异步处理及Glide图片加载等高频知识点,还涉及运行时权限和性能优化,非常适合对照源码与视频逐模块研读,是快速打通Android开发全流程的实战型资源。
1. 一个新闻 App 项目从解压到验收的关键路径
拿到"基于Android的焦点新闻App开发(源码+数据库+演示视频).rar"这类压缩包,最典型的场景是安卓app开发期末大作业或数据库课程设计的最后冲刺。焦点新闻的重点不在"新闻",而在"焦点"二字:栏目怎么切分、哪些内容置顶、收藏与阅读记录落在哪张表、界面与数据库如何对齐。一套能交差的项目通常由 Android Studio 工程、一个 SQLite 数据库文件和演示视频组成,评测老师第一眼看的是数据能不能查到,第二眼才看界面。下面我按接手这类项目的顺序展开:先立数据库,再走网络层,然后做界面,最后落在打包与录屏验收。
2. 数据库设计:焦点新闻的本地数据模型与增删改查
2.1 news 表结构:栏目、标题、时间、收藏状态怎么落库
焦点新闻的核心表只有一张 news,其余 user、favorite 表在课程设计里通常做成冗余或直接并入主表。把"焦点"语义拆成两个字段:is_top 表示置顶,is_favorite 表示收藏,这是最常见的做法。publish_time 用 TEXT 类型存yyyy-MM-dd HH:mm:ss,排序时按字符串比较即可,避免日期转换带来的时区问题。
2.1.1 news 表的字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER | 主键,自增 |
| category | TEXT | 栏目名称,如本地、国内、科技 |
| title | TEXT | 新闻标题,列表页直接展示 |
| summary | TEXT | 摘要,列表副标题 |
| content | TEXT | 正文,详情页展示 |
| source | TEXT | 来源,默认"焦点新闻" |
| publish_time | TEXT | 发布时间,用于倒序排列 |
| is_top | INTEGER | 置顶标记,1 置顶,0 普通 |
| is_favorite | INTEGER | 收藏标记,1 收藏,0 未收藏 |
| read_status | INTEGER | 阅读状态,用于列表灰显 |
| image_url | TEXT | 封面图地址,为空时显示占位图 |
建表 SQL 中,索引和默认值要提前想好,下面这段是可直接放进数据库课程设计报告里的写法。
CREATE TABLE news ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, title TEXT NOT NULL, summary TEXT DEFAULT '', content TEXT DEFAULT '', source TEXT DEFAULT '焦点新闻', publish_time TEXT DEFAULT (datetime('now', 'localtime')), is_top INTEGER DEFAULT 0, is_favorite INTEGER DEFAULT 0, read_status INTEGER DEFAULT 0, image_url TEXT DEFAULT '' ); CREATE INDEX idx_news_category ON news(category, publish_time DESC); CREATE INDEX idx_news_favorite ON news(is_favorite);注意 category 加了 NOT NULL,因为 TabLayout 的每个栏目都要靠这个字段过滤数据,空值会导致列表查询结果错位。索引 idx_news_category 按栏目和时间联合建立,栏目切换时执行WHERE category = ? ORDER BY publish_time DESC能直接走索引。is_favorite 单独建索引,是为了收藏页WHERE is_favorite = 1的查询不至于全表扫描。
2.2 用 SQLiteOpenHelper 建库与预置条目的最小代码
这个场景下我一般不用 Room,原因很直接:SQLiteOpenHelper 生成的 .db 文件可以直接从data/data/包名/databases/下拉出来,用 DB Browser 打开给老师看数据,而 Room 默认在抽象层内,答辩时多一层解释成本。另一个原因是 Room 要写 Entity、DAO、Database 三套文件,对期末大作业来说代码量反而变大。
public class NewsDbHelper extends SQLiteOpenHelper { private static final String DB_NAME = "focus_news.db"; private static final int DB_VERSION = 1; public NewsDbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_NEWS_SQL); insertPresetNews(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion < 2) { db.execSQL("ALTER TABLE news ADD COLUMN label TEXT DEFAULT ''"); } } private void insertPresetNews(SQLiteDatabase db) { ContentValues cv = new ContentValues(); cv.put("category", "焦点"); cv.put("title", "今日焦点:新闻App演示数据"); cv.put("is_top", 1); db.insert("news", null, cv); } }DB_VERSION 从 1 开始递增,onUpgrade 里用 ALTER TABLE 加列而不是 DROP 重建,这样用户升级时收藏数据不会丢。insertPresetNews 的作用是让冷启动时列表不为空,演示视频里断网状态下也能展示数据。如果预置数据量达到几百条,建议把 .db 文件放进 assets 目录,首次启动时用 InputStream 拷贝到 databases 目录,这里不再展开。
2.2.1 增删改查四个方法与返回值
数据库增删改查四个方法的写法虽然基础,但返回值是评审老师常问的点。insert 返回新行的 rowId,失败返回 -1;update 和 delete 返回受影响行数,为 0 说明条件没匹配上;query 返回 Cursor,必须在使用后关闭。
public long insertNews(News item) { ContentValues cv = new ContentValues(); cv.put("category", item.getCategory()); cv.put("title", item.getTitle()); cv.put("is_top", item.isTop() ? 1 : 0); return db.getWritableDatabase().insert("news", null, cv); } public int toggleFavorite(long id, boolean favorite) { ContentValues cv = new ContentValues(); cv.put("is_favorite", favorite ? 1 : 0); return db.getWritableDatabase().update("news", cv, "id = ?", new String[]{String.valueOf(id)}); }insert 的第二参数 nullColumnHack 传 null 即可,它只在 ContentValues 为空时才有意义。update 的第三个参数是 where 条件,注意用?占位,不要直接拼字符串,否则新闻标题里带单引号时 SQL 会直接报错。Cursor 查询结果建议封装成 List ,列表页 Adapter 里直接 getItem(position) 取数据。
2.3 数据库课程设计评审时最容易挑出的三个参数问题
第一个是 WAL 模式。新闻 App 的收藏操作发生在列表滑动过程中,主线程在读数据库,如果写操作直接落在主库文件上,可能出现短暂卡顿。在 SQLiteOpenHelper 的 onConfigure 里调用db.setWriteAheadLoggingEnabled(true),读写分离到 -wal 文件,列表滚动和收藏写入互不阻塞。
第二个是外键约束。如果单独做了 favorite 表,外键默认是关闭的,需要在 onConfigure 里执行db.setForeignKeyConstraintsEnabled(true),并在建表时写FOREIGN KEY (news_id) REFERENCES news(id)。不开启的话,删除新闻时收藏表会留下孤儿数据。
第三个是版本号与降级处理。onDowngrade 默认抛异常,如果老师把数据库文件从高版本换回低版本会直接崩溃。常见做法是改成db.setVersion(oldVersion)并清空缓存表,保证旧文件能重新走到 onUpgrade 逻辑。这三个点写进课程设计报告的"数据库优化"小节里,比贴十行代码更有说服力。
3. 数据层到界面:Retrofit 拉流、JSON 解析与列表渲染
3.1 网络方案选择:HttpURLConnection、OkHttp、Retrofit 在新闻项目里的分工
新闻 App 的联网部分有三个层次。原生 HttpURLConnection 只适合两三分钟讲完的简单 demo,因为读流、超时、线程切换全要手写;OkHttp 解决了连接复用和超时控制,但回调处理仍然繁琐;Retrofit 在 OkHttp 之上做接口声明式映射,一个注解对应一个方法,JSON 自动转对象。三者的取舍如下表。
| 方案 | 代码量 | 优点 | 适合场景 |
|---|---|---|---|
| HttpURLConnection | 大 | 无第三方依赖 | 纯本地 SQLite 演示 |
| OkHttp | 中 | 连接池、拦截器 | 少量接口的自学项目 |
| Retrofit | 小 | 注解映射、Gson 自动转换 | 带栏目的焦点新闻 App |
推荐直接用 Retrofit,因为新闻列表的路径和参数是固定的,用注解写比手拼 URL 直观得多。需要说明的是 Retrofit 底层依赖 OkHttp,所以 gradle 里只需要引 Retrofit 和 converter-gson 两条依赖。
3.2 用 Retrofit 定义焦点新闻接口的最小配置
在 build.gradle 的 dependencies 里添加依赖,版本号建议选 2.9.0,这是目前项目里最稳定的一版,不需要额外配转换器。
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'然后是接口定义与 Retrofit 实例化。
public interface NewsApi { @GET("api/focus/news") Call<NewsResponse> fetchNews(@Query("category") String category, @Query("page") int page, @Query("pageSize") int pageSize); } Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .client(okHttpClient) .build(); NewsApi api = retrofit.create(NewsApi.class);baseUrl 必须以/结尾,接口里的相对路径不能以/开头,这是 Retrofit 最容易踩的坑。@Query 会把参数拼到 URL 后面,对应服务端接收的 category、page、pageSize。okHttpClient 里建议配connectTimeout(10, TimeUnit.SECONDS)和readTimeout(15, TimeUnit.SECONDS),教室 Wi-Fi 不稳定时,10 秒连接超时比默认值更快进入失败重试分支。
3.2.1 回调中更新列表的注意点
api.fetchNews("科技", 1, 20).enqueue(new Callback<NewsResponse>() { @Override public void onResponse(Call<NewsResponse> call, Response<NewsResponse> response) { if (response.isSuccessful() && response.body() != null) { List<News> list = response.body().getItems(); adapter.submitList(list); } } @Override public void onFailure(Call<NewsResponse> call, Throwable t) { listState.setValue(STATE_ERROR); } });enqueue 的回调默认切回主线程,不需要手动 runOnUiThread。onResponse 不代表业务成功,HTTP 200 但 body 为 null 的情况也要判空。onFailure 里要同时处理断网和超时,建议把错误状态塞进 LiveData 或直接控制进度条的 visibility,让 UI 层根据状态切换"转圈、空列表、重试按钮"三种形态。
3.3 列表加载状态机:进度条、空态、失败重试的切换
新闻列表的加载状态如果只用一个 ProgressBar,演示时一旦断网就会露怯。我习惯用一个整形状态码加三套视图来控制。
int state = STATE_LOADING; // STATE_LOADING / STATE_SUCCESS / STATE_EMPTY / STATE_ERROR private void applyState(int state) { progressBar.setVisibility(state == STATE_LOADING ? View.VISIBLE : View.GONE); recyclerView.setVisibility(state == STATE_SUCCESS ? View.VISIBLE : View.GONE); emptyView.setVisibility(state == STATE_EMPTY ? View.VISIBLE : View.GONE); errorView.setVisibility(state == STATE_ERROR ? View.VISIBLE : View.GONE); if (state == STATE_LOADING) rotateProgress(); }注意进度条在列表已有数据时不要显示,否则下拉刷新会和转圈重叠。常见做法是加载更多时单独用列表底部的 footerProgress,首屏才用整页进度条。页面顶部的协调布局如果带了 banner,这个进度条最好放在 banner 下方,避免遮挡轮播图。
4. 栏目切换与详情页:焦点逻辑的前端落地
4.1 TabLayout + ViewPager2 联动栏目时的三个参数
焦点新闻的栏目切换用 TabLayout 加 ViewPager2 是标准答案。ViewPager2 替代了旧 ViewPager 的 FragmentStatePagerAdapter,创建 Fragment 的方式变成继承 FragmentStateAdapter。三个参数值得单独说:第一个是viewPager2.setOffscreenPageLimit(1),只预加载左右各一页,栏目多时不会一次性把所有 Fragment 全建出来;第二个是tabLayout.setTabMode(MODE_SCROLLABLE),栏目超过 5 个必须用滚动模式,固定模式会把 tab 挤变形;第三个是tabLayout.addOnTabSelectedListener里做埋点或滚动定位,不要在这里重复请求数据。
TabLayoutMediator mediator = new TabLayoutMediator(tabLayout, viewPager2, (tab, position) -> tab.setText(categoryList.get(position))); mediator.attach();TabLayoutMediator 是 material 库提供的联动器,手动设置 tab 文案即可。不要在 tab 的 onSelect 回调里手动 setCurrentItem,Mediator 已经处理过联动,重复设置会导致 Fragment 重建两次。
4.1.1 Fragment 与 ViewModel 的边界
每个栏目 Fragment 里如果各自持有 RecyclerView 和 Adapter,切走再切回时会丢失滚动位置。常见做法是 ViewModel 以栏目名为 key 缓存列表数据,Fragment onDestroyView 时清空 Adapter 引用但保留 ViewModel。这样 ViewPager2 预加载切换时,数据不会重新请求,界面也不会闪白。
4.2 焦点置顶排序与协调布局 banner 的实现
"焦点"二字的直接体现是置顶。列表查询时把 is_top 放在时间之前排序,SQL 写成ORDER BY is_top DESC, publish_time DESC,置顶新闻永远排在前面,时间倒序只对同级别生效。我的建议是置顶逻辑放在 SQL 层,而不是在 Java 里拆成两个列表再合并,后者的排序在分页时会出现重复项。
SELECT * FROM news WHERE category = ? ORDER BY is_top DESC, publish_time DESC LIMIT 20 OFFSET 0;banner 部分用协调布局加 ViewPager2 是最常见的效果。AppBarLayout 包裹 CollapsingToolbarLayout,标题栏随列表上滑折叠,banner 图片跟着缩放。轮播不能只依赖 ViewPager2 的自动翻页,还要在 onPageSelected 里同步指示器圆点,并用handler.postDelayed做 3 秒一帧的循环。注意在 onPause 时移除所有回调,否则离屏后 handler 仍持有上下文,演示过程中容易触发内存泄漏提示。
4.3 详情页:WebView 渲染与原生布局的取舍
新闻正文有两种展示路径。一种是后端返回 HTML,直接 WebView 加载,开发量最小,但 demo 里通常没有现成的网页服务;另一种是 content 字段存纯文本,详情页用 TextView 配合滚动布局。课程设计场景我建议混搭:富文本用 WebView 的 loadDataWithBaseURL,纯文本走原生布局,两种页面共用同一个 Activity,通过 Intent 传 id 区分。
Intent intent = new Intent(this, NewsDetailActivity.class); intent.putExtra("news_id", item.getId()); intent.putExtra("is_favorite", item.isFavorite()); startActivity(intent);详情页里收藏按钮的回写走第 2 章的 toggleFavorite 方法,成功后把按钮图标切换成实心状态,并更新列表页对应 item 的数据。演示视频里完整展示"收藏、退出、重新进入、收藏仍在"这个闭环,比展示任何动画效果都有说服力。
5. 打包与演示视频:把数据库和界面串成评审证据
5.1 录制前先做三类数据验证
演示最容易翻车的是断网状态。录制前先在 Android Studio 里关闭网络,冷启动 App,确认焦点栏目能显示 SQLite 预置数据,这说明数据库文件确实被打进了应用;然后打开网络拉取接口数据,验证 JSON 字段与列表渲染一致;最后收藏一条新闻,杀掉进程重新打开,确认 is_favorite 持久化成功。这三步走完,数据库、网络、界面三个环节就都覆盖了。
5.2 模拟器录屏的三个必调参数
用模拟器录屏时,分辨率、动画、时长三个参数直接影响观感。
adb shell wm size 1080x1920 adb shell settings put global window_animation_scale 0 adb shell settings put global transition_animation_scale 0 adb shell settings put global animator_duration_scale 0 adb shell screenrecord --time-limit 60 --size 1080x1920 /sdcard/demo.mp4 adb pull /sdcard/demo.mp4关闭系统动画后,列表滑动和 banner 切换不会拖泥带水。screenrecord 默认只能录 180 秒,加 --time-limit 60 控制在一分钟以内,方便直接嵌入说明文档。
5.3 源码、数据库、演示视频的归档检查
| 文件 | 检查点 |
|---|---|
| Android Studio 工程 | gradle 版本号与 dependencies 是否齐全 |
| databases 目录 | focus_news.db 能否用 DB Browser 打开 |
| 演示视频 | 是否包含"冷启动、栏目切换、收藏闭环"三段 |
| 数据库设计文档 | ER 图与 news 表字段是否一致 |
签名打包用 Build 菜单的 Generate Signed APK,keystore 文件建议和源码放同一层目录,避免换电脑后签名丢失无法安装。最后一件事是把模拟器里的数据库文件导出到项目根目录的 database 文件夹,老师拿到压缩包后边看视频边验证数据对应关系,这个项目的验收基本就稳了。
本文还有配套的精品资源,点击获取