考前先聊几句:这门课为什么让人又爱又恨
移动应用开发,听名字就觉得很“能打”——能自己写App、能参加技能大赛、毕业还能找份移动开发的工作。但真正到了期末复习,很多同学的表情就逐渐凝固了:知识点零零碎碎,光一个Android就有Activity、Service、BroadcastReceiver、ContentProvider四大组件要背,还有布局、适配、数据存储、网络请求、异步任务……复习资料打开一堆,合上脑子空空。
我当年复习这门课的时候,也是硬啃了不少文档、踩了不少坑。后来帮学弟学妹梳理备考重点、带过几轮省赛培训之后,才慢慢把这条“复习主线”摸清楚。这篇内容就是面向期末复习场景的移动应用开发核心梳理,既覆盖考试高频考点,也兼顾技能大赛“移动应用设计与开发”这类赛项里常考的实战技能。不管是期末突击还是备赛冲刺,你都能在里面找到可以直接拿来用的知识框架和复习思路。
先说清楚:这篇不是教科书式的全面罗列,而是按“考试怎么考、比赛怎么用、上手怎么做”三个角度精选出来的重点,自用可以,也欢迎转发给一起复习的小伙伴。
1. 整体复习框架:先打通“一条主线、四个支线”
移动应用开发的知识点看起来散,其实骨架非常清晰。我把整个复习体系归纳为:一条主线是“页面 → 逻辑 → 数据 → 网络”,四个支线分别是系统组件、UI布局与适配、数据持久化、网络与异步。这个框架不光应付期末考试,做比赛项目时也一样适用。
1.1 主线逻辑:一个App从打开到运行,到底发生了什么
移动应用(以Android为例)的基本运行路径是:用户点击图标 → 系统创建并启动一个Activity → Activity加载布局文件 → 布局中的控件被填充到屏幕上 → 用户交互触发事件 → 逻辑代码处理事件并读写数据(本地存储或远程服务器)→ 数据变化反馈到界面。
把这条链路拆开,就对应了复习的几个大块:
- 页面长什么样 → 布局与控件(XML)
- 页面怎么切换 → Activity/Intent/Fragment
- 数据存哪里 → SharedPreferences、文件、SQLite
- 数据怎么来 → 网络请求、JSON解析
- 耗时操作怎么做 → 线程、Handler、异步任务
考试也好、比赛也好,本质上都是围绕这条线在出题。你拿到一张试卷或一个赛题,第一件事不要慌,先判断它考的是这条主线的哪一段:是布局设计?是页面跳转传参?还是数据入库?判断对了,再去调对应的知识点。
1.2 支线拆解:每门课都绕不开的高频考区
四个支线是每年期末和技能大赛的“稳定出题区”:
- 系统组件:Activity的生命周期、Service的启动与绑定、BroadcastReceiver的动态与静态注册、ContentProvider的基本使用。
- UI与适配:五大布局(LinearLayout、RelativeLayout、FrameLayout、TableLayout、ConstraintLayout)的特性与嵌套,常用控件(TextView、Button、EditText、ListView、RecyclerView),dp/sp与px的区别,屏幕适配方案。
- 数据持久化:三种轻量级方案的适用场景,SQLite的增删改查,SQLiteOpenHelper的使用,升级时onUpgrade的处理。
- 网络与异步:HTTP协议基础,HttpURLConnection与OkHttp的对比,JSON格式与解析(JSONObject与Gson),Handler消息机制,AsyncTask的历史与局限,线程池入门。
有同学会问,是不是把这些背下来就能考好?我的答案是:背只是基础,动手才能在考试和比赛中真正得分。尤其到了综合题或比赛实操环节,光知道概念不知道怎么落地,很容易在细节上翻车。后面几个部分,我会把每个支线里的核心考点和实操要点掰开揉碎讲一遍。
2. 系统组件核心考点:生命周期、Intent传参、四大组件对比
系统组件这部分是移动应用开发的“地基”,也是期末考试简答题、填空题的“最喜欢”区域。技能大赛里虽然不会直接让你默写生命周期,但你在做页面跳转、数据回传、服务绑定这些操作时,每一步其实都在和组件打交道。
2.1 Activity生命周期:背图只是入门,关键在“场景题”
Activity生命周期几乎是每年必考,而且出题方式越来越活。常见的考法有两种:一种是直接画图填空;另一种是给一个场景,让你判断执行了哪些回调方法。
比如最经典的“屏幕旋转”场景:Activity从竖屏切到横屏,会走一遍 onPause → onStop → onDestroy → onCreate → onStart → onResume。为什么不是直接旋转?因为系统默认配置变化时会销毁并重建Activity。这个知识点单独背没意思,你把它理解成“系统觉得当前的Activity配不上新屏幕,所以重新生成了一个”,就好记多了。
再比如“按Home键回到桌面”:走 onPause → onStop,但不会走 onDestroy,因为Activity实例还在后台存活着。所以如果你在 onStop 里释放了某些资源,重新回到应用时,要在 onStart 或 onRestart 里重新初始化。
我复习时发现一个很有用的记忆方法:把生命周期分成“前台可见期”(onStart → onResume)、“部分可见期”(onPause)、“后台不可见期”(onStop)、“销毁期”(onDestroy)四个阶段,然后针对每个阶段去想一想“此时用户能看到什么?能操作什么?系统内存紧张时会不会杀我?”只要想明白这三个问题,绝大多数生命周期选择题都能秒答。
还有一个复习时容易忽略的考点:onSaveInstanceState。当Activity被系统回收前,系统会调用它来保存状态,你可以在里面用Bundle保存临时数据。注意它只在“非主动销毁”时才会被调用——如果你按返回键退出,是不会触发 onSaveInstanceState 的。这个细节考选择题的话,迷惑性特别大。
2.2 Intent与页面跳转:显式、隐式与传参回传的完整写法
Intent是组件之间通信的“信使”,期末笔试爱考概念,上机考试则实打实要写代码。复习时不要只背“显式Intent指定组件名,隐式Intent指定Action/Category”,要能默写出一段完整的跳转代码。
显式跳转很简单:
Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent);如果需要传参,用putExtra/putExtras:
intent.putExtra("user_id", 1001); intent.putExtra("user_name", "张三"); startActivity(intent);接收端在onCreate里取:
String name = getIntent().getStringExtra("user_name"); int id = getIntent().getIntExtra("user_id", 0);如果需要“从B页面回传结果给A页面”,就得用 startActivityForResult(新版本用 ActivityResultLauncher,但考试和很多旧项目里写 startActivityForResult 更多):
// A页面发起 Intent intent = new Intent(MainActivity.this, SelectActivity.class); startActivityForResult(intent, 1); // A页面接收 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 1 && resultCode == RESULT_OK) { String result = data.getStringExtra("result"); } } // B页面返回 Intent data = new Intent(); data.putExtra("result", "选了A"); setResult(RESULT_OK, data); finish();这段代码我是要求自己默写三遍以上的。因为考试卷子上不会给你IDE的自动补全,少写一个super调用、少判一个requestCode,整道题可能就白做了。
关于隐式Intent,复习重点在于理解Action与Category的配对规则。系统会根据你设定的Action(比如Intent.ACTION_VIEW)和Category(比如Intent.CATEGORY_DEFAULT),通过IntentFilter匹配找到对应的组件。考试常考的一个点是:隐式Intent至少需要一个Action和一个Category(默认是DEFAULT),缺少任何一个都可能导致匹配失败或抛ActivityNotFoundException。
2.3 Service与BroadcastReceiver:背概念之外要会的两件事
Service是“后台运行”的代名词,考试爱考“启动方式有哪两种、有什么区别”。这里一定要区分清楚:
- startService():启动服务,服务在后台无限期运行,即使启动它的组件销毁了,服务依然存在。需要调用stopService()或stopSelf()来停止。
- bindService():绑定服务,服务与绑定者“同生共死”,绑定者销毁时服务自动解绑销毁。绑定后还能调用服务内部的方法,实现进程内通信。
笔试可能会问“Service是否运行在主线程?”答案是默认运行在主线程(UI线程),所以Service里不能直接做网络请求或大量耗时操作,需要另开子线程。很多同学以为Service就等于子线程,这是考试常埋的坑。
BroadcastReceiver的复习重点则是两种注册方式的区别:静态注册在AndroidManifest.xml里写 标签,即使应用没启动也能收到广播;动态注册在代码里 registerReceiver() 注册,必须在onDestroy里 unregisterReceiver() 注销,否则会内存泄漏。新版Android对静态注册加了限制(部分隐式广播不再支持静态注册),这个在选择题里容易成为陷阱,复习时可以留意一下。
2.4 四大组件横向对比:这样记比较不容易混
很多同学容易把四大组件的职责搞混,我建议用一句话总结:
- Activity:负责“看得见的界面”,一切用户看到和交互的内容都源于它。
- Service:负责“后台任务”,没有界面,但可以在后台持续运行。
- BroadcastReceiver:负责“消息收听”,接收系统或应用发出的广播。
- ContentProvider:负责“数据共享”,为其他应用提供统一的数据访问接口。
记法可以这样:Activity是前台演员,Service是后台工作人员,BroadcastReceiver是电台听众,ContentProvider是数据仓库管理员。这四个角色各管一摊,考试问“哪个组件没有界面”马上想到Service和BroadcastReceiver(后者可以有界面但不必须有,实际开发中一般无界面)。
3. UI布局与列表:从XML基础到RecyclerView实战
UI这块是移动应用开发的“门面”,也是期末考试上机题和技能大赛里最拉分的地方。很多项目组队时,UI做得好的同学永远不愁找不到队友。复习UI不能只盯着“认识控件”,要把布局嵌套、尺寸单位、列表适配这些硬功夫练到位。
3.1 五大布局的选用原则:什么时候用哪个,心里要有数
线性布局(LinearLayout)是最基础、最容易理解的布局,适合从上到下或从左到右排布控件,考试上机写界面时优先用它。但要注意它的性能问题:嵌套过深会导致measure和layout阶段计算量成倍增长,布局层级越多,渲染越慢。比赛项目讲究界面流畅度,尽量把层级控制在3层以内。
相对布局(RelativeLayout)适合“控件之间相对定位”的场景,比如“图片在文字左边”“按钮在父容器右下角”。它比线性布局灵活,但约束关系写多了以后,可读性会变差。
帧布局(FrameLayout)最简单,所有子控件默认堆叠在左上角。它通常用来做“层叠效果”,比如一个界面上浮着一个悬浮按钮。帧布局也是Fragment容器最常用的宿主布局。
表格布局(TableLayout)适合规整的表格型界面,但实际App开发中用得已经很少了,考试一般只是要求认识。
约束布局(ConstraintLayout)是Android官方现在主推的布局,它既能像相对布局那样设置约束,又能做到扁平化(不嵌套)。比赛项目里如果对性能有要求,优先选它。复习时至少要掌握常见的约束属性:layout_constraintTop_toTopOf、layout_constraintStart_toEndOf等,并理解“把控件A的左边约束到控件B的右边”这种写法。
3.2 dp、sp与px:选择题爱考的单位换算
dp(density-independent pixel)是“密度无关像素”,用来保证在不同密度的屏幕上显示出来的物理尺寸大致一致。sp(scale-independent pixel)在dp的基础上还会跟随系统字体缩放,所以文字大小用sp,控件尺寸用dp。px是实际像素点,一般不要在布局里直接指定px,否则小屏大屏显示效果会差异很大。
考试可能给一个换算题:假设设备密度为2x(即density=2.0),那么1dp等于多少px?答案:2px。换算公式是 px = dp × density。
还有一个常见考点是“wrap_content和match_parent的区别”。wrap_content是“包裹内容”,控件有多大就显示多大;match_parent是“填满父容器”,占满父布局剩余的空间。这个虽然基础,但考选择题时经常结合权重(layout_weight)一起考,比如线性布局里两个按钮各设权重1,它们就平分剩余空间。要记住:权重是在剩余空间分配,不是按固定比例划分整个布局,所以如果有一个控件没设权重,它先占据自身需要的大小,剩下的空间才按权重分配。
3.3 列表控件:ListView与RecyclerView的复用机制与适配器
列表是移动应用里出现频率最高的界面形态。考试和比赛都绕不开ListView或RecyclerView。
ListView是老牌列表控件,复习时重点理解“适配器模式”。ArrayAdapter适合纯文本列表,SimpleAdapter适合简单图文列表,BaseAdapter则适合完全自定义的列表项。写出高效的BaseAdapter,核心就是convertView的复用和ViewHolder的缓存:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { convertView = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_student, parent, false); holder = new ViewHolder(); holder.tvName = convertView.findViewById(R.id.tv_name); holder.tvScore = convertView.findViewById(R.id.tv_score); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } Student s = dataList.get(position); holder.tvName.setText(s.getName()); holder.tvScore.setText(s.getScore() + "分"); return convertView; } static class ViewHolder { TextView tvName; TextView tvScore; }这段代码面试也爱考,它的核心是:convertView为空时才去加载布局并创建ViewHolder,之后直接复用,避免重复inflate和findViewById带来的性能损耗。
RecyclerView是ListView的“加强版”,熟练掌握它几乎是比赛项目的标配。复习时至少要把三件套弄明白:LayoutManager(线性、网格、瀑布流)、Adapter(需要继承RecyclerView.Adapter)和ItemDecoration(分隔线)。RecyclerView的条目点击事件没有内置接口,需要自己在Adapter里写接口回调:
public interface OnItemClickListener { void onItemClick(int position); } private OnItemClickListener mListener; public void setOnItemClickListener(OnItemClickListener listener) { this.mListener = listener; } // 在onBindViewHolder中设置 holder.itemView.setOnClickListener(v -> { if (mListener != null) { mListener.onItemClick(position); } });笔试里可能会问“为什么RecyclerView点击事件不直接像ListView那样setOnItemClickListener?”回答要点是:RecyclerView的布局方式多样,条目位置计算比较复杂,官方把点击事件交给开发者自己处理,这样更灵活。
3.4 上机实操时的UI细节:布局预览与真机适配
上机写界面时,我踩过不少坑,总结几个经验:
- 先画草图再写XML,哪怕只是在草稿纸上画个方框,都能节约大量调试时间。
- 单位用dp/sp,不要用px;字体颜色、背景色尽量抽到colors.xml里,方便统一修改。
- 布局写完先开Design预览,再看真机效果。比赛用机屏幕尺寸不定,建议用“小屏手机+大屏平板”两种设备各预览一次。
- 如果文字和图片混排,注意ImageView的scaleType属性。设置为fitCenter可以按比例缩放并居中显示,设置为centerCrop会裁剪填满控件(适合做头像这类固定尺寸的图)。
这些细节看着小,但上机考试时界面跑飞了、字被截断、按钮位置不对,一连串问题下来,心态很容易崩。提前把这些坑踩平,考试时才能稳得住。
4. 数据存储与SQLite:三种方案怎么选、增删改查怎么写
数据持久化是移动应用开发里另一大核心模块。期末笔试常考“三种持久化方式的区别”,上机和比赛则会让你实际操作存数据、读数据。复习的关键是分清“不同场景用不同方案”,别一上来就上数据库。
4.1 SharedPreferences、文件存储与SQLite的适用场景
SharedPreferences适合存“键值对”形式的简单配置数据,比如用户登录状态的标记、设置的开关项、上次保存的草稿时间等。它的本质是生成一个XML文件,用Map键值对的方式读写,使用非常简单:
// 写入 SharedPreferences sp = getSharedPreferences("user_config", MODE_PRIVATE); SharedPreferences.Editor editor = sp.edit(); editor.putString("username", "zhangsan"); editor.putBoolean("is_login", true); editor.apply(); // 读取 String name = sp.getString("username", "默认值"); boolean isLogin = sp.getBoolean("is_login", false);注意两点:一是写入后要调用apply()或commit(),apply()异步写盘、效率高,commit()同步返回boolean、有结果反馈,但会阻塞主线程;二是从Android 7.0开始,MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE已经被废弃,不要再用了。
文件存储适合存“流式数据”,比如图片、日志、导出文件。内部存储路径通过 getFilesDir() 获取,外部存储需要考虑权限申请。比赛项目里如果要导出Excel、保存日志文本,通常是文件存储的活。
SQLite是移动端最常用的嵌入式关系型数据库,适合存结构化数据,比如用户表、订单表、消息列表。它不需要独立的数据库服务,数据就是一个.db文件,但增删改查的SQL语句和后台数据库几乎一致,所以学好SQLite对未来学MySQL、SQL Server也有帮助。
三个方案的选择逻辑很简单:数据很零碎且只是配置 → SharedPreferences;数据是文件流 → 文件存储;数据条数多、有结构、需要查询统计 → SQLite。考试问“什么场景用哪种存储”,按这个思路答一般不会跑偏。
4.2 SQLiteOpenHelper与数据库升级:考试与项目的双热点
SQLiteOpenHelper是管理数据库创建和版本升级的核心类。复习时要能默写一个最小实现:
public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME = "student.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 student(" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "name TEXT NOT NULL," + "score INTEGER DEFAULT 0)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 版本升级时处理,比如加字段、加表 db.execSQL("ALTER TABLE student ADD COLUMN grade TEXT"); } }onCreate在数据库第一次创建时调用,onUpgrade在数据库版本号变大时调用。考试简答题经常问“数据库升级时怎么处理”,回答要点是:在onUpgrade里写ALTER TABLE语句或重新建表,并做好旧数据迁移,不能直接drop表(除非数据无所谓)。
实际操作中,我建议在helper构造方法里传入一个静态常量DB_VERSION,每次表结构变化时把版本号加1,然后同步修改onUpgrade里的逻辑。这样既清晰又好维护,比赛项目里多人协作也不容易踩坑。
4.3 数据库增删改查的标准写法与游标处理
SQLiteDatabase提供两类操作API:一是execSQL()执行无返回值的SQL语句,适合insert/update/delete;二是rawQuery()执行查询语句,返回Cursor游标。
但实际开发中更推荐使用系统封装好的insert/query/update/delete方法,避免SQL注入,代码也更简洁:
// 插入 ContentValues values = new ContentValues(); values.put("name", "李四"); values.put("score", 95); long id = db.insert("student", null, values); // 查询 Cursor cursor = db.query("student", null, "score > ?", new String[]{"90"}, null, null, "score DESC"); while (cursor.moveToNext()) { String name = cursor.getString(cursor.getColumnIndexOrThrow("name")); int score = cursor.getInt(cursor.getColumnIndexOrThrow("score")); } cursor.close(); // 更新 ContentValues updateValues = new ContentValues(); updateValues.put("score", 100); db.update("student", updateValues, "name = ?", new String[]{"李四"}); // 删除 db.delete("student", "id = ?", new String[]{"1"});用Cursor读取数据时,有三点需要注意:一是用完一定要cursor.close(),否则会内存泄漏;二是查询时尽量只取需要的字段,不要一味select *;三是用getColumnIndexOrThrow()代替getColumnIndex(),能在列名写错时快速报错,方便排查问题。
4.4 上机实操避坑:ID自增、中文乱码与主线程ANR
写数据库代码时,有几个实战中很容易踩的坑:
- 建表语句里主键自增要写成 INTEGER PRIMARY KEY AUTOINCREMENT,注意“INTEGER”不能写成“INT”,否则自增可能不生效。
- 中文乱码大多出现在读写编码不一致。Android默认UTF-8,一般不会乱码,但如果你在Windows下手动改了数据库文件,很容易出现乱码。解决办法是统一使用UTF-8,别用GBK。
- 不要把数据库操作放在主线程里做。数据库写入慢、数据量大时,主线程会被阻塞,超过5秒就会抛出ANR(Application Not Responding)提示。比赛项目里,数据库操作一定要放到子线程或使用线程池。
我在备赛和带比赛队伍时,见过不少选手在数据库读写上“翻车”。原因往往不是不会写SQL,而是没考虑好线程和生命周期。数据读写放到子线程后,记得通过runOnUiThread或Handler把结果回调到UI线程更新界面,这样既流畅又安全。
5. 网络请求与JSON解析:从HTTP基础到OkHttp+Gson组合
现在的移动应用几乎都要联网,网络请求和JSON解析是期末上机考试、技能大赛的重头戏。这块内容不算难,但知识点非常碎:权限、主线程限制、超时处理、解析异常、线程切换……任何一环出问题,程序就“崩给你看”。
5.1 HTTP协议基础与Android联网前的“三件套”准备
在写代码之前,先把HTTP复习到位:HTTP是应用层协议,基于请求-响应模型。请求行里包含请求方法(GET/POST/PUT/DELETE)、请求URL、协议版本。请求头用键值对携带额外信息(比如Content-Type、Authorization)。请求体是POST等方式携带的数据正文。响应包含状态行(状态码,如200成功、404资源不存在、500服务器内部错误)、响应头和响应体。
很多笔试题目从后端接口给你一段JSON,让你判断“用GET还是POST合适”——记住经验法则:查询操作且不涉及敏感数据用GET,创建/修改/删除或携带敏感信息用POST。
Android联网前有三件事必须做,漏一个程序就跑不起来:
- 在AndroidManifest.xml里添加网络权限:
- 如果访问HTTP明文流量,Android 9.0及以上需要在manifest里设置 android:usesCleartextTraffic="true",或配置networkSecurityConfig,否则会报“Cleartext HTTP traffic not permitted”。
- API 30及以上需要在manifest里声明android:usesCleartextTraffic="true"之外,还可能需处理网络权限的一些限制。最稳妥的方式是直接用HTTPS接口。
上机考试时,第一件丢分的事就是忘了加网络权限,代码写对了,但一运行就崩。备考时建议把它写进自己的“代码启动模板”里,每次新建项目先补齐。
5.2 HttpURLConnection与OkHttp:两种写法的对比与选择
HttpURLConnection是JDK自带的网络请求类,轻量、无需三方依赖,适合考试和简单请求。一个最小GET请求如下:
URL url = new URL("https://api.example.com/student/list"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); int code = conn.getResponseCode(); if (code == 200) { InputStream is = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line); } String response = sb.toString(); // 解析response reader.close(); is.close(); } conn.disconnect();POST请求则要设置setDoOutput(true),并写入请求体,比如JSON格式的body:
conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8"); String body = "{\"name\":\"张三\",\"score\":90}"; conn.getOutputStream().write(body.getBytes("UTF-8"));OkHttp是目前Android项目主流的三方网络库,由Square公司开源。它支持连接池复用、GZIP压缩、请求取消、拦截器等高级特性,代码也更简洁。最小用法:
OkHttpClient client = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); RequestBody requestBody = RequestBody.create( MediaType.parse("application/json;charset=UTF-8"), jsonString); Request request = new Request.Builder() .url("https://api.example.com/student/add") .post(requestBody) .build(); client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // 网络失败,e.getMessage()查看原因 } @Override public void onResponse(Call call, Response response) throws IOException { if (response.isSuccessful()) { String result = response.body().string(); // UI更新记得切回主线程 } } });考试如果允许引用三方库,优先用OkHttp;如果纯粹笔试手写代码,HttpURLConnection的写法必须会。两种都要练,因为笔试可能规定“不准使用第三方库”,上机则可能要求“必须使用OkHttp+Gson”。
5.3 Gson解析JSON:对象、数组与嵌套结构的处理
服务端返回的JSON数据通常有两种结构:简单对象和数组。用Gson解析时,核心是先定义好对应的JavaBean实体类。
简单JSON对象:
String json = "{\"name\":\"张三\",\"score\":90}"; Student s = new Gson().fromJson(json, Student.class);JSON数组:
String jsonArray = "[{\"name\":\"张三\",\"score\":90},{\"name\":\"李四\",\"score\":95}]"; List<Student> list = new Gson().fromJson(jsonArray, new TypeToken<List<Student>>(){}.getType());注意这里 TypeToken 必须加上,因为泛型在运行时会被擦除,不传TypeToken的话Gson无法获知List的元素类型。
比赛项目的接口返回往往是带状态码和消息的统一格式:
{ "code": 200, "message": "success", "data": { "total": 2, "list": [ {"name": "张三", "score": 90}, {"name": "李四", "score": 95} ] } }对应实体类可以写一个通用Result类:
public class Result<T> { private int code; private String message; private T data; // getter/setter省略 }解析时:
Result<PageData> result = new Gson().fromJson(json, new TypeToken<Result<PageData>>(){}.getType());这样写的好处是,一套Result类可以复用很多接口,比赛现场写代码时效率特别高。我带队时一直强调:先定义好“返回结构”,再写具体的解析逻辑,别一个个接口硬编码解析,否则改一个字段就要改好几处。
5.4 主线程不能联网?理解Android的线程限制与Handler
这是Android开发里最“经典”的报错之一:NetworkOnMainThreadException。系统强制要求网络请求不能放在主线程执行,因为网络操作不可控,如果大量请求阻塞主线程,界面就会卡顿甚至ANR。
解决方案主要有几种:Thread+Handler、AsyncTask(已过时不推荐新项目用)、线程池、协程。在期末复习阶段,重点是理解Handler消息机制——它是Android异步消息处理的核心,也是笔试高频考点。
Handler的工作机制可以理解为:主线程里有一个Looper在不停轮询消息队列,子线程通过Handler发送Message到队列,Looper取出消息后交给Handler的handleMessage方法处理。这样就实现了“子线程干活,主线程更新UI”的效果。
// 主线程创建Handler Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { if (msg.what == 1) { String result = (String) msg.obj; tvResult.setText(result); } } }; // 子线程中发送消息 new Thread(() -> { String result = doNetworkRequest(); // 模拟网络请求 Message msg = new Message(); msg.what = 1; msg.obj = result; handler.sendMessage(msg); }).start();也可以直接使用 runOnUiThread :
runOnUiThread(() -> tvResult.setText(result));笔试里如果问“为什么不能在子线程直接更新UI”,回答思路是:Android的UI访问不是线程安全的,主线程通过Looper机制维护UI队列,子线程直接改UI会导致状态不一致,所以系统只允许UI线程对界面进行修改。
5.5 实战中JSON解析与网络请求的常见“翻车”点
网络这块踩坑率非常高,我把高频问题列出来,复习时直接对照排查:
- 超时:连不上服务器或请求太久,需要设置connectTimeout和readTimeout。真实场景里服务器慢是常态,超时时间设5-10秒比较合理。
- JSON解析报错:最常见的是字段类型不匹配。比如服务端返回的是字符串"90",你实体类里写成int,Gson就会抛JsonSyntaxException。解决方法是确认接口文档,或者把字段类型改用String接收,再手动转int。
- 返回空数据:注意判空。解析之前先判断response是否为空,解析出List后也要判断list.size()是否大于0,避免空指针。
- 访问http明文被拦:高版本Android默认禁止明文流量,比赛现场的测试接口经常是http的,记得在manifest里开usesCleartextTraffic。
- 线程问题:onResponse回调是后台线程,直接在里面操作TextView会崩溃,必须切到主线程。
复习时我建议把网络请求做成一个“标准流程”来练:封装OkHttpClient → 构建Request → enqueue异步回调 → 判空 → 用Gson解析 → 切主线程更新UI → 失败时Toast提示。练到肌肉记忆,考试时就不容易慌了。
6. 期末复习与备赛冲刺:我的实操经验与最后叮嘱
到了最后一部分,我想把真正“落地”的复习方法和比赛经验分享给你。这部分不是知识点,但我觉得比知识点更重要——因为方法对了,复习效率能翻倍。
6.1 三轮复习法:第一轮搭框架、第二轮刷题、第三轮做项目
很多同学期末复习是一遍过,结果前面背了后面忘。我带过的备考小组里,用的最多且效果最好的是“三轮复习法”:
第一轮(约占总时间40%):搭框架。沿着“页面→逻辑→数据→网络”这条主线,把每个模块的核心概念和代码模板过一遍。这轮不追求背熟,追求的是“知道考什么、知识点在哪一章”。
第二轮(约占总时间30%):刷题。把历年期末考试题、比赛样题拿出来做。做题时不要看笔记,先自己回忆、自己写,写不出来再翻书。这轮的核心是查漏补缺,把“我以为我会”的地方揪出来。
第三轮(约占总时间30%):做项目。期末考试如果有上机题,或者你要参加技能大赛,这轮几乎决定成败。找一个小而完整的项目,比如“学生成绩管理系统”App,把布局、列表、SQLite增删改查、网络请求、JSON解析全部串起来做一遍。项目不在大,在完整。做完一个,你对整个知识体系的掌握程度会上一个台阶。
6.2 比赛项目备赛的三条经验:分工、版本控制、模拟现场
省赛类的移动应用开发赛项,平时训练时我注意到三个关键点,分享给你:
第一,团队分工要按“界面+逻辑+数据”来切,不要按“你的页面、我的页面”来切。因为比赛的评分维度中,整体功能完整度往往比单页面精美更重要。界面负责人统一管理布局样式和颜色规范,逻辑负责人统一处理页面跳转与数据传递,数据负责人统一封装网络层和数据库层,这样整合时不会出现各写各的、接口对不上。
第二,一定要用版本控制工具(Git)。哪怕只有一个人写代码,也要养成每次改完一个小功能就commit的习惯。比赛现场时间紧,如果改了界面后功能坏了,能用Git快速回滚,节省大量排查时间。
第三,每周至少做一次“模拟现场”训练。严格按照比赛的时间限制和题目要求,关掉手机、不看笔记,连续几个小时独立完成一个功能模块。很多选手平时写代码没问题,一到比赛就紧张得连页面跳转都写不顺,就是因为缺少这种限时训练。
6.3 最后的经验之谈:代码量才是硬道理
期末复习也好、比赛备赛也罢,我的个人体会是:移动应用开发这门课,看十遍书不如写一遍代码。很多同学喜欢把时间花在“看视频”“背重点”上,但一上机就写不出来,原因很简单——写代码是“手部记忆”驱动的技能,只有手指敲过一遍,才知道哪里容易漏分号、哪里容易写错类名、哪里容易忘了注册权限。
所以我最后给你一个可量化的建议:考前至少独立完成三个小项目,比如一个“待办事项App”、一个“新闻列表App”、一个“登录注册+个人中心App”。每个项目强制覆盖至少三个核心模块:Activity跳转与传参、列表展示、数据存储或网络请求。这三个项目做完要是还觉得没底,就再往里加功能——加一个图片加载、加一个下拉刷新、加一个按条件查询。
复习资料再多,最终都要落到这一行行代码上。希望这份整理能帮你把知识体系串起来,也祝你在期末考试和技能大赛里,拿到自己满意的成绩。如果你在复习过程中遇到具体报错或某个知识点卡住了,回头看看正文里对应的章节,或者自己动手跑一遍代码——大多数问题,敲一敲就通了。