ARTICLE DETAIL

资讯详情

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

Android开发工程师面试全指南:技术栈、底层原理与实战经验

Android开发工程师面试全指南:技术栈、底层原理与实战经验 1. 这个职位到底在做什么先看清岗位本质不少刚入行或者准备转岗的同学一听说“Android开发工程师”脑子里浮现的还是“写手机App的”。这话对但只对了一小半。我在这个行业待了十多年从早期的功能机Java ME时代一路做到现在Android开发这个岗位的边界早已经被撑得非常宽。今天的Android开发工程师要面对的不只是手机上的一个应用界面而是整个智能设备生态——手机、平板、车机、电视、手表、IoT设备甚至工业设备里的嵌入式Android系统。如果把这个岗位拆开看它至少包含三个层面的角色。第一层是应用层开发就是大家最熟悉的用Kotlin或Java写业务逻辑调SDK画界面处理网络请求这部分是大多数初级和中级工程师的主战场。第二层是框架层开发涉及Android Framework、系统服务、AMS/WMS/PMS这些核心机制需要你理解系统启动流程、Binder通信、四大组件的工作原理这部分通常是高级工程师和架构师的领域。第三层是系统级开发也就是大家常说的BSP、内核驱动、系统定制这一层更接近底层硬件要跟Linux内核、设备树、硬件抽象层打交道。这几年行业里还冒出一个趋势就是AI应用开发工程师和Agent智能体开发工程师这两个方向。其实这不算一个全新的岗位而是Android开发在AI时代的自然延伸。端侧跑大模型、调用NPU加速、让App具备多模态交互能力这些都是Android开发者可以承接的新机会。后面我会专门展开讲。所以这篇文章不是一份简单的“面试题背答案手册”而是一份从岗位认知到技术栈拆解、再到面试实战的全链路指南。不管你是准备校招的应届生还是想跳槽升职的社招开发或者是从其他端转过来的同学都能从里面找到自己需要的部分。我会把自己这些年踩过的坑、面过的人、以及被面过的经历都揉进去讲希望能帮你少走点弯路。2. 技术与能力模型大厂到底在面什么2.1 基础语言和框架是门槛不是亮点先说一个残酷的现实Kotlin和Java语言本身只是面试的入场券。现在的面试官不会因为你“会用Kotlin写协程”就给你加分因为这在简历上几乎是人手一条。但反过来说如果你连这些都不扎实基本第一轮就挂了。常见的基础考点包括集合框架的源码实现比如HashMap的扩容机制、ConcurrentHashMap的锁分段设计并发编程包括线程池的参数含义与拒绝策略、Handler的消息机制、AsyncTask的缺陷JVM与ART虚拟机的内存模型、GC机制、类加载过程Kotlin协程的本质包括挂起函数的原理、协程调度器的切换机制这些知识点背会了不算本事关键是要能用白话讲清楚底层原理。我面过很多候选人背得滚瓜烂熟但一问“你项目里为什么选择这个方案”就答不上来。这就是典型的知其然不知其所以然。面试官想看到的不是你的记忆力而是你建立知识体系的能力——你能把Java内存模型、Android的进程模型、OOM的成因串成一条线才算真正理解了这门语言在Android环境中的特殊之处。2.2 系统源码与自定义View拉开差距的分水岭到了高级岗位的面试有一个问题几乎是必问的“你了解Activity的启动流程吗”这个问题看起来简单但可以从一分钟的回答延伸到十分钟的深入。一个只停留在API层面的开发会答“调startActivity系统帮我们创建Activity走生命周期”而一个真正读过源码的开发者会从Instrumentation开始讲沿着AMS、ActivityTaskManager、ApplicationThread、ActivityThread这条链路把一次启动过程中涉及的关键类、跨进程Binder调用、消息循环的调度全部串起来。同样的分水岭还体现在自定义View上。现在很多业务开发已经不怎么写自定义View了一方面是因为Compose的普及另一方面是因为现有控件已经覆盖了大多数需求。但你只要稍微深入一点比如要实现一个带惯性滑动的复杂图形组件或者一个高性能的图表控件就必须理解Measure、Layout、Draw这三个过程理解View的嵌套测量逻辑理解Choreographer如何驱动帧渲染。这些都是隐藏在实际业务背后的核心能力也是初级到高级的分界线。我在面社招候选人时特别爱问的一个问题是“你在项目里做得最有技术难度的一件事是什么”注意我要的不是项目名称不是业务流程而是具体到某个技术问题——你怎么发现它的你怎么定位它的你最终怎么解决的中间尝试过哪些方案为什么选择了最后的方案。这个问题能一次性考察候选人的技术深度、解决问题的思路、以及表达能力。很多人挂就挂在根本讲不出一个像样的、有技术含量的案例。2.3 工程化与性能优化大项目的生存法则小项目写业务大项目做工程。当你参与的是一个数十人协作、代码量几十万行的项目时很多事情就不再是“把功能做出来”那么简单了。组件化和模块化是必考点。它解决的核心问题是多团队并行开发时如何避免代码相互冲突如何独立编译、独立测试、独立发布这里面涉及路由框架的设计、模块间的通信方式接口下沉还是ARouter、公共基础库的边界划分、版本管理策略等等。面试官通常会问“你们的组件化是如何实现的如果两个模块需要互相调用你怎么办”性能优化更是重头戏。面试中经常被问到的有这几类启动优化冷启动耗时怎么统计启动过程中有哪几条主线程耗时大项三方SDK如何异步化或延迟初始化内存优化如何发现内存泄漏LeakCanary的原理是什么Bitmap如何做内存复用流畅度优化掉帧如何定位Systrace和Perfetto怎么用Compose重组优化怎么做包体积优化So库如何裁剪资源混淆怎么做图片压缩有哪些更优解这些问题每一个都可以挖很深。举个例子面试官问“卡顿怎么定位”初级答“用Profiler看”中级答“在关键路径打点用Systrace抓trace”高级会把Systrace、CPU Profiler、Perfetto、线上监控平台如Matrix结合公司的实际监控体系完整讲一遍甚至能提到如何通过IdleHandler在主线程空闲时预加载资源来减少高峰期的卡顿。就这几句话的差距直接决定你是P5还是P7。3. 面试中的技能展示与实操经验3.1 简历准备用项目亮点替代罗列简历是面试的第一关。我跟很多候选人聊过发现绝大多数人的简历都存在一个通病把项目经历写成了需求文档的复制粘贴。什么“负责XX模块的开发”、“参与XX项目的迭代”这种描述一点信息增量都没有。一份能打动面试官的简历必须做到“结果量化”和“技术亮点前置”。比如同样是写启动优化平庸的写法是“优化了App启动速度”优秀的写法是“通过异步初始化、类加载优化、IO预读等手段将冷启动时间从3.2秒降至1.8秒提升约44%”。记住数字永远比形容词有说服力。面试官在每份简历上停留的时间可能只有几十秒你的任务是在这几十秒里让面试官产生“这个人值得聊聊”的想法。另外不要只在简历上写你做了什么还要写你遇到了什么问题、你怎么解决的。我建议你为简历上每一个亮点准备一个完整的故事按照“背景—挑战—行动—结果”四段式来组织语言。面试现场最怕的就是候选人简历写得很漂亮实际一问三不知。所以宁可少写也要每条都能经得住追问。3.2 面试流程与表达技巧面试是一场交流而非考试很多人把面试当成考试这是心态上最大的误区。面试官不是在出题难为你而是在判断你能不能成为同事。从这个角度出发你在面试时的表达方式往往比技术答案本身更重要。技术面试的黄金法则只有一条先说结论再讲理由后举例子。我面过太多候选人问一个问题能从大学课程讲到上一个项目绕了五分钟还没到核心。面试官心里早就走神了。正确的方式是面试官问“讲一下App启动流程”你先用三句话给出主干——点击图标后Launcher通过Binder调AMSAMS检查进程是否存在不存在则通过Zygote fork出新进程并创建ActivityThreadActivityThread通过Binder回调AMS完成Activity生命周期的调度。然后根据面试官的反应再展开到细节。这样既展示了你的知识框架又照顾了交流的节奏。还有一点特别重要遇到不会的问题不要慌也不要瞎编。可以说“这块我不是特别深入但我之前了解过XX相关内容我的理解是……”然后给出你的推理过程。面试官更看重的是你面对未知问题的思考方式而不是你是否背到了标准答案。诚实承认自己的不足同时展示出自己的学习能力和推理能力远比故作镇定地胡编乱造要好。3.3 手撕代码与系统设计核心场景的答题框架现在Android技术面试的手撕代码环节不再是单纯的算法题了很多公司会加入与业务场景强相关的问题。常见的有设计一个LRUCache写一个线程安全的单例实现一个生产者消费者模型给定一个场景让你做架构设计。这类题目的核心还是考察基础功底。LRUCache考察你对LinkedHashMap的理解线程安全单例考察你对类加载机制和并发控制的理解生产者消费者模型考察你对wait/notify或BlockingQueue的使用熟练度。做题的时候我建议你先写伪代码整理思路再上手写完整代码边写边解释你的设计考量。系统设计题更考验综合能力。比如面试官问“让你设计一个IM消息系统你会怎么设计Android端架构”你不需要真的做出完整架构但要展示出清晰的思考路径数据层用什么存储Room还是Realm连接层用什么协议WebSocket还是MQTT消息时序怎么保证离线消息怎么处理多端同步怎么做消息与UI的绑定怎么做慎重思考有层次地回答。我之前面过一个小伙子问到这个题目时他把网络库选型、消息可靠性保障、UI层的数据驱动设计全串起来了逻辑非常清晰当场就发了offer。4. 高频技术点深度盘点2025年Android面试的核心题库4.1 应用层从UI到通信这些技术必须拿捏UI相关的高频考点首当其冲是Compose与View体系的对比。现在新项目基本都在用Compose写UI面试官也爱问它和传统View体系的区别。你需要理解Compose的声明式UI范式、重组机制、状态管理与MVVM的契合度、以及它在性能上的权衡——比如重组范围如何最小化、稳定性推断如何影响跳过重组。同时也要清楚存量项目依然是以View为主所以在面试中不要把Compose吹得好像能解决一切问题而是结合项目情况客观分析它的优劣。网络通信相关OkHttp和Retrofit是绕不开的。面试官会问OkHttp的请求流程、拦截器机制、连接池复用、HTTP/2与HTTP/3的支持情况。Retrofit则主要考察动态代理如何把接口转成Call对象、注解处理器如何解析配置、以及对协程的支持方式。如果你能讲清楚OkHttp的Dispatcher如何调度异步请求以及Retrofit的CallAdapter.Factory如何适配协程的suspend函数基本就能在这个环节拿到不错的分数。本地存储方面Room已经成了标准答案。除了基础的Entity、Dao、Database定义还需要理解Room与LiveData/Flow的联动、数据库迁移策略、以及如何通过预填充数据库提升首启速度。有些面试官会进一步追问SQLite的底层原理比如事务如何工作、WAL模式的优势这些也需要有基本认知。4.2 系统底层Framework、BSP与跨端调试Framework开发是资深Android工程师的分水岭。岗位热词中频繁出现的“android framework”“嵌入式工程师如何开发xlink zynq”“mtk_unisoc 平台 arm64 android 内核与 BSP 开发”都在指向一个趋势手机厂商、芯片厂商、IoT设备厂商对具备系统定制能力的Android开发者的需求越来越旺盛。这一类岗位的工作重点包括深度定制系统UISystemUI、修改系统权限管理策略、编写系统级Jar包或Sdk extension、定制开机动画和预装应用、对接底层硬件服务如传感器、蓝牙、摄像头HAL层。面试中考得最多的是Binder机制、Handler消息循环、AMS/WMS的核心职责、以及App与系统服务的通信方式。除此之外AIDL手写、Binder驱动的大致数据传输流程也是高频考点。跨端调试也是系统开发绕不开的一环。很多开发者对adb shell这套命令并不陌生比如热词里出现的“adb shell sh /storage/emulated/0/android/data/xx/up.sh”这其实是获取root权限后在设备上执行脚本的典型场景。你至少要熟练掌握adb install、adb push/pull、adb logcat、adb shell dumpsys/am/pm等常用指令能通过抓取日志排查问题能通过dumpsys查看系统服务状态。做系统级开发时还需要会抓取kernel log、会解析 tombstone 文件、回车能看到崩溃堆栈的 native 层问题。底层开发还经常涉及差分包与系统升级。热词里提到的“android no-ab制作差分包 imgdiff 崩溃”就是一个很典型的实操场景。简单解释一下系统OTA升级很多时候不是全量包而是差分包——只包含新旧版本之间差异的部分体积小、下载快。制作差分包时如果使用imgdiff对system.img做二进制差分在某些特殊情况下比如文件过大、分区布局变化会崩溃。这时候需要退回到bsdiff策略或者手工划分差分块。这类问题光靠应用层开发的经验根本解决不了必须对Android构建产物、文件系统结构、分区表格式有深入理解。4.3 专项技术蓝牙、传感器、多媒体与AI应用开发蓝牙开发是物联网和可穿戴设备领域的常客。Android侧的核心考点包括BLE连接流程扫描、连接、发现服务、读写特征值、MTU协商与分包策略、后台扫描与连接的功耗控制、以及蓝牙音频的A2DP/AVRCP协议基础。实际项目中最容易踩的坑是国产手机对后台蓝牙扫描的限制策略不同需要针对主流机型做兼容适配。这个问题在面试中也可以主动提出会显得你有充分的线上经验。传感器相关的开发也要有一定了解。这里说的不只是简单的加速度计、陀螺仪数据读取而是如何用麦克风实现声强计、如何对传感器数据进行滤波处理、如何融合多个传感器数据实现手势识别。MediaPipe在Android平台上的落地正是这一类技术的典型代表。你只需要提前搞清楚MediaPipe的连接、模型加载、GPU委托推理、手势关键点提取这一条链路就能在面试中展示出自己对前沿技术的敏锐度。AI应用开发这块现在越来越多人问。端侧大模型的推理通常依赖TFLite或ONNX Runtime还要结合NPU、GPU、DSP这些异构计算单元做加速。你需要了解量化INT8/FP16的基本原理、模型如何压缩与转换、内存如何高效管理、以及如何设计一个供上层调用的推理引擎SDK。这个话题目前红利期很明显如果有实际项目经验哪怕只是一个小Demo都能让面试官眼前一亮。5. 求职策略与避坑指南5.1 不同阶段的求职侧重点应届生求职面试官更看重的是基础是否扎实、学习能力是否够强、项目经验有没有真实的思考。这个阶段不需要追求项目数量而是要能把一个项目讲透。哪怕只是课程设计或者个人Demo只要你能说清楚技术选型的原因、遇到的困难、以及改进的方向就已经比大多数候选人强了。另外刷题LeetCode在应届生面试中的比重还比较高建议至少刷完Hot 100和剑指Offer中涉及数组、链表、树、动态规划的经典题。初中级社招面试官重心会放在实际项目经验和业务落地能力上。这个阶段你需要准备好一个“代表作”级别的项目复盘。如果简历上说做过性能优化那你得能讲出线上监控体系怎么搭的、优化前后的数据对比、以及你的优化方案对业务指标的实际影响。如果做过组件化就要讲清楚路由表的生成机制、模块间依赖怎么梳理、以及基础库的升级策略。高级岗位求职面试官考的是技术深度和架构视野。你不仅要知道怎么做还要知道为什么这么做——为什么选择这种架构方案而放弃另一种为什么这个技术栈可以支撑未来两年的业务迭代你还可能会遇到跨部门协作、技术规划、团队管理等软技能问题。这一阶段有个常被忽略的点多关注行业里头部公司公开的技术博客和技术大会演讲了解一线大厂在性能优化、大前端、端智能方向上的最新探索这些内容往往能在面试中作为谈资也能体现你对行业趋势的敏感度。5.2 简历与面试中常见的坑第一类坑是简历信息前后矛盾。项目时间重叠、技术栈描述与实际不符、上一份工作年限对不上这类问题在面试官的详细追问下很容易露馅。我的建议是不要在任何核心信息上夸大因为一旦被拆穿整个面试的可信度都会崩塌。第二类坑是面试中的“答非所问”。面试官问“你优化启动过程中遇到的最大困难是什么”你回答“我们用阿里的Sophix做热修效果不错”——这答的是另一个问题。这里教你一个小技巧先花几秒钟确定面试官真正想问的维度再作答。如果拿不准可以用“你问的是XX这个方面吗”来确认这不算丢人反而显得你严谨。第三类坑是忽视软技能与职场素养。Android开发早已不是单打独斗的工作技术之外的沟通能力、ownership意识、风险评估意识都会在面试中被综合评估。在回答项目经验时多提及你如何与产品经理、UI设计师、测试工程师协作推动项目落地会提升面试官对你的整体认可度。5.3 工具与环境问题的应对策略聊几个在开发者和面试中经常被卡住的工具链问题。首先是Android Studio的中文设置。新版Android Studio其实自带中文语言包只需要打开Settings - Plugins搜索“Chinese (Simplified) Language Pack”安装后重启即可。如果你所在网络环境插件下载很慢可以手动到JetBrains插件市场下载ZIP包选择从磁盘安装。其次是Gradle下载频繁失败的问题。Android Studio每次新建项目都会根据gradle-wrapper.properties里的distributionUrl去下载对应版本的Gradle国内网络环境下经常卡在这。解决办法是你自己先在浏览器或下载工具中把对应版本的gradle-x.x-all.zip下载下来解压到一个固定目录然后在File - Settings - Build Tools - Gradle里配置本地Gradle distribution路径或者在gradle-wrapper.properties里改为本地的file:///路径。配置一次之后所有项目都能用省心很多。还有就是“The following SDK component was not installed: android sdk build-tools”这类报错。这通常不是真的无法安装而是Android Studio自动下载SDK组件时网络超时或文件损坏。解决办法是打开SDK Manager手动勾选缺失的Build-Tools版本如果反复失败就单独去官网下载build-tools对应版本解压后放到Android SDK目录下的build-tools文件夹里。平时我也建议大家保留一份完整的离线SDK包以备不时之需。6. 常见问题与面试实战问答场景6.1 面试官最爱问的高频问题速查表我整理了一份在Android面试中经常被问到的问题清单按不同层级做了归类层级高频问题示例推荐准备深度初级Activity生命周期、四大组件、Handler机制、ListView/RecyclerView区别、OkHttp基本流程能讲清楚原理主干能写简单的Demo代码中级启动优化方案、内存泄漏识别与修复、自定义View流程、组件化架构、协程原理能结合项目案例讲出实际数据分析与踩坑过程高级Binder通信原理、AMS/WMS源码分析、性能监控体系设计、跨端架构设计、大前端趋势判断能有体系地输出观点并结合行业实践说明自己的取舍逻辑这里多提醒一句不要只准备“清单”而忽略了融会贯通。有不少候选人我把两个看起来不相关的问题放到一起问——比如“Handler消息机制与Binder通信有什么关系”或者“自定义View的滚动与Compose的重组有哪些类似之处”——就答不上来了。面试官不是想考你偏题而是想看到你的知识是否能串联成网。6.2 三类典型的实战问答脱稿演练现在把三类最有代表性的问答场景完整拆解给你。场景一综合实力评估题——“你做过最复杂的UI交互是什么”初级候选人我做过一个列表页倒计时功能很复杂我用CountDownTimer实现记得及时取消防止内存泄漏。中级候选人我做过一个视频播放页的弹幕系统。弹幕层是一个自定义ViewGroup负责测量每条弹幕的宽高和展示位置用一个线程循环计算弹幕的X轴位移通过View的setTranslationX实现平滑移动。为了性能优化我把弹幕条目的View做了缓存复用避免频繁创建和销毁另外根据屏幕宽度动态计算同屏弹幕条数防止叠加过多造成卡顿。高级候选人我在弹幕系统的基础上还设计了一套渲染引擎。弹幕的样式信息字体、颜色、边框、透明度做成了样式协议由独立的渲染器完成绘制与业务数据解耦。底层渲染使用TextureView承载弹幕数据走内存环形缓冲区通过Choreographer的帧回调驱动把帧率稳定在60FPS。我还会针对用户的设备性能做分级策略——低端机走降级模式减少同屏弹幕数量关闭不必要的特效避免用户感知明显的掉帧。同样的一个主题三个回答的深度和信息密度差距很大。你准备面试案例的时候不妨用这个标准给自己做个预判。场景二综合实力评估题——“你们的App崩溃率是怎么治理的”初级候选人我们接了Bugly崩溃后能看到日志我们会定期查看并根据堆栈修复。中级候选人我们建了一套崩溃治理的闭环流程。客户端通过Bugly或自研的Kotlin扩展崩溃采集模块上报Crash信息服务端处理堆栈归一化随后自动分配到对应的负责人。我会重点分析聚合后排名靠前的Top Crash查看对应的堆栈、机型、系统版本和复现路径修复后通过灰度发布验证然后周会上同步线上崩溃率的变化趋势。高级候选人在闭环流程的基础上我们还做了几个深度的技术动作。第一建立了native崩溃的分析体系对tombstone文件做符号化解析结合logcat和kernel log定位到具体的内存错误场景。第二针对Java Crash设计了差异化处理策略在主线程异常时尝试走兜底逻辑避免直接闪退——当然这是有损降级需要非常谨慎。第三推进了线上崩溃监控告警在关键版本上配置了崩溃率阈值超阈值的版本能自动触发回滚开关避免故障扩大化。这里的考察点有两个一是能否从碎片问题中整理出系统性的解决方案二是是否理解线上稳定性治理的原则——不发生不是目标能快速发现、快速止血、快速修复才是目标。场景三综合实力评估题——“请设计一个IM消息发送流程”这类题目考察的是系统设计能力。你需要分模块来解答消息发送入口在UI层数据先写入本地数据库状态为SENDING然后加载到发送队列发送队列由WorkManager或者自研的线程池驱动将消息通过WebSocket或HTTPS发送到服务端收到服务端ACK后更新数据库中消息状态为SENT并通过Room的Flow或LiveData通知UI更新如果超时未收到ACK则进入重试逻辑指数退避策略超过最大重试次数后标记发送失败等待用户手动重发。这里要特别注意两点一是消息的顺序性如何保证二是消息的幂等性如何实现。顺序性可以给消息增加自增序列号在服务端按序列号排序客户端按序列号展示幂等性则是通过客户端的消息IDUUID去重防止服务端的ACK重传导致消息被重复处理。把这些细节讲清楚面试官会觉得你想问题够周全。6.3 企业文化与个人气场技术之外的决胜盘最后一个要多说几句的话题往往是候选人最容易忽略的面试不只是在考技术也是在筛选“能聊得来的人”。每一个岗位最终都会嵌入团队、嵌入公司文化面试官和HR都会评估你的协作方式、沟通风格、以及解决问题的态度。你在面试中呈现的状态应该是“对这个方向有热情、对技术有追求、对业务有理解、对团队有贡献意愿”的专业人士形象。不需要刻意装得热情高涨但要有真实的热爱和好奇。我记得有一个候选人答完技术题后主动问面试官“你们在跨端方案上是怎么权衡的我们之前用Flutter遇到了一些性能和生态的问题很好奇你们是怎么做的”。这一个问题让整场面试的气氛瞬间变成了同行之间的交流。最后他的技术评级虽然不算顶尖但因为沟通意愿和思考方式太对味了依然拿到了offer。这些年在带团队和参与招聘的过程中我见过太多技术不错但沟通一塌糊涂的候选人也见过不少基础一般但特别会思考、会表达的宝藏候选人。技术可以通过努力弥补但思维方式和工作习惯却是长期养成的。7. 最后分享一点个人的实操体会写了这么多我觉得最值得说的还是这一点不要为了面试而面试而是把面试当作一次系统性的技术复盘。每投递一家公司之前先根据它的业务方向和技术栈梳理出可能涉及的知识点再对照自己的简历做一次全面的追问式自检。这个过程会逼着你把平时“会用但没想过原理”的地方补齐也会让你重新审视自己项目的技术决策是否合理。哪怕最后没有跳槽成功这一轮复盘带来的技术成长也远比刷几十道面试题管用。另外我一直建议大家养成写技术笔记的习惯。不是那种复制粘贴官方文档的笔记而是把你真实项目中遇到的问题、排查过程、解决方案记录下来。这些内容就是你面试中最宝贵的素材库。等到你面试的时候翻开笔记每一个问题都是一个有血有肉的实战案例比任何八股文都有说服力。我自己的博客里存的全是这种东西面试前翻一翻比临时抱佛脚安心得多。最后再分享一个小技巧面试回答问题的时候眼睛看着面试官边说边观察对方的反应。如果对方在快速点头说明你说到点子上了可以继续深入如果对方在微微皱眉就要考虑是不是说得太偏了主动回到主干。技术面试的本质就是一场实时双向反馈的交流。抓住这个核心你的临场表现至少能提升一个档次。
返回列表