恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Android手机多媒体开发实战:音频、视频、摄像头与图片处理全解析
首页
资讯中心
/
Android手机多媒体开发实战:音频、视频、摄像头与图片处理全解析
Android手机多媒体开发实战:音频、视频、摄像头与图片处理全解析
发布时间:2026/10/3 15:07:27
很多移动开发教程会把“运用手机多媒体”放在中间偏后的章节这个位置其实很有讲究到了这里你已经掌握了界面搭建、四大组件的基本用法开始真正跟系统硬件打交道了。手机多媒体这个词听起来很宽泛但落到程序里就四件事——声音播放、视频渲染、摄像头调用、图片处理。别小看这四件事它们背后牵扯权限机制、生命周期、渲染队列、内存优化哪一个都能让新手原地摔上一跤。这篇文章我就把这一章背后真正要学的东西拆开讲不只讲API怎么调更讲清楚为什么要这么做、踩过的坑在哪里、排查思路是什么顺便给出一套可以直接抄作业的代码写法无论你是在跟教材学还是在公司里被分到一个多媒体相关的需求都应该能从中拿点东西直接用。1. 手机多媒体到底“多”在哪先看清全链路1.1 “放个音乐”只是冰山一角很多初学者写了一个MediaPlayer放mp3就觉得自己学会了手机多媒体这个理解太浅了。一个完整的多媒体链路应该是这样采集麦克风录音、摄像头拍照录像→ 编码压缩成AAC、H.264→ 存储或传输写入文件、走网络→ 解码读回来还原→ 渲染投到扬声器、屏幕上。应用层开发者做功能时不需要自己写编码器但必须清楚自己正处在链条的哪一段否则排查问题会像无头苍蝇乱撞。比如你接了一个音频播放需求看起来只是setDataSource再start可为什么播放网络歌曲会卡为什么切到后台就断了为什么来电时音乐还在响这些问题的根因都不在播放器本身而在你根本没理解音频焦点、网络缓冲、生命周期这些“底层插件”。我见过太多新手拿着播放器的报错日志去搜“MediaPlayer error 什么什么”结果绕了一大圈发现是自己没在onPause里暂停资源。所以学这一章第一件事不是背API而是建立“多媒体不是单一组件而是一条管道”的意识。1.2 系统在背后替你做了什么以Android为例你在Java或者Kotlin里调的MediaPlayer底层并不是Java实现而是通过JNI调用了C层的多媒体框架再往下走才接触到音频驱动和视频解码器。系统帮你屏蔽了这些复杂的底层逻辑你只需要跟一组状态机打交道Idle、Initialized、Prepared、Started、Paused、PlaybackCompleted、Error、Released。很多人写播放器的时候不按状态机走直接在setDataSource之后立刻start轻则没声音重则直接抛IllegalStateException。多媒体框架的另外一个重要特点是“资源独占性”。比如摄像头同时只能被一个应用占用音频输出也有焦点机制。你看视频的时候切到后台如果播放器不主动暂停音频和画面就会脱节你拍照的时候如果上一个App没释放摄像头openCamera就会报错。这些独占资源的调度逻辑说到底就是“谁申请谁释放、不释放就崩溃”。这跟现实里会议室预订一个道理你预约了不出现又不取消别人就只能在外面干等。1.3 这章适合谁学到什么程度算入门如果你是刚看完四大组件和基本布局想给自己的App加点“好玩”的东西这章正好是你的下一站。学到了什么程度算入门我认为有三个标准第一能独立播放本地和网络音频并正确处理生命周期与音频焦点第二能调用系统摄像头拍照并把照片正确显示到界面上尽量降低内存占用第三知道什么时候该用现成组件比如VideoView什么时候该用更灵活的方案比如TextureView配合播放器而不是一律能跑就行。如果你是想做专业的音视频应用比如剪辑工具、直播、在线播放器那这一章只是起点后面还有硬解软解、码率控制、渲染同步这些更深的内容。但对大多数普通应用来说把“音频提醒、视频演示、拍照上传、图片列表”这些场景做顺就已经能解决九成用户需求了。这篇文章的核心思路就是给你一条最短路径把这些场景做到稳定可靠而不仅仅是把Demo跑起来。2. 开工之前权限和资源这两关必须先过2.1 危险权限与运行时申请代码怎么写才稳手机多媒体涉及的行为大多跟用户隐私直接相关所以系统在权限上卡得非常严。你要申请的往往不是普通权限而是危险权限。Android 6.0之后危险权限必须运行时动态申请写在Manifest里只是第一步实际使用前还要检查用户是否已经授予否则一调用摄像头直接SecurityException崩溃。以Android现在的权限体系为例播放本地音频文件需要读媒体文件的权限录音需要RECORD_AUDIO拍照需要CAMERA这些都属于危险权限。而且从Android 13开始媒体读取权限被拆得更细READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO。如果你的App需要同时读图片和音频就要分开申请。这个拆分的逻辑其实很好理解系统希望你在哪个场景就申请哪个权限而不是一把梭把相册全读了。我给你的建议是封装一个权限请求工具不要在Activity里裸写requestPermissions。因为回调是异步的逻辑一多就会乱。封装时注意三个关键点第一检查清单可以合并多权限用数组一次申请但弹窗会逐个询问第二要处理用户勾选“不再询问”这时候再弹系统框已经没用了最好引导用户到设置页自己打开第三判断权限是否被拒绝时用shouldShowRequestPermissionRationale它能告诉你第一次拒绝和永久拒绝的区别这个状态在处理产品逻辑时非常有用比如用户首次拒绝只是手抖你要给他重新申请的入口永久拒绝则要引导跳设置。权限的代码演示我放在后面具体场景里给这里先说清楚一个原则不要过度申请权限也不要消极躲避。只在真正要用某个硬件的那个操作节点去申请用户理解度会高很多。比如用户点击“拍照”按钮时弹权限框顺理成章一进App就把所有权限要完多半会被用户直接卸载。2.2 素材放res/raw、assets还是外部存储做多媒体功能第一件事往往是“音频文件放哪”“视频文件放哪”。看起来只是放文件的位置但选错位置会引发一堆连锁问题。Android里本地素材有几个常见位置res/raw、assets、内部存储、外部存储。res/raw目录下的文件会被系统赋予一个资源ID访问最简单——R.raw.sound生成一个MediaPlayer直接setDataSource(context, R.raw.sound)就能用。但这个位置的限制也很明显资源会被打进APK包不适合放体积大的文件也不方便动态替换。assets目录类似但它不会生成资源ID需要用AssetManager去读取好处是可以自定义目录结构、适合放游戏配置或预置的场景文件。真正用户产生的多媒体文件比如拍的照片、录的音、下载的视频不应该放这些静态资源目录应该存在应用的专属目录里。这里再强调一个关键常识Android有内部存储和外部存储之分外部存储不是你理解的SD卡它更接近于“用户可见的共享存储区”。应用自带的隐秘文件应该放filesDir也就是context.getFilesDir()这个目录不需要权限就能读写而如果文件想要被其他应用访问或者用户能在文件管理器里看到就要放到外部存储的公共目录这就必须申请存储权限。搞反了的话轻则文件找不到重则权限被拒后整个功能瘫痪。我实际带项目时最常见的错误是新手图省事把网上缓存的一堆用户头像丢到cacheDir还不清缓存时间长了存储暴涨然后莫名其妙被系统清掉导致图片加载失败。正确的做法是可再生的数据用缓存目录重要数据放filesDir用户主动产生的文件才考虑公共存储。这三个位置想清楚存储相关的问题能少一半。2.3 音频焦点和音量通道无声播放最容易踩的坑播放音频没声音大概是多媒体开发里最让人头疼的问题因为不报错完全静默失败。经验不足的人第一反应是去查播放器代码可真相往往在音量通道上。Android的音量分成好几个流媒体音量、铃声音量、闹钟音量、通话音量。你播放音乐时控制的是STREAM_MUSIC也就是手机侧边音量键按的时候调的那个。有些新手用AudioManager调音量结果调成了STREAM_RING界面和媒体播放完全是两条路自然听不到。还有一个更隐蔽的坑音频焦点。当你的App播放音乐用户切到另一个视频App如果双方都不遵守音频焦点规则就会出现两个声音同时响。Android提供了一个协调机制叫AudioFocus你在播放前先申请焦点获得回调后再开始播放失去焦点时暂停恢复焦点时继续。这个机制不是强制性的但你不做产品体验就会被其他做得好的App碾压。尤其现在很多系统会直接对不处理焦点的播放器做降噪处理或静默掉表象就是“我的代码没问题但别人一放音乐我的就没声了”。实践上我建议把音频焦点封装成播放器的外部策略不要跟播放逻辑缠在一起。因为不同业务对焦点的需求不一样短提示音根本不申请焦点音乐播放器申请长期焦点视频播放器申请短暂焦点且允许duck压低音量而不是暂停。把这些策略做成可配置后面接各种需求时就不用反复重写播放器。3. 三大核心场景实操音频、视频、摄像头3.1 MediaPlayer播放音频别再用过时的setDataSourceMediaPlayer是老牌播放器从Android早期就存在直到现在依然能用但用法上有几个细节变了很多人还停留在老写法上。第一网络路径播放时必须异步准备。同步prepare()在加载网络资源时会直接卡死UI线程初学者一卡就以为是死机了。正确写法是先调用prepareAsync()然后注册OnPreparedListener资源准备好了再启动播放。千万不要在没准备好的状态下调用start()会抛异常或者无声。第二播放结束要释放资源。MediaPlayer持有的是底层硬件资源占用内存和句柄用完不release反复开关页面就会内存暴涨。我见过一个音板Demo用户点一次按钮创建一个MediaPlayer切换页面不释放结果播放到第20多次直接闪退。正确做法是在onDestroy或者onStop里停止并release并把对象置空防止重复释放。第三从Android 9开始优先级是MediaPlayer还是新方案你需要知道Media3ExoPlayer的继任者。MediaPlayer在音频示例里依然够用但如果你做的是复杂播放器不建议继续在它上面叠需求Media3在音频焦点、加载策略、字幕支持上做得更好。学习MediaPlayer的意义在于理解播放器基本盘不是让你以后到处用它。我放一段最简单的音频播放示例这个流程适配大部分场景MediaPlayer mediaPlayer new MediaPlayer(); try { mediaPlayer.setDataSource(context, uri); mediaPlayer.setAudioAttributes( new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build()); mediaPlayer.prepareAsync(); mediaPlayer.setOnPreparedListener(mp - mp.start()); mediaPlayer.setOnCompletionListener(mp - { // 播完后的清理逻辑 mp.release(); }); } catch (IOException e) { e.printStackTrace(); }注意AudioAttributes里USAGE_MEDIA和USAGE_GAME的区别媒体播放和游戏音效走不同的系统策略别混。也别忘记在Android 9以上媒体音量会跟铃声独立调节测试无声时要先确认侧键调的是媒体音量。3.2 短音效用SoundPool省电又跟手如果只是播放按钮点击音、消息提示音、游戏里的吃金币音还去用MediaPlayer就有点杀鸡用牛刀了。MediaPlayer的缺点是延迟较高、每次创建销毁开销大短音效频繁触发会卡顿。SoundPool是专门为这种场景设计的它的核心优势是一次加载、多次播放、低延迟、支持多个声音同时响。SoundPool在Android 5.0以后的构建方式变了需要用SoundPool.Builder。常见参数有两个maxStreams表示最多同时播放的声音数量超过上限系统会停掉旧的AudioAttributes表示这个音效的用途。还有一个关键点是异步加载通过setOnLoadCompleteListener监听加载完成加载没完成就调用play会毫无反应这个错误特别隐蔽不报错但就是不出声。一个我常用的短音效工具类写法是这样的SoundPool soundPool new SoundPool.Builder() .setMaxStreams(4) .setAudioAttributes( new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_GAME) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build()) .build(); int soundId soundPool.load(context, R.raw.click_effect, 1); soundPool.setOnLoadCompleteListener((sp, sampleId, status) - { if (status 0) { sp.play(sampleId, 1f, 1f, 1, 0, 1f); } });使用SoundPool的第二个注意点是批量预加载。如果你的音效是在一个界面统一用建议在界面初始化时提前加载全部音效而不是用户点一下才加载。否则第一次点击会卡一帧体验非常差。第三个坑是音效文件不要用无损格式推荐使用体积小加载快的OGG或高压缩比MP3文件太大加载体感明显纯属自己给自己挖坑。3.3 VideoView放视频简单但只适合“够用就行”VideoView理解起来非常直白它就是MediaPlayer加SurfaceView的组合封装你只需要setVideoPath或者setVideoURI再调用start()就能播放视频还自带一个MediaController可以做播放暂停和进度条。如果你的需求是放一个引导视频、教学视频、首页背景视频不需要复杂定制VideoView是性价比最高的选择。但它的局限也很明显。第一渲染层用的是SurfaceViewSurfaceView是独立窗口浮在普通View之上你很难在它上面放半透明的控件做动画。第二它的内部播放器封装得比较死网络视频缓冲策略、错误重试、字幕支持这些需求都不好扩展。第三它默认处理了Surface生命周期但遇到快速切换页面、旋转屏幕这种场景还是容易黑屏。用VideoView时有一个极容易踩的坑视频宽高比自动适应。如果你直接把VideoView塞进一个固定布局里视频会被拉伸变形。正确做法是重写onMeasure根据视频的宽高比动态调整VideoView的宽高。很多新手在布局里用match_parent怼满全屏横屏视频还好竖屏视频直接拉成一张厚饼那画面相当抽象。还有一个典型错误是在页面不可见时没有暂停播放。VideoView跟着Activity走如果你在onPause里不主动pause()切到后台时声音还会继续响用户后台挂机一下流量哗哗地走。这个问题在上一节提到的生命周期意识里反复强调多媒体组件真的不能“放那不管”。如果你以后要做的视频功能不是单纯播放一段预置资源而是要做沉浸式列表播放、自动播放下一条、弹幕互动之类那时候就该上ExoPlayer或者Media3了。学VideoView不是终点它是你理解“简单方案与复杂方案边界”的很好的例子。3.4 拍照并展示照片Intent FileProvider 组合拳调用系统摄像头拍照是手机多媒体里最典型的需求逻辑上也最简单——发一个隐式Intent就行。但现在实操起来有一个绕不开的坎FileProvider。在Android 7.0之前你可以在Intent里直接传一个Uri.fromFile(file)。7.0之后系统禁止App对外暴露file://格式的Uri否则直接抛FileUriExposedException。原因是安全设计File Uri太容易泄露路径容易让恶意应用读取你的私有数据。解决方法是两步第一步在AndroidManifest里注册FileProviderprovider android:nameandroidx.core.content.FileProvider android:authorities你的包名.fileprovider android:grantUriPermissionstrue android:exportedfalse meta-data android:nameandroid.support.FILE_PROVIDER_PATHS android:resourcexml/file_paths / /provider第二步在res/xml/file_paths.xml里配置可共享的目录。比如你要把照片存在应用外部缓存目录paths external-cache-path nameexternal_cache path. / /paths然后用FileProvider.getUriForFile生成content://Uri还要记得给系统相机临时授予读写权限。File photoFile new File(getExternalCacheDir(), temp_photo.jpg); Uri photoUri FileProvider.getUriForFile(context, 你的包名.fileprovider, photoFile); Intent intent new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivityForResult(intent, REQUEST_CODE_CAPTURE);这里有个经验教训FileProvider路径配置错了系统相机一返回就闪退而且报错根本不在你的代码里而是在ActivityThread里抛出异常排查起来特别懵。我建议你一开始就把file_paths里要用的目录配全包括cache、files、external_cache、external_files别等后面报错了再加。拍完照后还要做两件事。一是根据媒体库扫描让图片出现在相册那是走MediaScannerConnection二是把照片正确显示到ImageView上。这里直接decodeFile的后果非常严重手机拍一张1200万像素的照片解出来的Bitmap可能占几十MB内存小内存机器直接OOM。解决办法是用BitmapFactory.Options先读取图片宽高再按需做inSampleSize采样具体方案我们放到下一节详细讲。顺带提醒一点startActivityForResult本身在AndroidX里已经废弃了建议用ActivityResultLauncher替换。这不是说旧写法不能用而是新架构更安全能在启动前就校验权限、处理回调时不会因Fragment重建丢结果。新的注册方式用起来也很顺手看一遍官方示例基本就能迁移。4. 进阶坑位生命周期、Surface与Bitmap逐个拆4.1 播放器与Surface的渲染耦合问题视频黑屏但声音正常是多媒体开发里最高频的问题之一。大多数情况下是渲染Surface和播放器的时序没对上。MediaPlayer需要绑定一个Surface用于画画面而SurfaceView的Surface不是创建好就一直有效的它有自己的生命周期。当你设置播放器数据源后如果Surface还没创建完成就调用setDisplay或者setSurface画面就不上屏但音频照常走于是就有了“只听声音不见人”的诡异现象。正确的流程是先监听SurfaceHolder.Callback的surfaceCreated确认Surface有效再去把Surface设置给播放器在surfaceDestroyed时移除播放器的显示目标并暂停播放。SurfaceView在View不可见时Surface会被销毁所以列表里滑出屏幕的视频如果不暂停就会反复触发销毁重建小内存手机上直接卡成PPT。此时很多人会考虑用TextureView替换SurfaceView因为TextureView是可渲染的普通View能放动画、能走ViewGroup布局不像SurfaceView那样悬浮在窗口之上。两者对比起来各有取舍SurfaceView性能好、开销低适合视频播放本身TextureView灵活、支持变换和缩放适合需要叠加UI的场景。现在的视频列表应用里很多是把TextureView放在Item上配合复用机制做流畅滑动但代价是比SurfaceView费电、延迟稍高。具体选哪个没有绝对答案要根据你的业务场景来权衡。4.2 旋转屏幕和按Home键播放器“死给你看”Activity在旋转屏幕时会经历销毁重建很多人第一次做视频播放转个屏幕就发现播放器崩了或者画面停了。原因很简单Activity重建了旧的MediaPlayer和Surface都被释放新Activity拿着新播放器却没有资源地址和进度。解决方案也不复杂就是一套生命周期约定。必须在onPause时暂停播放在onStop时停止播放并释放不需要的资源在onDestroy时最终release。与此同时用onSaveInstanceState保存当前播放进度重建后恢复进度用seekTo跳回去。这套约定不是Android规定的强制死板要求而是系统资源有限情况下最稳妥的使用方式。你不在适当时机释放系统也会在底层替你清理但清理时可能已经让用户看到ANR或者闪退。这里还有一个小技巧如果你希望旋转屏幕时播放不中断可以从两个方向考虑一是锁定屏幕方向强制竖屏播放简单粗暴但不一定符合产品预期二是手动处理配置变更在Manifest里给Activity加android:configChangesorientation|screenSize让Activity不重建只走onConfigurationChanged。这个方案适用于播放器页面比如全屏播放、视频聊天页面它能减少很多不必要的重建开销。代价是你要自己处理控件布局变更比如横竖屏切换时调整播放器的宽高比。踩过一次坑之后我的习惯是播放页面手写configChanges处理音频播放页面老老实实走生命周期释放。4.3 Bitmap加载与OOM先读尺寸再采样图片处理是手机多媒体里水量最大的一块雷区。一张手机拍摄的4032x3024照片解码成ARGB_8888位图占用的内存是4032x3024x4约48MB。如果你的相册页面同时加载9张这样的原图内存直接爆掉OOM是分分钟的事。好在Android提供了很优雅的采样机制。核心思路是先用inJustDecodeBoundstrue读取一次图片的宽高这次decode不会真正加载像素内存开销极小。拿到宽高后根据你实际要显示的尺寸计算inSampleSize例如要显示200x200的缩略图原图4000x3000那么采样率至少应该是20实际算下来大概显示为200x150内存占用降到十几KB差别是几千倍。我常用的采样计算逻辑是BitmapFactory.Options options new BitmapFactory.Options(); options.inJustDecodeBounds true; BitmapFactory.decodeFile(filePath, options); int width options.outWidth; int height options.outHeight; int sampleSize 1; int targetWidth 400; while (width / sampleSize targetWidth * 2) { sampleSize * 2; } options.inSampleSize sampleSize; options.inJustDecodeBounds false; Bitmap bitmap BitmapFactory.decodeFile(filePath, options);这里的“* 2”是给自己留个余量防止列表里还要做圆角、模糊效果导致再需要更大尺寸的位图。除了采样还要养成及时调用bitmap.recycle()的习惯虽然Android在GC时会回收但位图在旧版本上存的是Native内存GC不一定及时。现在我们开发大多用Glide或Coil加载图片它们内部已经做好了采样、缓存、生命周期绑定比自己手写decodeFile稳定得多。但如果你只是单张拍照展示没引入图片库自己写采样也完全够用。还有一个容易被忽视的问题是图片方向。手机相机拍出的照片会写入EXIF旋角信息如果你直接用BitmapFactory解码不带方向信息显示出来可能是横的。正确做法是用ExifInterface读取orientation字段然后通过Matrix做旋转处理。这属于踩了才知道的暗坑不处理的话用户拍一张竖屏照片传上来预览却是横向的很容易被当成瞎写。4.4 后台播放与音频焦点要不要继续响得看场景产品对多媒体在后台的表现有不同的预期音乐播放器要支持后台播放需要前台服务配合保证进程优先级足够高短视频应用切后台就应该暂停不然用户会在不知情的情况下被声音轰炸语音通话类应用甚至要“抢占”音频焦点把其他App声音压下去。音频焦点的处理策略我之前提过这里补充一个具体场景假设你的App正在播放视频用户接了个电话系统会短暂失去音频焦点。你收到AUDIOFOCUS_LOSS_TRANSIENT回调时应该暂停播放等收到AUDIOFOCUS_GAIN时再恢复。如果是AUDIOFOCUS_LOSS说明焦点被长期拿走最稳妥的做法是停止播放并释放资源别赖着不走。音频焦点处理不当的后果在产品上很明显。我做过一个有声小说App初版没有处理焦点用户在听书时去刷抖音结果书的声音和抖音的声音混在一起直接被竞品压着打。后来补上焦点逻辑体验立刻正常。这个细节教科书上很少讲但真实项目里几乎必碰。另外讲一下后台播放与服务的取舍如果只是“退到后台继续播”可以考虑在onPause里判断业务条件决定是否暂停如果要做锁屏控制、通知栏进度条那就必须引入MediaSession和前台服务这套体系已经超出本篇范围但方向要先有概念。别等到产品提需求才开始查资料实时音视频领域每一个“简单需求”背后都可能是一整套框架。5. 常见问题速查照着这个表排查省三天时间我把实操作业里遇到的典型问题整理成一张速查表每一行都是我或者学员实际撞过的墙。排查速度提升一档少走很多弯路。症状可能原因排查方向与解决方案播放音频没声音媒体音量为0、音频焦点被抢、声道设置错误用AudioManager检查STREAM_MUSIC音量按音量键确认调的是媒体通道检查AudioAttributes用法视频画面黑屏但声音正常Surface未就绪就setDisplay、Surface销毁后播放器未清理用SurfaceHolder.Callback保证时序surfaceDestroyed里移除播放器的Surface并暂停视频被拉伸变形VideoView宽高没有根据视频尺寸适配重写onMeasure按视频宽高比算出显示宽高固定布局不要硬怼match_parent拍照返回后闪退FileProvider未配置、file_paths路径不对、Uri授权缺失检查Manifest provider节点、检查xml路径配置、确认addFlags授权图片加载崩溃OOM直接decode原图而没有采样用inJustDecodeBounds先读尺寸按需计算inSampleSize或换用Glide/Coil权限拒绝后无法重试没处理“不再询问”状态用shouldShowRequestPermissionRationale判断永久拒绝跳设置页手动开启播放器切后台继续响onPause/onStop里没有暂停释放在页面不可见时调用pauseActivity销毁时release短音效点快了不出声SoundPool还没加载完就play、maxStreams太小监听setOnLoadCompleteListener再播放按场景调大maxStreams旋转屏幕后播放中断Activity重建播放器资源被重新创建用onSaveInstanceState保存进度并seekTo播放页加configChanges手动处理旋转这张表不是让你出问题才翻建议在写代码前先过一遍。很多人犯的错误都是可以提前规避的所谓“经验”其实就是别人已经踩过的坑你要避开而已。6. 学完这章之后我建议你这样练手如果你正在跟教程学这一章光在模拟器上跑Demo真的学不到什么模拟器没有真实的摄像头环境、音频渲染时序也跟真机不一样。我的建议是找一台Android真机做一个串联小项目打开页面播放一段背景音乐点击按钮拍一张照照片显示在列表中点列表项播放一段本地视频切后台时音乐自动暂停回前台继续播放。这个小项目看着简单但它把音频焦点、生命周期、权限申请、拍照FileProvider、图片压缩、Surface渲染全部串到了一起真能做完并且不崩溃你对手机多媒体的理解基本过关了。做这个练手项目时有一点我想特别提醒不要一出问题就去搜包名加报错关键词先自己打印日志看状态变化。播放器报IllegalStateException你去看日志里在哪个方法、当时是什么状态比抄一堆答案有用得多。我甚至建议你自己写一个状态机把MediaPlayer的不同状态打出来比任何调试都直观。另外一个方向是思考多媒体和业务的连接。手机多媒体的本质是硬件能力的产品化你做的不只是“能放视频”或“能拍照片”而是要通过这些能力去解决用户的实际问题。同样是调用摄像头做一个证件照工具和做一个美颜App背后要深挖的技术方向完全不同。这一章教的是基础能力能力和场景结合才是你真正的竞争力。我自己也是从用VideoView放一个视频开始一路做到自定义渲染、音画同步每次回看这一章的标题“丰富你的程序”都有新的理解。慢慢来多做多踩这些东西就会变成你自己的手感。