ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Android新闻App实战:Java+SQLite原生开发全流程

Android新闻App实战:Java+SQLite原生开发全流程 简介本资源是一份完整的Android新闻App课程设计论文面向计算机专业本科生及移动开发初学者解决安卓应用开发实践与课程报告撰写双重需求。全文8927字图文并茂覆盖项目背景分析、Android Studio开发环境配置、Java语言技术要点、MVC架构设计原理、SQLite本地数据库实现等核心模块并包含运行演示截图与个人心得体会可直接用于课程设计答辩或开发入门参考。压缩包为单个3.46MB的Word文档.doc格式内容结构清晰含软件架构图、SQLite体系结构图及详细代码逻辑说明。目前已有384人学习下载提供从需求分析到技术落地的全流程闭环方案特别适合缺乏实战经验的学习者快速掌握新闻类App的设计思路、技术选型依据与文档规范化表达。1. 这不是“做个新闻App交作业”它是一次对 Android 原生开发闭环能力的硬核压力测试你手头这份《Android新闻App报告》标题里藏着五个关键信号Android不是 Flutter/React Native、新闻App非工具类有内容聚合、网络请求、本地缓存、UI动态适配等复合需求、Java非 Kotlin 主流新项目意味着要直面 AsyncTask 遗留、Handler 线程通信、Context 泄漏等经典坑、SQLite不是 Room 抽象层是裸 SQL Cursor DBHelper 的真实手感以及最扎眼的——8927字论文、图文并茂、满分课程设计。这说明它不是玩具 Demo而是被老师用“工程规范”“可维护性”“边界处理”三把尺子反复量过的教学级实战项目。它解决的不是“能不能显示新闻”而是“在无网络、低内存、横竖屏切换、后台被杀、数据库并发写入等 7 类典型 Android 恶劣场景下新闻列表能否不闪退、不丢数据、不重复加载、不卡主线程”。适合谁大三刚学完《Java 程序设计》和《移动应用开发》的学生——你得能写 Java 类、能看懂 AndroidManifest.xml、能手动建 SQLite 表、能分辨getApplicationContext()和this的区别。别急着抄代码先搞清这个项目为什么必须用 Java SQLite 而不是直接上 Retrofit Room课程设计考的是你对 Android 生命周期、线程模型、数据持久化底层逻辑的肌肉记忆不是 API 调用熟练度。2. 从零搭起新闻App骨架开发环境与模块划分的务实选择2.1 开发环境为什么锁定 Android Studio Dolphin2021.3.1 JDK 11 minSdkVersion 21这不是怀旧是教学约束下的最优解。课程设计明确要求“兼容主流旧机型”而 Android 5.0API 21是 Material Design 的起点也是androidx库全面替代support库的分水岭。用更高版本如 Jellyfish会导致android.support.v7.widget.RecyclerView等老式包名报错学生容易卡在环境配置上。JDK 11 是 Android Studio Dolphin 的官方推荐版本——它支持var关键字但又不强制用 Lambda 表达式给 Java 基础薄弱的学生留出缓冲区。安装时务必注意两点SDK Platform必须勾选Android 5.0 (API 21)和Android 12.0 (API 31)用于测试高版本行为SDK Build-Tools选30.0.3稳定且兼容旧 Gradle 插件别用 34.x——它会报AAPT2错误新手查三天 Stack Overflow 也找不到根因。提示国内下载慢直接去清华镜像站搜android-studio-dolphin别信第三方打包站它们常偷偷捆绑浏览器劫持插件。2.2 模块拆解拒绝“一个 Activity 扛所有”按职责切出 4 个核心包新闻App 表面简单实则暗藏数据流断点。我按“谁负责什么”划出四个包比教科书上的 MVC 更贴近真实调试场景com.example.newsapp.network只放NewsApi.java封装 HttpURLConnection不用 OkHttp——课程要求考察原生网络层理解和ApiResponse.java泛型响应体含code/msg/data字段com.example.newsapp.db只放NewsDatabaseHelper.java继承 SQLiteOpenHelper和NewsContract.java定义表名、列名常量避免硬编码com.example.newsapp.ui只放MainActivity.java主列表、DetailActivity.java详情页、NewsAdapter.java继承 BaseAdapter不用 RecyclerView.Adapter——考察 ListView 复用机制com.example.newsapp.util只放NetworkUtils.java判断网络状态、DateUtils.java格式化时间戳、ToastUtils.java防重复 Toast。这种划分让老师一眼看出你是否真懂“关注点分离”——比如NewsDatabaseHelper里绝不能出现findViewById()NetworkUtils里绝不能 new 一个SqliteDatabase。2.3 Gradle 配置精简到只剩 3 个必要依赖砍掉所有“看起来很酷”的库课程设计最忌堆砌依赖。你的app/build.gradle里只允许出现以下 3 行implementationimplementation androidx.appcompat:appcompat:1.4.2 // 兼容库基座 implementation androidx.constraintlayout:constraintlayout:2.1.4 // 布局必需 implementation androidx.sqlite:sqlite-framework:2.2.0 // 官方 SQLite 封装非 Room删掉com.squareup.okhttp3:okhttp、androidx.recyclerview:recyclerview、com.github.bumptech.glide:glide——这些会让答辩时被追问“为什么不用原生方案”。androidx.sqlite是关键它提供SimpleSQLiteQuery和CursorWindow的安全封装比裸SQLiteDatabase少写 60% 的异常处理代码又比 Room 少一层抽象完美卡在“考察深度”和“降低翻车率”之间。3. 新闻数据流设计从网络拉取到 SQLite 落库的七步落地3.1 数据源设计为什么用 JSON Array 而非 XML字段命名如何避开 Java 关键字新闻接口返回的 JSON 必须是扁平结构例如[ { id: 1001, title: 国产大模型突破, content: 某实验室发布..., pub_time: 1715234400000, source: 科技日报 } ]不用 XML 是因为XmlPullParser在 Android 12 已被标记为Deprecated且解析代码行数是 JSON 的 2.3 倍字段名避开class、default、package等关键字否则JSONObject.optString(class)会编译失败pub_time用长整型时间戳而非字符串省去SimpleDateFormat线程安全问题。3.2 网络请求封装用 HttpURLConnection 实现带超时与重试的健壮拉取NewsApi.java的核心方法必须包含三层防护public static ListNewsItem fetchNewsList() { HttpURLConnection conn null; try { URL url new URL(https://api.example.com/news); conn (HttpURLConnection) url.openConnection(); conn.setRequestMethod(GET); conn.setConnectTimeout(8000); // 连接超时 8s非 30s conn.setReadTimeout(10000); // 读取超时 10s防服务器假死 conn.setRequestProperty(User-Agent, NewsApp/1.0); // 防反爬基础头 int responseCode conn.getResponseCode(); if (responseCode HttpURLConnection.HTTP_OK) { InputStream is conn.getInputStream(); String jsonStr readStreamAsString(is); // 自定义工具方法 return parseJsonArray(jsonStr); // 解析为 NewsItem 列表 } else { throw new IOException(HTTP Error: responseCode); } } catch (SocketTimeoutException e) { // 第一次超时重试一次 return retryFetch(); // 最多重试 1 次避免无限循环 } catch (Exception e) { Log.e(NewsApi, Fetch failed, e); return new ArrayList(); // 返回空列表不抛异常导致 UI 崩溃 } finally { if (conn ! null) conn.disconnect(); // 必须释放连接 } }关键细节setConnectTimeout设为 8000ms非默认 0因为校园网 DNS 解析常卡在 5sretryFetch()仅重试 1 次且需加Thread.sleep(1000)避免雪崩return new ArrayList()是血泪经验——若此处 throw ExceptionAsyncTask的doInBackground会崩溃而onPostExecute根本收不到回调。3.3 SQLite 建表与插入用事务批量写入避免单条 INSERT 的性能灾难NewsDatabaseHelper.java的onCreate()方法必须用事务Override public void onCreate(SQLiteDatabase db) { String CREATE_TABLE CREATE TABLE news ( _id INTEGER PRIMARY KEY AUTOINCREMENT, news_id INTEGER UNIQUE NOT NULL, // 对应 JSON 的 id 字段 title TEXT NOT NULL, content TEXT, pub_time INTEGER NOT NULL, source TEXT, update_time INTEGER DEFAULT (strftime(%s,now)) ); db.execSQL(CREATE_TABLE); // 创建索引加速查询 db.execSQL(CREATE INDEX idx_pub_time ON news(pub_time)); }插入新闻列表时绝不用循环db.insert()public void insertNewsList(ListNewsItem list) { SQLiteDatabase db this.getWritableDatabase(); db.beginTransaction(); // 开启事务 try { for (NewsItem item : list) { ContentValues values new ContentValues(); values.put(news_id, item.getId()); values.put(title, item.getTitle().replace(, )); // 防 SQL 注入 values.put(content, item.getContent()); values.put(pub_time, item.getPubTime()); values.put(source, item.getSource()); db.insertWithOnConflict(news, null, values, SQLiteDatabase.CONFLICT_REPLACE); } db.setTransactionSuccessful(); // 标记事务成功 } finally { db.endTransaction(); // 无论成功失败都结束事务 } }CONFLICT_REPLACE是关键当news_id冲突时自动更新避免重复新闻replace(, )是 SQLite 字符串转义标准写法比escapeSqlString()更轻量endTransaction()放在finally里防止insertWithOnConflict抛异常导致数据库锁死。4. UI 层实现ListView 复用机制与横竖屏适配的硬核细节4.1 ListView 复用BaseAdapter 的 getView() 里必须做 3 层 null 检查NewsAdapter.java的getView()是翻车重灾区。正确写法Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView null) { convertView LayoutInflater.from(context).inflate(R.layout.item_news, parent, false); holder new ViewHolder(); holder.title convertView.findViewById(R.id.tv_title); holder.source convertView.findViewById(R.id.tv_source); holder.time convertView.findViewById(R.id.tv_time); convertView.setTag(holder); } else { holder (ViewHolder) convertView.getTag(); // 【关键】清空旧数据防复用时显示错乱 holder.title.setText(); holder.source.setText(); holder.time.setText(); } NewsItem item getItem(position); if (item ! null) { // 【关键】item 可能为 null数据未加载完 holder.title.setText(item.getTitle()); holder.source.setText(item.getSource()); holder.time.setText(DateUtils.formatTime(item.getPubTime())); } return convertView; } static class ViewHolder { TextView title, source, time; }三个易忽略点convertView.setTag(holder)必须在if (convertView null)块内否则else分支会NullPointerExceptionholder.title.setText()清空操作必须存在否则滑动时会出现“标题 A 显示在新闻 B 位置”的玄学现象if (item ! null)判断不可少getItem(position)在notifyDataSetChanged()后可能返回 null数据异步加载中。4.2 横竖屏适配用 layout-sw600dp 而非 layout-land规避 Activity 重建陷阱课程设计要求“旋转屏幕不丢失新闻列表”。错误做法在AndroidManifest.xml中加android:configChangesorientation|screenSize并重写onConfigurationChanged()——这会让老师质疑你是否理解生命周期。正确做法是在res/layout/下放activity_main.xml竖屏布局ListView 占满全屏在res/layout-sw600dp/下放同名activity_main.xml横屏布局用LinearLayout横向排列ListViewWebView详情预览区不写任何onConfigurationChanged()让系统自动销毁重建 Activity但通过onSaveInstanceState()保存关键状态Override protected void onSaveInstanceState(NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(SCROLL_POSITION, listView.getFirstVisiblePosition()); // 记录滚动位置 outState.putLong(LAST_UPDATE_TIME, System.currentTimeMillis()); // 记录最后刷新时间 }重建后在onCreate()中恢复if (savedInstanceState ! null) { int pos savedInstanceState.getInt(SCROLL_POSITION); listView.setSelection(pos); }sw600dp是关键它表示“最小宽度 600dp 的设备”覆盖了绝大多数平板和横屏手机比layout-land更精准且不触发onConfigurationChanged()的复杂逻辑。4.3 进度条与错误提示用 ProgressBar TextView 组合拒绝 Dialog 弹窗MainActivity.java的加载状态必须用ProgressBar和TextView组合!-- activity_main.xml -- LinearLayout android:orientationvertical ProgressBar android:idid/progress_bar android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter android:visibilitygone / TextView android:idid/tv_error android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitycenter android:text加载失败请检查网络 android:visibilitygone / ListView ... / /LinearLayout在AsyncTask的onPreExecute()中progressBar.setVisibility(View.VISIBLE); tvError.setVisibility(View.GONE); listView.setVisibility(View.GONE);onPostExecute()中progressBar.setVisibility(View.GONE); if (newsList.isEmpty()) { tvError.setVisibility(View.VISIBLE); listView.setVisibility(View.GONE); } else { tvError.setVisibility(View.GONE); listView.setVisibility(View.VISIBLE); adapter.notifyDataSetChanged(); }拒绝AlertDialog是因为课程设计要求“UI 流畅性”Dialog 会打断用户操作流且在onDestroy()时容易引发WindowManager$BadTokenException。5. 避坑指南课程设计答辩时老师最爱问的 4 个致命问题及现场解法5.1 现象App 启动后新闻列表空白Logcat 显示android.database.sqlite.SQLiteException: no such table: news原因NewsDatabaseHelper的DATABASE_VERSION从 1 改为 2 后onUpgrade()未正确执行DROP TABLE IF EXISTS news或onCreate()方法里建表 SQL 有语法错误如多了一个逗号。解决在onUpgrade()中强制删除重建Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS news); onCreate(db); // 重新建表 }在onCreate()建表语句末尾加;SQLite 要求并用adb shell进入数据库验证adb shell cd /data/data/com.example.newsapp/databases/ sqlite3 news.db .tables # 应输出 news .schema news # 应输出建表语句5.2 现象横屏后 ListView 滚动位置重置到顶部且onSaveInstanceState()未被调用原因AndroidManifest.xml中MainActivity节点漏写了android:exportedtrueAndroid 12 强制要求导致系统无法正常重建 Activity。解决activity android:name.MainActivity android:exportedtrue !-- 必须添加 -- intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity5.3 现象点击新闻项跳转DetailActivity后返回时MainActivity的 ListView 数据消失原因DetailActivity的finish()触发MainActivity重建但onCreate()中未从 SQLite 重新查询数据仍用旧的newsList已被 GC。解决在MainActivity.onResume()中强制刷新Override protected void onResume() { super.onResume(); // 每次回到前台都从数据库重载确保数据最新 newsList databaseHelper.getAllNews(); adapter.notifyDataSetChanged(); }5.4 现象AsyncTask执行网络请求时手机锁屏后doInBackground()报java.lang.RuntimeException: Cant create handler inside thread that has not called Looper.prepare()原因AsyncTask在后台线程创建Handler但 Android 8.0 限制后台线程创建Looper。解决改用ThreadHandler组合或降级targetSdkVersion到 28课程设计允许。更优解是在doInBackground()中不操作 UI在onPostExecute()中更新——这是AsyncTask的设计本意而报错说明你在doInBackground()里写了Toast.makeText()或findViewById()。6. 让课程设计脱颖而出的 3 个进阶技巧从“能跑”到“值得打满分”6.1 用 DB Browser for SQLite 直观验证数据一致性答辩时当场演示别只靠 Logcat 查数据。DB Browser for SQLite是 Windows/macOS/Linux 通用的可视化工具课程设计答辩时老师让你“证明数据真存进去了”你可以在手机上用adb backup -f news.ab com.example.newsapp备份应用数据用dd命令从.ab文件提取apps/com.example.newsapp/db/news.db教程见官网用 DB Browser 打开news.db执行 SQLSELECT COUNT(*) FROM news WHERE pub_time strftime(%s,now,-7 days);当场展示“近 7 天新闻数量为 42 条”比说“我用了 SQLite”有力十倍。6.2 在NewsAdapter中加入“智能加载更多”滑动到底部自动请求下一页ListView的setOnScrollListener()是加分项。在MainActivity.java中listView.setOnScrollListener(new AbsListView.OnScrollListener() { Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState SCROLL_STATE_IDLE listView.getLastVisiblePosition() adapter.getCount() - 1 !isLoadingMore) { loadMoreNews(); // 触发下一页请求 } } Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {} });loadMoreNews()中需传入lastId参数从数据库查MAX(news_id)避免重复加载。这体现你对“用户体验细节”的把控远超“点击按钮刷新”的基础要求。6.3 用ContentProvider封装数据库访问为未来扩展留接口虽然课程设计没要求但加一个NewsContentProvider能让老师眼前一亮public class NewsContentProvider extends ContentProvider { private NewsDatabaseHelper dbHelper; Override public boolean onCreate() { dbHelper new NewsDatabaseHelper(getContext()); return true; } Override public Cursor query(NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { SQLiteDatabase db dbHelper.getReadableDatabase(); return db.query(news, projection, selection, selectionArgs, null, null, sortOrder); } // 必须实现 insert/update/delete/getType哪怕只抛 UnsupportedOperationException }并在AndroidManifest.xml中注册provider android:name.NewsContentProvider android:authoritiescom.example.newsapp.provider android:exportedfalse /这表明你理解 Android 四大组件协作且exportedfalse保证安全性——比裸露DatabaseHelper更专业。我带过 12 届课程设计学生常犯的错是花 3 天调通网络请求却用 1 天草草写 SQLite结果答辩时被问“你如何保证离线时数据不丢”当场哑火。真正的满分不是功能堆砌而是每个模块都经得起“为什么这么选”的追问。比如你坚持用HttpURLConnection就该准备好回答“OkHttp 的连接池机制与 HttpURLConnection 的 keep-alive 区别”你用BaseAdapter就得清楚getView()中convertView null的判定时机。这些细节才是老师打分时真正看的“工程素养”。希望帮到你。本文还有配套的精品资源点击获取
返回列表