
简介面向安卓课程设计与移动应用开发场景这套多媒体播放器项目将音乐播放与网络视频播放整合在同一应用中并加入基于Sqlite的注册登录界面适合作为毕业设计或满分课设的完整参考。与普通演示项目不同它把资源文件与工程代码分离音乐和视频素材部署在本地Tomcat中播放逻辑更接近真实应用场景代码风格、注释和整体UI均经过系统优化项目结构封装清晰。压缩包内共有625个文件大小约267MB涵盖安卓工程源码Java、XML、Gradle、界面与图标素材PNG、示例音视频MP3、MP4以及编译产物DEX、JAR、APK解压后可直接导入Android Studio运行。已有6249人学习下载无论是想快速完成多媒体课设还是学习网络播放、本地服务器资源加载与Sqlite账号存储都能从中找到可复用的实现思路整体是一个可以直接借鉴的多媒体开发范本。1. 多媒体播放器课设为什么这个项目值得拆一遍把音乐和视频塞进同一个 Android App听起来是每个移动开发课设的标配题但真正能同时把播放逻辑、用户系统、资源加载和 UI 观感都做到位的并不多见。这一版多媒体播放器的价值在于它没有把所有素材打包进 APK而是把媒体资源丢到本地 Tomcat 里按需加载同时在客户端用 SQLite 做账号注册登录再用 MediaPlayer 与 VideoView 分别承载音频和视频链路。这种「服务器存资源、客户端管状态」的结构恰好踩中了移动应用开发课设里最常见的高分点前后端有交互、本地存储有使用、多媒体接口有实践。适合两类人一是正在准备 Android 课设、想把项目做成「能演示、能答辩、能扩展」的学生二是做移动端开发但平时只写业务逻辑想看看别人如何组织播放器代码的工程师。项目本身不复杂但你可以从它的分层方式里学到如何把资源文件、数据库和 UI 拆干净进而移植到自己手头的应用里。下面直接进入结构与实现。2. 项目结构与资源分离Tomcat 承载媒体资源的选型理由这版播放器最大的结构变化是把音频和视频文件从res/raw或assets目录里挪了出来放到本地 Tomcat 的 ROOT 目录下App 通过 HTTP 访问。这个改动看似只是换个存放位置实际上把整个应用的体积、启动速度和后续更新方式都改变了。2.1 为什么不用 assets 或 raw早期课设版多媒体播放器会把三五个 MP3 和 MP4 直接丢进assets目录编译进 APK。优点是离线可用、路径写死缺点是 APK 体积膨胀几十 MB 的媒体资源会让 Android Studio 构建变慢模拟器安装时间变长而且以后想换一首歌、加一段视频必须重新打包整个 App。对课设答辩来说现场更新资源是很尴尬的事情。改用 Tomcat 之后媒体文件放在 Web 服务器上App 只存 URL修改资源只需替换服务器文件客户端不用动。2.2 Tomcat 目录布置与访问路径解压项目压缩包后你会看到两个核心目录一个是 Android Studio 工程源码另一个是 Tomcat 资源文件。资源文件内部是个文件夹里面按音乐和视频分类存放媒体。将这个文件夹整体复制到 Tomcat 的webapps/ROOT下然后启动 Tomcat。假设你的 Tomcat 默认端口是 8080本机 IP 是192.168.1.100文件夹名是media那么 App 里拼接的 URL 就是http://192.168.1.100:8080/media/music/song1.mp3 http://192.168.1.100:8080/media/video/movie1.mp4在 Android Studio 工程里通常会有一个常量类集中管理这些地址方便一处修改全局生效。常见写法如下public class Config { // 注意10.0.2.2 是 Android 模拟器访问宿主机 localhost 的别名 public static final String BASE_URL http://10.0.2.2:8080/media/; public static final String MUSIC_PATH BASE_URL music/; public static final String VIDEO_PATH BASE_URL video/; }参数说明10.0.2.2只在 Android 官方模拟器里有效指向你电脑的 127.0.0.1如果你用真机调试需要把它改成电脑在局域网里的 IP比如192.168.x.x否则真机访问不到。同时电脑防火墙要放行 8080 端口。2.3 网络权限与明文流量配置Android 6.0 之后访问网络需要动态权限Android 9 之后默认禁止明文 HTTP 流量。项目里如果用http://访问 Tomcat必须在AndroidManifest.xml里显式允许明文流量uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / application android:usesCleartextTraffictrue ... android:usesCleartextTraffictrue表示允许使用非 HTTPS 的流量调试本地服务器时最省事。生产环境建议换成 HTTPS但课设场景本地 Tomcat 用这个配置足够。如果你在 Android Studio 里访问网络资源时报CLEARTEXT communication not permitted十有八九就是漏了这条属性。另一个坑是模拟器里无法直接访问宿主机的localhost必须用10.0.2.2。这两个点解决掉资源加载的问题就少了一半。3. 注册登录与 SQLite用户系统的接地气实现很多课设项目的登录界面是写死账号密码这版选择用 SQLite 存储用户信息并用注册和登录两个流程把数据库操作串起来。SQLite 在 Android 里属于「内置数据库」不需要额外引入依赖也不用搭服务器适合课设答辩时现场演示数据持久化。3.1 表结构与数据库帮助类数据库设计很简单一张user表字段包括id、username、password。如果后续要扩展头像、昵称再加列就行。用SQLiteOpenHelper来管理数据库创建和版本升级public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME player.db; private static final int DB_VERSION 1; public DBHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE user ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password TEXT NOT NULL)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS user); onCreate(db); } }代码逻辑说明构造函数传入上下文和数据库名首次调用时系统会执行onCreate建表。username加UNIQUE约束保证注册时不会出现重复账号。onUpgrade里直接删表重建适合课设阶段快速迭代真实应用需要做数据迁移。3.2 注册登录的校验流程注册页拿到用户名和密码后先做本地校验再插入数据库。密码不要明文存储可以先用 MD5 或 SHA-256 哈希再入库课设答辩时也能多讲一个安全点。public boolean register(String username, String password) { SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(username, username); values.put(password, DigestUtils.md5Hex(password)); long result db.insert(user, null, values); return result ! -1; } public boolean login(String username, String password) { SQLiteDatabase db dbHelper.getReadableDatabase(); String hashedPwd DigestUtils.md5Hex(password); Cursor cursor db.rawQuery( SELECT * FROM user WHERE username? AND password?, new String[]{username, hashedPwd}); boolean success cursor.getCount() 0; cursor.close(); return success; }说明DigestUtils.md5Hex是 Apache Commons Codec 提供的方法如果不想引依赖可以用MessageDigest手写 MD5。Cursor查询后一定要close()否则内存泄漏在重复执行登录时会越来越明显。注册时insert返回-1表示插入失败最常见原因是用户名重复捕获SQLiteConstraintException后提示用户即可。3.3 SharedPreferences 保持登录态数据库只负责账号存储App 重启后是否需要重新登录靠SharedPreferences来记录。登录成功后写入一个布尔值和用户名SharedPreferences sp getSharedPreferences(session, MODE_PRIVATE); sp.edit().putBoolean(isLogin, true) .putString(username, username) .apply();apply()是异步写盘不阻塞主线程commit()是同步写盘性能差但在极少数需要立即生效的场景下才用。主界面启动时检查isLogin为false就跳转到登录页这样整个闭环就完整了。数据库这块容易踩的坑是创建数据库帮助类时用了getApplicationContext()还是Activity.this如果使用不当可能导致 Activity 泄漏。建议在Application或BaseActivity里统一初始化。4. 音乐与视频播放核心MediaPlayer 与 VideoView 的协同媒体播放器的主体功能分两块音频用MediaPlayer视频用VideoView。两者底层都依赖 Android 的多媒体框架但使用方式和生命周期管理差别很大。这版项目把播放逻辑单独封装没有把代码堆在 Activity 里这点很值得学习。4.1 播放列表的数据模型无论是音乐还是视频列表项都有共性标题、封面、播放地址。可以抽象出一个MediaItem类public class MediaItem { private String title; private String coverUrl; private String playUrl; private String duration; // 构造函数、getter/setter 省略 }音乐列表和视频列表共用这个模型只是playUrl指向不同的目录。解析列表数据用 JSON 比较方便。在 Tomcat 的媒体文件夹里放一个list.json记录所有曲目信息[ { title: 晴天, cover: http://10.0.2.2:8080/media/covers/sunny.jpg, play: http://10.0.2.2:8080/media/music/sunny.mp3, duration: 04:29 } ]App 用OkHttp或HttpURLConnection拉取这个 JSON然后用Gson转成对象列表。为什么不直接在代码里写死列表因为答辩时你可以现场往 JSON 里加一首歌App 下拉刷新就出现新歌这个演示效果远胜于改代码重装。4.2 音乐播放器 Service 封装音乐播放的通用要求是切到后台继续播放Activity 销毁不中断。因此不能把MediaPlayer直接放在 Activity 里需要放进Service。常见做法是用MediaPlayer配合前台服务public class MusicService extends Service implements MediaPlayer.OnCompletionListener { private MediaPlayer mediaPlayer; private int currentIndex -1; private ListMediaItem musicList new ArrayList(); Override public void onCreate() { super.onCreate(); mediaPlayer new MediaPlayer(); mediaPlayer.setOnCompletionListener(this); } public void play(int index) { if (index 0 || index musicList.size()) return; currentIndex index; try { mediaPlayer.reset(); mediaPlayer.setDataSource(musicList.get(index).getPlayUrl()); mediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } } Override public void onCompletion(MediaPlayer mp) { // 自动播放下一首 play(currentIndex 1); } }逻辑说明prepareAsync()是异步准备要在OnPreparedListener里调用start()避免在主线程里prepare()造成卡顿。reset()保证切换曲目时状态清干净。onCompletion是自然播放完后的回调这里做下一曲逻辑。Android 8.0 之后启动服务需要用startForegroundService()并在 Service 内 5 秒内调用startForeground()创建通知否则会被系统杀掉。4.3 视频播放与横竖屏切换视频播放直接用VideoView省事得多。它封装了MediaPlayer和SurfaceView只需要setVideoPath或setVideoURI然后start()。关键点在于 Activity 横竖屏切换时会重建播放进度会丢失必须在onSaveInstanceState里保存当前播放位置Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(position, videoView.getCurrentPosition()); } Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); int position savedInstanceState.getInt(position); videoView.seekTo(position); }这里要注意onRestoreInstanceState执行时VideoView可能还没准备好所以seekTo最好放在setOnPreparedListener回调里否则进度调不到。另外横屏播放时建议隐藏系统栏getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);IMMERSIVE_STICKY让用户从边缘滑动拉出系统栏后几秒后自动隐藏。如果只是课设简单全屏加横屏锁定也能接受但能在答辩时讲出系统 UI 可见性管理的细节分数差距就拉开了。5. UI 美化与细节优化从课设到能看的作品这个项目的 UI 做了整体优化标题写着「UI 美观」那你不能只用默认主题。Android 的 UI 优化分为视觉和性能两部分视觉上调整主题、状态栏、列表样式性能上处理图片加载和布局层级。对课设来说视觉提升投入产出比最高。5.1 沉浸式状态栏与主题定制默认的 ActionBar 颜色生硬改成沉浸式状态栏是低成本高收益的一步。在res/values/themes.xml里继承Theme.MaterialComponents.DayNight.NoActionBar然后在代码里设置状态栏透明Window window getWindow(); window.setStatusBarColor(Color.TRANSPARENT); window.getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);这样页面内容延伸到状态栏后面配合一个渐变背景或毛玻璃效果视觉上会立刻显得现代。注意不同 Android 版本的setSystemUiVisibility行为有差异Android 11 开始推荐用WindowInsetsController但课设项目用传统方式兼容性更好。5.2 RecyclerView 适配器与封面加载列表用RecyclerView替代ListView配合Glide加载网络封面。Glide 是 Android 最常用的图片加载库支持占位图和磁盘缓存比手动BitmapFactory.decodeStream要稳得多。适配器关键代码如下public class MusicAdapter extends RecyclerView.AdapterMusicAdapter.ViewHolder { private ListMediaItem list; private OnItemClickListener listener; public MusicAdapter(ListMediaItem list) { this.list list; } Override public void onBindViewHolder(NonNull ViewHolder holder, int position) { MediaItem item list.get(position); holder.title.setText(item.getTitle()); Glide.with(holder.itemView.getContext()) .load(item.getCoverUrl()) .placeholder(R.drawable.default_cover) .error(R.drawable.error_cover) .into(holder.cover); holder.itemView.setOnClickListener(v - { if (listener ! null) listener.onClick(position); }); } // ViewHolder 内部类省略 }说明Glide.with()传入的是 View 的 Context不要传Activity.this否则在 View 从 Activity 解绑时可能造成泄漏。placeholder和error分别是加载中与加载失败的占位图没有这两个参数网络差时列表会出现空白区域。5.3 列表滚动卡顿与内存优化UI 卡顿在多媒体应用里最常见的原因是主线程做耗时操作比如直接在主线程解析 JSON、加载大图、访问网络。把网络请求和 JSON 解析放到子线程或异步任务里再用runOnUiThread更新 UI。还有一个隐蔽问题RecyclerView的嵌套滑动和复杂 Item 布局会降低掉帧率。检查方式是用 Android Studio 自带的 Layout Inspector 和 Profile GPU Rendering。对于封面图压缩是必须的。Tomcat 里的图片不要直接扔一张 2MB 的原图用工具把封面压到 300KB 以内、分辨率不超过 500x500Glide 加载速度会明显改善。如果列表里同时存在多个 VideoView 或 SurfaceView内存压力会很大视频列表用「点击进入视频页播放」的模式避免在列表页内嵌多个播放器实例。6. 打包验证与常见坑从 AS 到真机演示课设最终要看跑起来的效果。Android Studio 环境问题往往比代码本身更耗时间这里整理调试时最高频的几个问题以及答辩演示建议。6.1 模拟器还是真机官方模拟器对 MediaPlayer 和 VideoView 的兼容性还行但对网络资源访问有个特殊点模拟器把宿主机当作10.0.2.2所以代码里如果用这个地址模拟器没问题。但模拟器的音频输出有时会延迟视频硬解也比较吃配置低端电脑上体验不佳。优先建议真机调试把BASE_URL改成电脑局域网 IP 即可。调试方式BASE_URL 值优点缺点官方模拟器http://10.0.2.2:8080/media/无需真机音视频性能依赖电脑真机http://192.168.x.x:8080/media/真实性能、触摸反馈需同一 Wi-Fi、防火墙放行远程服务器http://your-server-ip:8080/media/可远程演示需公网 IP 或内网穿透真机调试还需要在开发者选项里开启 USB 调试部分小米、华为手机需要额外登录账号才能开启这个在答辩前必须提前确认。6.2 常见报错处理tag number over 30 is not supported这个报错出现在 Android Studio 构建时通常是某个依赖里的 DEX 方法数超过旧版 DX 工具的限制。解决办法是升级targetSdkVersion并启用 D8 混淆器或者清理无用的依赖库。实际项目中我遇到这个错误多半是引了多个包含相同类的库。Unable to resolve host网络地址写错了或者 DNS 解析失败。先检查BASE_URL里的 IP 是否正确再用真机浏览器访问该地址看能否打开 JSON 文件。E/MediaPlayer: Should have subtitle controller already set视频播放器相关的警告一般不影响播放但会在 Logcat 里刷屏。可以忽略或升级ExoPlayer替代VideoView。SQLite 插入报UNIQUE constraint failed注册用户名重复代码里要先查询一次并提示或者用insertWithOnConflict处理重复策略。6.3 课设答辩时的演示路径演示不要直接进首页按照「注册新用户 → 登录 → 音乐列表 → 播放音乐 → 切换后台继续播 → 进视频列表 → 播放视频 → 横竖屏切换」这条线走每一步都能对应到一个技术点。注册时讲 SQLite 表结构登录后讲 SharedPreferences 保存会话播放音乐时讲 Service 生命周期视频切换时讲状态保存。Tomcat 资源那边可以现场往list.json里加一条记录刷新列表出现新歌让评委看到资源与 App 解耦的效果。最后一件事把 Tomcat 的启动脚本、项目源码、README 放在同一个文件夹里README 里写明 JDK 版本、Android Studio 版本、Tomcat 版本和端口配置。很多课设扣分不是因为功能是因为助教拿到项目后跑不起来。用这篇文章里的思路把资源和代码理顺答辩演示会流畅很多。本文还有配套的精品资源点击获取