
简介2023年全国职业院校技能大赛中职移动应用与开发赛项赛题是一份面向中职组选手及指导教师的官方赛题文档适合备赛训练、赛制研读与模拟评分。赛题以“数字生活”为主题围绕智慧党建、乡村民宿、智慧健康等融合场景展开模块A要求使用Adobe XD完成看电影、物流查询、找工作、宠物医院等界面的高保真原型图模块B基于Android Studio或HbuilderX开发“时代楷模”App覆盖UI还原、网络通信、数据解析、数据存储与打包发布模块C则考核产品使用手册和缺陷分析文档的提交质量并明确了各模块分值、竞赛时间、成果物命名规范、提交路径以及禁止填写选手信息的注意事项。资源为1个PDF文件压缩包大小2.55MB已有945人学习研读后可快速梳理国赛考核逻辑判断自身在原型设计、前端编码、测试交付等环节的薄弱点并依照官方任务要求开展仿真训练是一份适合中职移动应用赛项备赛的参考材料。1. 2023 年的中职移动应用与开发赛项赛题究竟在考什么很多第一次带队的老师看到赛题压缩包时第一反应是“这不就是现场写个 App 吗”。真正开始做才发现2023 年的中职移动应用与开发赛项更像一场“限时还原工程”拿到原型图、切图和 JSON 数据文件在规定时间里把页面搭出来、把交互跑通、把接口接上最后还要把工程打包提交到评分环境。评分的人不会关心你的动画有多炫只会按任务书里的操作步骤逐一点击点不到就算丢分。这个方向适合两类人一类是正在备赛的中职学生需要知道先练什么、赛场上哪些地方最容易卡住另一类是指导老师想快速判断训练计划该往哪个模块倾斜。赛题表面上考代码实际上考的是对任务书的拆解能力、对素材目录的理解以及“在有限时间内先保住核心流程”的工程判断力。下面按我帮某中职学校备赛时的思路把这套赛题从前到后拆一遍附带可直接套用的代码和避坑记录。2. 赛题结构和评分逻辑先判断裁判手里拿的是哪张表2.1 赛题包的常见内容和工作流程中职移动应用与开发赛项通常会把所有材料打成一个压缩包解压后大约包含五类东西任务书、素材目录、接口说明、原型图、评分标准节选。任务书会写清楚需要实现几个页面、每个页面的功能边界、最低支持的 Android 版本素材目录一般分成res、assets、icon等子目录把切图、占位图、JSON 数据都放在里面。接题后的前 10 分钟别急着打开 Android Studio先把压缩包里的目录结构、文件命名和接口字段看一遍。这个习惯能避免后面反复回去翻素材。我一般会让队员按下面的顺序过一遍材料先读任务书圈出“必须实现”和“加分项”再打开原型图对照任务书列出页面清单最后打开 JSON 数据文件确认字段名和类型。这里有个容易被忽略的地方——任务书里可能要求“点击列表项跳转到详情页”但原型图上详情页的字段可能比 JSON 数据多这时候要优先以任务书为准不要脑补字段。赛题给的 JSON 数据字段名往往和实体类一一对应拼错一个下划线就会在评分时暴露。2.2 评分点拆解功能完整、交互还原、代码规范从历年的出题方向看评分表大约分成四块功能完整度、界面还原度、异常处理、代码规范。功能完整度占比最大包括登录、列表、详情、收藏、个人中心这类常规模块界面还原度看的是字体大小、间距、圆角、背景色是否和原型图接近异常处理考的是接口失败时有没有提示、没网时页面会不会白屏代码规范体现在类名、包名、资源文件命名和关键注释。这里有一个很现实的优先级功能跑通 界面接近 异常处理 代码注释。很多学生喜欢先花 40 分钟抠一个列表项的阴影效果最后发现登录请求还没接上这是典型的备赛误区。正确做法是先把所有页面的核心跳转跑通再回头做视觉精调。评分环境通常是模拟器或真机直接操作只要界面能用、不会闪退界面还原度不会扣太多分反过来如果某个按钮点击没有响应整道功能题可能一分没有。2.3 三到四小时的时间分配方案2023 年的赛题时长一般是 3 到 4 个小时具体以任务书为准。我带队训练时常用一个“3090403020”的时间模型前 30 分钟读题、列页面清单、建工程中间 90 分钟写页面和功能再花 40 分钟接接口和 Mock 联调留 30 分钟做自测和打包最后 20 分钟检查提交内容。这个模型不适合所有人但它的核心逻辑是把“打包前至少留 50 分钟检查”设为硬性要求而不是把时间全花在写代码上。如果赛题只要求做两个模块时间分配可以更激进比如只留 15 分钟读题把多出来的时间放在自测上。自测时不要只测正常路径还要测“输入框为空时点登录”“断网时打开列表”这类边界场景。评分人员有时会刻意点这些按钮目的是看你有没有做异常处理。哪怕只是弹一个 Toast也比无反应强得多因为无反应会被直接判定为功能缺失。3. 按赛题模块复现Android 工程从零到闭环3.1 建空工程和资源目录规划打开 Android Studio 后建议选择 Empty Activity 模板而不是带 BottomNavigation 或 DrawerLayout 的模板因为自带模板会生成多余的界面和依赖反而干扰赛题要求。项目参数上我一般会把 Package name 设置为com.school.app这样的简短域名避免包名过长导致资源引用路径出错。Minimum SDK 选择 API 23这样能覆盖大多数中职学校机房里的测试机Target SDK 选 API 30 或 32主要目的是避免 Android 系统高版本对明文网络请求的限制。赛题给的素材通常需要手动拷贝到工程目录下面是一个常见的初始化命令# 解压赛题素材包后在项目根目录执行 # 先把 JSON 数据文件放到 assets 目录 mkdir -p app/src/main/assets/mock cp -r 赛题素材包/json/* app/src/main/assets/mock/ # 把切好的图片放到 drawable 目录注意目录名不能写错 mkdir -p app/src/main/res/drawable/ cp -r 赛题素材包/images/* app/src/main/res/drawable/这段命令会创建assets/mock目录把 JSON 文件复制进去再把切图复制到drawable。后续代码里访问 assets 文件时路径要以mock/xxx.json开头访问图片时直接在布局里用drawable/xxx引用。这里要注意一点res/drawable目录下不能出现文件名大小写混用的情况比如IMG_Logo.png编译时会直接报资源名错误。Android 系统的资源命名规则是只能用小写字母、数字和下划线养成习惯后才不会在提交前突然冒出一堆编译错误。3.2 登录页与表单校验登录页是赛题中出现频率最高的入口因为它能串联起“输入账号密码 → 请求接口 → 保存 Token → 跳转主页”这条完整链路。实现登录页时我建议先写一个LoginActivity把表单校验和网络请求分开这样评分人员如果快速点击多次登录按钮不会出现重复请求叠加的问题。下面是一个可以直接套用的登录页核心代码// LoginActivity.java 关键部分 public class LoginActivity extends AppCompatActivity { private EditText etAccount; private EditText etPassword; private Button btnLogin; private ProgressBar loading; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); etAccount findViewById(R.id.et_account); etPassword findViewById(R.id.et_password); btnLogin findViewById(R.id.btn_login); loading findViewById(R.id.loading); btnLogin.setOnClickListener(v - doLogin()); } private void doLogin() { String account etAccount.getText().toString().trim(); String password etPassword.getText().toString().trim(); // 先做本地非空校验避免无效请求 if (account.isEmpty() || password.isEmpty()) { Toast.makeText(this, 账号和密码不能为空, Toast.LENGTH_SHORT).show(); return; } loading.setVisibility(View.VISIBLE); // requestLogin 是下面的网络请求方法具体实现见第4章 Mock 联调 requestLogin(account, password); } }这段代码的核心作用是点击登录按钮后先取输入框内容并去除首尾空格再判断是否为空空值直接拦截并提示。这样做的原因是评分环境可能没有后端服务如果什么都没输入就发请求最后只会得到超时错误白白浪费检查时间。登录按钮点击后显示加载框防止用户重复点击加载框的显示和隐藏要放在请求回调里否则请求失败时加载框会一直卡在界面上。网络请求的方法名requestLogin可以自行封装但建议保持接口路径和参数名跟赛题任务书完全一致因为评分环境可能会对接口路径做强匹配。3.3 列表页与详情页用 RecyclerView 支撑核心流程中职移动应用与开发赛项很喜欢考“首页列表 点击进详情”的组合因为它能同时考察数据展示、图片加载、页面跳转和空状态处理。列表页最推荐的控件是 RecyclerView配合一个简单的 ListAdapter 就能满足赛题要求不需要引入复杂框架。下面是一个常见的 Adapter 写法// ListAdapter.java 列表适配器 public class ListAdapter extends RecyclerView.AdapterListAdapter.ViewHolder { private ListNewsBean dataList new ArrayList(); public void setData(ListNewsBean list) { dataList.clear(); dataList.addAll(list); notifyDataSetChanged(); } NonNull Override public ViewHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_news, parent, false); return new ViewHolder(view); } Override public void onBindViewHolder(NonNull ViewHolder holder, int position) { NewsBean bean dataList.get(position); holder.tvTitle.setText(bean.getTitle()); holder.tvSummary.setText(bean.getSummary()); // 用 placeholder 兜底避免图片加载失败导致整个 item 显示空白 Glide.with(holder.itemView.getContext()) .load(bean.getCoverUrl()) .placeholder(R.drawable.ic_placeholder) .error(R.drawable.ic_error) .into(holder.ivCover); } static class ViewHolder extends RecyclerView.ViewHolder { TextView tvTitle; TextView tvSummary; ImageView ivCover; ViewHolder(View itemView) { super(itemView); tvTitle itemView.findViewById(R.id.tv_title); tvSummary itemView.findViewById(R.id.tv_summary); ivCover itemView.findViewById(R.id.iv_cover); } } }Adapter 的核心是setData方法它清空旧数据并加入新数据后调用notifyDataSetChanged刷新列表。注意这里必须调用notifyDataSetChanged否则 RecyclerView 不会重新绘制。onCreateViewHolder只负责创建布局实例onBindViewHolder负责绑定数据两者职责不能混。图片加载使用 Glide 时placeholder和error两个参数建议都写上赛题给的图片地址有时是相对路径加载失败时至少能看到一个占位图而不是一个空白方块。详情页通常接收列表项传递的 id 或标题参数最简单的方式是 Intent 传值。跳转代码可以写在列表点击事件里比如// 在 Activity 中给 RecyclerView 设置 item 点击事件 adapter.setOnItemClickListener((position, bean) - { Intent intent new Intent(ListActivity.this, DetailActivity.class); intent.putExtra(id, bean.getId()); intent.putExtra(title, bean.getTitle()); startActivity(intent); });接收端在DetailActivity中用getIntent().getStringExtra(id)取出来再根据这个 id 去请求详情接口或从本地缓存读取。赛题有时会让详情页展示多条数据这时要注意 id 是 String 类型还是 int 类型从 JSON 里解析时保持一致否则用equals判断时容易出现1和1不匹配的怪问题。3.4 本地缓存与登录态保持赛题评分时评分人员可能在同一个 App 里反复登录退出甚至杀掉进程重新打开。如果每次冷启动都要求重新登录不一定算功能缺失但如果你能在重新打开后保持登录态通常会被视为加分项。最简单的方案是使用 SharedPreferences 保存 Token 和用户信息而列表数据可以用 JSON 字符串缓存到本地下次启动时先显示缓存再刷新网络。下面是一个轻量级缓存工具类// CacheManager.java 本地缓存工具 public class CacheManager { private static SharedPreferences sp; // 在 Application 或第一个 Activity 中调用 init public static void init(Context context) { sp context.getSharedPreferences(app_cache, Context.MODE_PRIVATE); } public static void saveString(String key, String value) { sp.edit().putString(key, value).apply(); } public static String getString(String key) { return sp.getString(key, ); } public static void clearLogin() { sp.edit().remove(token).apply(); } }这个工具类用apply()写入数据它是异步提交不会阻塞 UI 线程比commit()更适合在界面操作中调用。登录成功以后执行CacheManager.saveString(token, token)后续请求列表时带上这个 token退出登录时执行clearLogin()。列表数据的缓存方式同理把接口返回的整个数组转成字符串存进去读取时再用JSONArray解析回对象列表。这样做的另一个好处是就算评分环境完全断网App 也不是黑匣子至少能显示上一批数据避免白屏。需要特别注意SharedPreferences 不适合存体积很大的数据只适合放 Token、用户信息和少量列表缓存。如果赛题要求本地收藏几十条记录可以用 SQLite但为了节约时间我建议先直接拖一个 JSON 字符串到 SharedPreferences 顶住等核心流程跑通后再考虑换数据库。4. 接口联调与数据模拟没有后端时怎么跑通评分流程4.1 用本地 JSON 文件模拟后端接口正式比赛时赛题通常会提供一个后端接口地址或要求考生自己搭 Mock。但实际训练中很多中职学校没有现成后端于是就会遇到“页面写完了接口怎么都调不通”的尴尬。我一般会在工程里放一个MockInterceptor用本地 JSON 文件模拟接口响应这样既能调试 UI也能保证评分环境在没有网络的情况下也能跑通。先看assets/mock/login.json的格式{ code: 0, message: success, data: { token: mock_token_a1b2c3, userInfo: { userName: student01, displayName: 赛题演示用户 } } }这个 JSON 结构遵循了常见的“code message data”三层结构code0表示成功非 0 表示业务失败。接下来用 OkHttp 的 Interceptor 拦截指定路径直接返回这个字符串。下面是MockInterceptor的完整写法// MockInterceptor.java 本地模拟接口 public class MockInterceptor implements Interceptor { private Context context; public MockInterceptor(Context context) { this.context context; } Override public Response intercept(Chain chain) throws IOException { String path chain.request().url().encodedPath(); if (/api/login.equals(path)) { return buildJsonResponse(chain, loadMockFile(login.json)); } if (/api/news/list.equals(path)) { return buildJsonResponse(chain, loadMockFile(list.json)); } if (/api/news/detail.equals(path)) { return buildJsonResponse(chain, loadMockFile(detail.json)); } return chain.proceed(chain.request()); } private String loadMockFile(String fileName) throws IOException { InputStream is context.getAssets().open(mock/ fileName); ByteArrayOutputStream baos new ByteArrayOutputStream(); byte[] buffer new byte[1024]; int len; while ((len is.read(buffer)) ! -1) { baos.write(buffer, 0, len); } is.close(); return baos.toString(UTF-8); } private Response buildJsonResponse(Chain chain, String json) { ResponseBody body ResponseBody.create( MediaType.parse(application/json; charsetutf-8), json); return new Response.Builder() .code(200) .message(OK) .body(body) .request(chain.request()) .protocol(Protocol.HTTP_1_1) .build(); } }这段代码的核心机制是通过encodedPath()获取请求 URL 里的路径比如http://123.456.789.10/api/login对应/api/login命中后直接读取本地 JSON 并构造一个 HTTP 200 响应。这样做的好处是不管真实后端在不在线App 的前端逻辑都能完整跑一遍。在使用这个 Interceptor 时要注意两个参数第一个是context.getAssets().open(mock/ fileName)中的路径前缀如果你的 JSON 文件直接放在 assets 根目录这里就要去掉mock/前缀保持一致第二个是MediaType.parse(application/json; charsetutf-8)不要省略 charset否则中文可能显示成乱码。正式提交赛题时如果评分环境允许联网可以直接把 MockInterceptor 禁用掉通过BuildConfig.DEBUG开关控制。4.2 模拟延迟、错误响应和 Token 校验Mock 接口做出来后不能让它一直秒回否则异常处理代码根本测不出来。需要手动给模拟接口加延迟和错误响应这样才能暴露问题。加延迟很简单在intercept方法里插入Thread.sleep(600)即可// 模拟 600ms 网络延迟测试加载动画是否正常 try { Thread.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); }600 毫秒是一个比较接近真实弱网的数值太长会浪费时间太短测不出 loading 效果。除了延迟还要模拟 401 Token 过期。评分人员如果先登录拿到 Token再清掉本地数据后重新请求列表你的 App 应该弹出“登录过期请重新登录”而不是崩溃。在MockInterceptor中可以通过检查请求头的方式实现if (/api/news/list.equals(path)) { String token chain.request().header(Authorization); if (TextUtils.isEmpty(token)) { return buildJsonResponse(chain, {\code\:401,\message\:\未登录或登录过期\}); } }这里有一个业务约定请求列表时必须在 Header 里带上Authorization: token。这个约定在真实接口中也很常见早期训练就养成这个习惯后面接真实后端时不用改代码。返回code401后前端要判断这个值并跳回登录页同时清空本地缓存不能只弹一个 Toast 不处理。错误响应方面可以再模拟一个code500的返回用来测试列表页的加载失败占位图但要避免所有接口都是 500否则评分人员点进页面时看不到正常数据。4.3 联调验收清单把功能点变成可勾选的表接口联调不是“能跑就行”而是要有一个明确的验收过程。我通常会准备一张表格让队员拿着对照逐项打勾。这张表的核心作用是防止自测时只走正常路径漏掉异常路径。下面是一个适用于大多数赛题模块的验收清单检查项操作步骤预期结果登录成功输入正确账号密码进入首页Token写入本地登录失败输入错误密码提示错误不跳转登录保持杀掉App重新打开自动进入首页不用重新登录列表加载打开首页显示 Mock 数据加载框消失列表断网禁用网络后打开列表显示缓存数据或空状态占位图详情跳转点击列表项进入详情页字段完整收藏功能点击收藏图标图标变为选中态重启后仍保留这张表里的每一项都对应一个可以在评分表上被扣分的点。我在训练时会让队员自己当“评分员”互相走一遍表谁漏了一项就给对方报一个真实的 bug。这种练习比多写 20 行代码更能提升赛场稳定性因为很多学生在赛场上不是不会写而是只顾着写新功能忘了回头验证前面已经做完的页面。5. 备赛避坑指南赛场上最容易翻车的 5 个细节5.1 现象界面写完了运行却白屏有学生把登录页写好布局预览看起来一切正常但一装到模拟器上就白屏。Logcat 里报的往往是Caused by: java.lang.ClassNotFoundException或者You need to use a Theme.AppCompat theme之类的错误。这个问题最常见的原因是Activity 继承AppCompatActivity但工程主题没有继承Theme.AppCompat。在AndroidManifest.xml里给 Activity 指定一个不兼容的主题或者直接没设置主题都会导致白屏。解决方法是修改AndroidManifest.xml中的 application 标签统一使用带 AppCompat 的主题android:themestyle/Theme.AppCompat.Light.NoActionBar。如果赛题要求无标题栏、全屏展示可以用NoActionBar变体如果要求自定义标题就在布局里自己写。还有一个容易被忽略的点新建工程时如果选了Empty Activity系统默认会生成一个theme不要删掉MainActivity后再手动新建LoginActivity时漏掉主题声明。5.2 现象接口请求一直失败报错里写着 “Cleartext HTTP traffic not permitted”很多中职学生第一次在真机上调试时都会踩这个坑。赛题给出的后端接口可能是http://开头而 Android 9 及以上默认禁止明文 HTTP 访问。在真机上测试时系统会直接抛出Cleartext HTTP traffic not permitted导致请求全部失败。这不是代码逻辑的问题而是安全策略限制。解决方法是在AndroidManifest.xml的 application 标签下加一行android:usesCleartextTraffictrue。如果评分环境要求必须使用 HTTPS这一项就不要再保留否则会留下安全隐患。训练时为了快速联调可以临时打开提交前要重新确认是否需要关闭。另外真机请求本机后端时不能写localhost或127.0.0.1要写电脑在局域网里的 IP 地址比如http://192.168.3.88:8080/api/login否则手机会找不到目标机器这个也算学生最容易忽略的参数。5.3 现象打包后图片和图标全部消失页面在开发机上显示正常一打包安装到另一台设备图片全部变成空白Logcat 里也没有明显报错。这种问题多半是资源文件放错了位置。赛题素材包里经常会有png、jpg、webp多种格式如果你把jpg图片放进了res/drawable布局里引用drawable/banner本身没问题但如果文件名中间带横线或大写字母比如banner-home.png或Tab_Bg.png编译阶段会直接报资源名不合法。还有一种情况是图片放在assets下代码里却用R.drawable.xxx去引用自然找不到资源。解决方法是在布局里用drawable/xxx引用的图片必须放在res/drawable目录文件名只能包含小写字母、数字和下划线在代码里用openFd(xxx.png)读取的图片必须放在assets目录。提交之前用 Android Studio 的Build Analyze APK看一眼 APK 里是否包含这些资源这个步骤一分钟就能排除大半资源问题。5.4 现象在评分环境里编译失败原因却是本机 Gradle 版本不对不少学生开发机上一切正常代码提交后评分环境一打开就报 Gradle 同步失败。这不是赛题代码的问题而是你在工程里引用了某个只在开发机本地存在的依赖或者 Gradle 插件版本过高。中职赛项的评分电脑不一定有外网不能现场下载新版依赖所以我在训练时一直坚持一个原则不引入任何不必要的第三方库优先用原生控件和 AndroidX 自带的组件。比如图片加载用 Glide 没问题但如果不是必须也可以先用ImageView.setImageResource顶住。另一个解决方案是提前把项目完整 Clean 一遍再到另一个目录重新打开编译一次。这个过程能暴露出“依赖没缓存”“SDK 路径写死”“项目名带中文”等问题。特别注意项目路径不能出现中文和空格赛题提交时如果压缩包解压路径带中文Gradle 可能直接找不到ndk或sdk这属于最冤的翻车情况。5.5 现象时间不够最后草草提交了一个无法编译的工程每次比赛都有学生因为前面抠界面花太多时间最后 20 分钟才发现还有两个核心按钮没绑定事件只能把没有编译通过的工程直接提交。这个坑的根源不是时间不够而是没有在赛程中做“阶段性可运行”检查。我见过最好的备赛习惯是每完成一个页面就编译运行一次每完成一个功能点就用真机点一遍。这样即使最后真的做不完提交出去的也是一个能跑的部分版本至少能拿到已完成模块的分数。如果时间真的不够优先保证登录和首页两个模块能跑通。这两个模块几乎是所有移动应用赛题的共同入口也是评分人员第一个操作的点入口打不开后面再好看的功能都没机会展示。宁可把详情页做得简单一点也要让首页列表出数据这个原则一定要提前刻在脑子里。6. 最后一周的冲刺把验收清单变成肌肉记忆最后一周不建议再学新框架或新控件而是要把已经写过的代码练成“闭眼也能写”的程度。我会让学生在倒计时的每天上午做一次完整模拟赛从解压赛题包到打包提交全程计时。下午不做新需求只做三件事第一把前一天模拟赛里暴露的问题修掉第二把第 4 章那张验收清单再走两遍第三把常用工具类CacheManager、MockInterceptor、JsonUtil整理成一个模板工程熟悉到可以直接默写。还有一个很实用的习惯用 Git 做本地版本管理。不需要会复杂分支操作只要每完成一个阶段就执行一次git add .和git commit相当于给工程买了一份后悔药。万一改坏了某个页面还能回退到之前能运行的版本不会因为一个空指针异常卡半小时。冲刺阶段要刻意训练的是“快速定位问题”的能力。比如模拟器白屏先看 Logcat 红色异常接口报错先看请求的 URL 和响应状态码图片不显示先确认资源路径。这套排查顺序可以提前写在一张纸质表上放在键盘旁边赛场上紧张到大脑空白时照着表一步步走比临时回忆靠谱得多。带学生备赛这些年我最大的教训是赛场上最重要的不是你写了多少行代码而是你提交前敢不敢打开自己写的 App把任务书里的每一个按钮都点一遍。很多平时练得不错的学生最后输在“没时间检查”上。把这句话留给将要上场的你希望帮到你。本文还有配套的精品资源点击获取