news 2026/10/10 10:05:09

Android视频播放器开发实战:Java实现MediaPlayer与生命周期管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Android视频播放器开发实战:Java实现MediaPlayer与生命周期管理

做 Android 视频播放器,可能是每个移动端开发者都会碰到的经典需求。网上搜“Android studio 视频播放器软件源代码 java”,能翻出不少Demo,但很多要么是几年前的老项目跑不起来,要么就是把 ExoPlayer 的官方示例改个 UI 就发出来,真正能把 MediaPlayer、SurfaceView、生命周期这些底层逻辑讲清楚的不多。这篇博文我就以实际项目经验为基础,带你把 Java 版 Android 视频播放器的完整实现拆开揉碎,从选型到代码再到踩坑,一次性讲透。


1. 项目整体设计与思路拆解

1.1 一个视频播放器项目到底包含什么

在正式写第一行代码之前,先想清楚一个问题:你想要的“视频播放器”是哪种?这个需求直接决定项目复杂度。

最简单的形态,是一个页面 + 一个 VideoView,加载本地 mp4 就完事。这种 Demo 最大的问题是不具备真实项目的代表性,一旦遇到网络视频、列表滑动、横竖屏切换、音量亮度调节、倍速播放,几乎全部要推翻重写。

我在设计这个项目时,目标很明确:做一个“麻雀虽小五脏俱全”的结构,包含两个核心界面——视频列表页和播放页。列表页读取包内或手机存储的视频文件,点击某一项跳转播放页;播放页负责视频解码、渲染、进度控制、音量亮度控制、横竖屏切换和异常处理。虽然代码量不大,但把移动端播放器的关键问题都覆盖了一遍。

这个项目适合三类人看:一是刚学完 Android 基础、想拿一个综合性项目练手的初学者;二是工作中突然接到视频相关需求、需要快速上手方案的开发者;三是想了解 MediaPlayer 系播放器底层机制的人。

1.2 为什么选 Java 而不是 Kotlin

这个项目标题里明确写了“java语言”,其实背后有一个比较现实的原因。虽然 Kotlin 已经是 Android 生态的官方首选,但很多老项目的存量代码、外包项目的技术栈、以及不少教科书和培训资料还在用 Java。Java 版本有一个独特的优势:API 调用方式直观,没有协程和扩展函数的语法干扰,非常适合用来理解播放器的状态机模型。

MediaPlayer 本身就是一个状态机:空闲、初始化、准备中、准备完成、播放中、暂停、播放完成、错误、释放。用 Java 的 switch-case 和回调接口来处理这些状态,逻辑更直白。我见过不少用 Kotlin 写的播放器,协程一多反而把状态流转搞乱了。所以这个项目我保留了 Java 的保守风格,后续如果读者想转 Kotlin,核心逻辑其实可以平移。

1.3 项目目录结构与核心类设计

整个工程采用标准的 Android Studio 结构,包名可以随意,但内部类的职责必须清晰。我直接说结论,以下这套分包方式是实测下来最顺手的:

com.example.videoplayer ├── MainActivity.java // 视频列表页 ├── PlayerActivity.java // 播放页(核心) ├── VideoAdapter.java // RecyclerView 适配器 ├── model/ │ └── VideoInfo.java // 视频信息实体类 └── utils/ └── VideoScanner.java // 扫描本地媒体库的工具类

MainActivity 负责扫描视频、展示列表;PlayerActivity 负责播放控制;VideoInfo 记录视频路径、名称、时长、大小;VideoScanner 封装了 MediaStore 和 File 两种扫描方式。职责单一,哪个环节出问题能快速定位。


2. 播放内核选型:MediaPlayer 与 ExoPlayer 的取舍

2.1 MediaPlayer 的本质与副作用

MediaPlayer 是 Android 框架层提供的系统播放器,底层封装了 Stagefright/OMX 的解码能力,对开发者来说是一个“黑盒”。它的优势很明显:集成简单、支持常见格式、系统级优化到位。

但它的短板也真实存在。最让人头疼的是对 HLS、DASH 流媒体的支持不够灵活,遇到某些网络串流会出现音画不同步;其次是扩展性差,你想接入一个自定义字幕渲染器或做音频音效处理,会被系统实现限制住。如果你只是做一个本地视频播放器,MediaPlayer 的稳定性是够用的。

这里我补充一句实在话:很多教程说 MediaPlayer 已经过时,其实不对。在本地播放场景下,MediaPlayer 的兼容性和耗电表现并不差,关键看你的视频源是不是可控的。

2.2 ExoPlayer 的架构优势与使用成本

ExoPlayer 是应用层的播放器库,好处是可控性极强。它把“数据源”“渲染器”“轨道选择器”拆成了独立模块,你可以只加载需要的组件。比如只播放 mp4,就只带 ProgressiveMediaSource;要支持自适应码率流,再把 HlsMediaSource 加进来。

但付出的代价是学习曲线陡峭。ExoPlayer 的 PlayerView 虽然开箱即用,但一旦涉及自定义加载策略、缓存预加载、失败重试,你得先搞清楚它那一套 ListenablePlayer 回调体系,对新手不太友好。

2.3 我的最终选择与理由

这个项目我用了MediaPlayer + SurfaceHolder + TextureView的组合。原因有三:

第一,标题场景是本地和简单的网络视频文件,MediaPlayer 完全能胜任。第二,Java 版本里 MediaPlayer 的状态逻辑更适合用来教学,因为每个回调都对应一个明确的生命周期节点。第三,我保留了切换 ExoPlayer 的扩展点——统一封装了一个 PlayerManager 类,未来如果需求升级,只需替换 Manager 内部实现,UI 层零改动。

提示:如果你一开始就清楚项目必须支持 HLS 直播流、自适应码率切换,直接上 ExoPlayer 更省事,别走我这套方案再迁移。


3. 核心代码实现:从列表到播放的完整链路

3.1 扫描视频文件:解决“数据从哪来”的问题

列表页之前,先要有数据。Android 获取本地视频有两种主流方式:查 MediaStore 媒体库和直接遍历目录。前者是官方推荐路径,能拿到系统索引过的所有媒体文件;后者适合了解文件系统的人去浏览特定路径。

我的 VideoScanner 用 MediaStore 实现,因为系统索引已经包含视频时长、分辨率、添加时间等信息,省去解析视频头部的开销。核心代码非常简洁:

public static List<VideoInfo> scanVideos(Context context) { List<VideoInfo> videos = new ArrayList<>(); Uri collection = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ? MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL) : MediaStore.Video.Media.EXTERNAL_CONTENT_URI; String[] projection = { MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE, MediaStore.Video.Media.DATA }; Cursor cursor = context.getContentResolver().query( collection, projection, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { VideoInfo info = new VideoInfo(); info.setName(cursor.getString(1)); info.setDuration(cursor.getLong(2)); info.setSize(cursor.getLong(3)); info.setPath(cursor.getString(4)); videos.add(info); } cursor.close(); } return videos; }

注意一个关键细节:Android 10(API 29)以上,MediaStore.Video.Media.EXTERNAL_CONTENT_URI被标记为 deprecated,需要改成getContentUri(MediaStore.VOLUME_EXTERNAL)。如果你的 targetSdk 比较新,还用老写法提交代码,编译直接报警告,真机上面还会出现查不到数据的问题。

3.2 列表页与 RecyclerView 适配

列表页使用 RecyclerView 是现在的主流做法。适配器里要注意一个体验问题:获取视频缩略图是高耗时操作,如果在 onBindViewHolder 里同步用MediaStore.Images.Thumbnails获取,列表滑动必然卡顿。这里我建议结合线程池加载缩略图,或者在保持 Demo 简单的前提下先只显示名称和时长。

VideoAdapter 的绑定逻辑可以这样写:

@Override public void onBindViewHolder(@NonNull VideoHolder holder, int position) { VideoInfo item = videos.get(position); holder.title.setText(item.getName()); holder.duration.setText(formatDuration(item.getDuration())); holder.itemView.setOnClickListener(v -> itemClickListener.onItemClick(item.getPath())); } private String formatDuration(long durationMs) { long totalSeconds = durationMs / 1000; long minutes = totalSeconds / 60; long seconds = totalSeconds % 60; return String.format(Locale.CHINA, "%02d:%02d", minutes, seconds); }

列表页跳转播放页时,只需要把视频路径作为 Intent 的 extra 传过去。这里不建议直接传整个 VideoInfo 对象,因为实体类一旦实现 Serializable 会被系统序列化,来回传递浪费性能,传路径字符串最简单干净。

3.3 播放页的布局与实际绑定逻辑

PlayerActivity 的布局是我花时间最多的部分。传统的 SurfaceView 存在一个老问题:它有自己的独立窗口,无法进行平移、缩放、旋转等动画操作,叠加 UI 控件也可能出现“盖不住”或“显示层级乱”的问题。

所以我用了TextureView,它是一个传统的 View,可以被包含在视图层级中,完美支持动画和 UI 叠加。播放页布局的核心结构:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" /> <LinearLayout android:id="@+id/bottom_control" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="#99000000" android:orientation="vertical" android:padding="12dp"> <SeekBar android:id="@+id/seek_bar" android:layout_width="match_parent" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/btn_play_pause" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="播放" /> <TextView android:id="@+id/tv_current_time" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="00:00 / 00:00" /> </LinearLayout> </LinearLayout> </FrameLayout>

这里我把控制栏直接叠在 TextureView 上层,点击视频区域时控制栏显示/隐藏,交互直观。

3.4 MediaPlayer 初始化的完整步骤

播放器的初始化是整个项目的核心,我会把每一步都拆开说明,因为它直接涉及到 MediaPlayer 状态机的正确流转。

private void initPlayer(String videoPath) { // 1. 确保 TextureView 的 Surface 已经准备好 if (!textureView.isAvailable()) { textureView.setSurfaceTextureListener(this); return; } // 2. 创建并配置 MediaPlayer if (mediaPlayer == null) { mediaPlayer = new MediaPlayer(); } else { mediaPlayer.reset(); } try { // 3. 设置视频源 mediaPlayer.setDataSource(videoPath); // 4. 绑定 Surface mediaPlayer.setSurface(new Surface(textureView.getSurfaceTexture())); // 5. 异步准备,避免阻塞 UI 线程 mediaPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); showError("视频路径无效"); } // 6. 设置监听回调 mediaPlayer.setOnPreparedListener(mp -> { int duration = mp.getDuration(); seekBar.setMax(duration); tvCurrent.setText(formatTime(duration)); mp.start(); updateProgress.run(); }); mediaPlayer.setOnCompletionListener(mp -> { btnPlayPause.setText("重新播放"); seekBar.setProgress(0); handler.removeCallbacks(updateProgress); }); mediaPlayer.setOnErrorListener((mp, what, extra) -> { handler.removeCallbacks(updateProgress); showError("播放出错: what=" + what + ", extra=" + extra); return true; }); }

这里有两个新手容易忽略的点,我特意单独拎出来说:

第一,为什么用 prepareAsync 而不用 prepare。MediaPlayer 的 prepare 是同步阻塞方法,操作耗时取决于视频大小和磁盘 IO,如果在主线程调用,会出现几秒到十几秒的黑屏和 ANR 风险。prepareAsync 搭配 OnPreparedListener 才是正道。

第二,setSurface 的时机。TextureView 在创建后的某个时刻才会有可用的 SurfaceTexture,主线程直接调用 mediaPlayer.setSurface 可能抛 IllegalStateException。所以正确姿势是在 onSurfaceTextureAvailable 回调里再初始化播放器,或者像上面代码那样检查 isAvailable。

3.5 进度条刷新与拖动跳转

进度条的自动更新我用了一个 Handler + Runnable 的经典模式:

private final Runnable updateProgress = new Runnable() { @Override public void run() { if (mediaPlayer != null && mediaPlayer.isPlaying()) { int position = mediaPlayer.getCurrentPosition(); seekBar.setProgress(position); tvCurrent.setText(formatTime(position)); } handler.postDelayed(this, 300); } };

刷新间隔 300ms 是实测的平衡点——太频繁会频繁触发 UI 绘制、浪费电;间隔太长,进度条的移动感就变差了。SeekBar 的拖动监听要区分“用户拖动时”和“播放自动刷新时”,否则会互相打架,标准的处理是加一个布尔标记:

seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser && mediaPlayer != null) { // 拖动进度时暂停刷新 isDragging = true; } } @Override public void onStartTrackingTouch(SeekBar seekBar) { isDragging = true; } @Override public void onStopTrackingTouch(SeekBar seekBar) { if (mediaPlayer != null && isDragging) { // 用户松开后跳转 handler.removeCallbacks(updateProgress); mediaPlayer.seekTo(seekBar.getProgress()); if (!mediaPlayer.isPlaying()) { mediaPlayer.start(); } handler.postDelayed(updateProgress, 300); isDragging = false; } } });

这里涉及的 seekTo 也有讲究。MediaPlayer 的 seekTo 是异步操作,执行后 getCurrentPosition 不会立刻更新,所以我在用户松手后才恢复进度刷新,避免进度条“跳回原位”的诡异现象。

3.6 生命周期管理:播放器最容易被坑的一环

播放器与 Activity 的耦合程度极高,监听回调涉及异步任务,稍不留神就会出现内存泄漏或IllegalStateException。

我实测有效的生命周期处理方案如下:

  • onPause:如果视频正在播放,先暂停,保存当前播放位置。
  • onStop:同上,释放不必要的资源。
  • onDestroy:必须释放播放器、移除 Handler 回调、解绑 Surface。
@Override protected void onPause() { super.onPause(); if (mediaPlayer != null && mediaPlayer.isPlaying()) { savedPosition = mediaPlayer.getCurrentPosition(); mediaPlayer.pause(); } } @Override protected void onDestroy() { super.onDestroy(); handler.removeCallbacks(updateProgress); if (mediaPlayer != null) { if (mediaPlayer.isPlaying()) { mediaPlayer.stop(); } mediaPlayer.release(); mediaPlayer = null; } }

release()之后一定要把引用置 null,否则 Activity 销毁后 MediaPlayer 持有的资源不会被 GC 回收,反复进出页面最终会直接把内存打到 OOM。这是我在项目里踩过最深刻的坑之一,后面在问题章节详细说。


4. 常见问题与排查技巧实录

4.1 权限配置引发的“看不见视频”问题

不少初学者把权限配好、代码写完,一跑模拟器发现列表是空的。排查思路其实很固定:

首先确认 Manifest 里有这两个权限:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

其次确认运行时权限有没有动态申请。Android 6.0(API 23)开始,危险权限必须运行时申请,只写 Manifest 不行。我在 MainActivity 里用 ActivityResultContracts 做动态申请:

ActivityResultLauncher<String[]> permissionLauncher = registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), result -> { Boolean readGranted = result.get(Manifest.permission.READ_EXTERNAL_STORAGE); if (Boolean.TRUE.equals(readGranted)) { initVideoList(); } else { Toast.makeText(this, "没有存储权限,无法扫描视频", Toast.LENGTH_SHORT).show(); } }); // 触发申请 permissionLauncher.launch(new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, (Build.VERSION.SDK_INT >= 33) ? Manifest.permission.READ_MEDIA_VIDEO : Manifest.permission.READ_EXTERNAL_STORAGE });

Android 13 上把存储权限拆成了 READ_MEDIA_VIDEO,和旧权限不兼容,做兼容时要同时考虑两个分支。这个我记在了每天的开发笔记里,因为每到新系统升级都会有人踩。

4.2 播放黑屏、只有声音没有画面

“黑屏有声音”是 MediaPlayer 项目里最典型的症状,原因基本集中在Surface 绑定失败或TextureView 尺寸不正确。

我遇到过的真实场景是:TextureView 还没有完成初始化,MediaPlayer 就已经 start,此时 Surface 为 null,视频解码出来无路可去,声音正常播放但画面停留在黑屏状态。解决办法就是前面说的在onSurfaceTextureAvailable回调之后再创建播放器。

另一个原因是视频分辨率与 TextureView 宽高不一致,旧机型上默认拉伸变形。处理逻辑是播放器准备完成后,对比视频宽高和控件宽高计算缩放比例:

int videoWidth = mp.getVideoWidth(); int videoHeight = mp.getVideoHeight(); ViewGroup.LayoutParams lp = textureView.getLayoutParams(); float viewRatio = (float) textureView.getWidth() / textureView.getHeight(); float videoRatio = (float) videoWidth / videoHeight; if (videoRatio > viewRatio) { lp.height = (int) (textureView.getWidth() / videoRatio); } else { lp.width = (int) (textureView.getHeight() * videoRatio); } textureView.setLayoutParams(lp);

不要小看这套代码,缺少它,很多视频会出现上下黑边局部裁切或者变形。

4.3 内存泄漏与反复进出页面崩溃

这个坑我实打实排查过一整天。现象是:进入播放页、退出、再进入,反复五六次之后应用直接闪退,Logcat 里报 OOM。

最终定位到的原因是两层。第一层,Handler 无界消息持有 Activity 引用,Activity 销毁后 updateProgress Runnable 还在任务队列里,Handler 又持有 Activity,导致泄漏。第二层更隐蔽:TextureView 的 SurfaceTexture 绑定了 MediaPlayer,如果 MediaPlayer 不 release,SurfaceTexture 的资源会被系统继续占用。

解决方式就是我前面写的 lifecycle 代码。补充一点:Handler 在销毁时除了 removeCallbacks,还可以加handler.removeMessages(0)清空所有消息,双保险。

注意:不要在 onStop 里释放 MediaPlayer。Activity 进入后台再回到前台是常见的操作,如果释放得太早,回前台要重新初始化,容易出现黑屏闪烁。onPause 只暂停、onDestroy 才彻底释放,是我验证下来的最佳节奏。

4.4 播放损坏文件时的异常处理

媒体文件损坏概率不低。如果你只做了 setDataSource + prepareAsync,遇到文件头损坏,onError 回调能触发,但不做处理的话用户只能干等。

我用一个统一的错误处理流程:onError 里先判断 what 和 extra 的值,打印当前状态,再调用release()重建播放器。重点在于错误回调返回值的含义——返回 true 表示已处理错误,系统不再向用户弹错误对话框;返回 false 会让系统尝试恢复,但这往往导致第二次错误甚至 ANR。所以业务上建议返回 true。

4.5 常见问题速查表

现象原因解决要点
列表查不到视频没动态申请存储权限检查权限申请流程与 Android 13 的新权限
黑屏但有声音Surface 未绑定成功在 onSurfaceTextureAvailable 后再初始化播放器
画面变形或截断视频宽高比与控件不一致对比 videoRatio 与 viewRatio 动态调整TextureView
反复进出页面崩溃MediaPlayer 未 release / Handler 泄漏onDestroy 中 release 并置空,移除全部回调
seekTo 后进度条回跳拖动与刷新消息竞争用 isDragging 标记控制刷新
播放中断断续续网络视频加载不稳定使用本地缓存或切换 ExoPlayer
MediaPlayer 抛 IllegalStateException状态调用顺序错误任何操作前先检查当前状态,reset 后再 setDataSource

5. 功能扩展:从本地到网络,从基础到进阶

当前这个项目已经能跑通“扫描 - 列表 - 播放 - 控制”的完整链路,但要真正用于生产,还有几个方向可以扩展。我把自己规划过的几种加分扩展都列在下面,供你按需取舍。

第一个方向是支持网络视频。把 setDataSource 的路径换成 http/https 链接,理论上就能播放,但实际网络视频会遇到缓冲卡顿、网络波动、格式兼容问题。更稳的做法是引入 OkHttp 作为数据源,或者干脆切换到 ExoPlayer 的 ProgressiveMediaSource,这样还能拿到它的缓存策略。

第二个方向是手势交互。在 TextureView 的 onTouchEvent 里做手势识别:屏幕左侧上下滑动调亮度,右侧上下滑动调音量,居中滑动快进快退。手势控制属于体验升级,代码量不大,但能明显提升一个播放器的“质感”。

第三个方向是播放列表与视频封面。当前项目每次只传一个路径,如果要做成连续播放,可以在 PlayerActivity 里维护一个 List<String>,通过 position 实现上一集/下一集切换。封面加载则建议用 Glide 或 Coil,它们内部做了解码复用和内存缓存,比自己写 Bitmap 解码可靠得多。

第四个方向是缓存与预加载。对不追求直播场景的应用,缓存能显著降低回播的加载时间。简单的做法是使用 Android 的 HTTP 缓存策略,进阶做法是接入第三方缓存库把流媒体切成分片落盘。不过这一步涉及线程模型和数据一致性,难度陡增,新手先把基础链路稳住再动。


6. 我总结的一点体会

这个项目做完之后,我最大的收获其实不是掌握了 MediaPlayer 的 APIs,而是学会了“以一个状态机的视角去理解播放器”。MediaPlayer 的每个方法都有状态约束,真正稳的项目从来不靠乱调 API,而是靠设计良好的状态管理和资源释放策略。

根据我个人的经验,给准备上手这个项目的同学三条建议:第一,不要直接复制大段代码去跑,务必亲手把生命周期部分写一遍,因为只有踩过“退页面崩溃”的坑,才能真正理解 release 为什么必须在 onDestroy 而不是 onStop;第二,测试时务必准备几种不同类型的视频源——横屏、竖屏、高分辨率、低分辨率、甚至一个损坏的文件,覆盖面越大,你提前发现的问题越多;第三,本地播放跑通之后,再考虑接入网络和 ExoPlayer,分阶段迭代的体验比一次性堆大而全的方案舒服得多。

如果你后续想继续深入,建议从改造 PlayerManager 开始,把 MediaPlayer 的实现替换成 ExoPlayer,你会发现 UI 层基本不用动——这本身就是分层设计带来的最大红利。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 10:05:03

申请 EV 代码签名证书需要准备哪些材料?

EV代码签名证书是高等级代码签名证书&#xff0c;主要用于Windows软件、内核驱动程序签名&#xff0c;能够消除Windows系统“未知发布者”安全警告&#xff0c;满足微软驱动开发的强制签名要求。和OV代码签名证书相比&#xff0c;EV证书的企业身份核验标准更严格&#xff0c;申…

作者头像 李华
网站建设 2026/10/10 10:03:56

uni-app跨端开发实战:儿童安全教育平台从0到1

一个朋友在做一个幼教机构的信息化改造项目&#xff0c;中途拉着我聊了很久。他说机构想给家长提供安全教育内容&#xff0c;但又不想只发几张海报了事&#xff0c;打算做一个能看动画、能答题、能记录学习进度的小平台。聊到技术选型时他特别纠结&#xff0c;原生开发成本高&a…

作者头像 李华
网站建设 2026/10/10 10:03:29

基于Web的长江游轮公共服务系统:从数据库设计到工程部署

“基于Web的长江游轮公共服务系统”&#xff0c;这个标题我盯了很久。链路够长&#xff0c;也够典型——游轮业务、公共服务、Web化、后台管理、数据库设计、论文文档&#xff0c;基本把Web工程类项目的全要素都装进去了。很多人看到这种题目会以为只是个“CRUD拼凑”&#xff…

作者头像 李华
网站建设 2026/10/10 10:03:27

智能体从概念到落地:核心组件、记忆机制与多智能体编排实战

简介&#xff1a;这份PDF文档系统梳理了华为首次提出的智能体参考架构&#xff0c;面向政企数字化转型决策者、智慧城市方案设计者及AI架构学习者。内容围绕云网边端协同展开&#xff0c;涵盖智能交互、智能联接、智能中枢、智慧应用四层架构&#xff0c;并延伸至全场景智慧城市…

作者头像 李华
网站建设 2026/10/10 10:03:26

Windows 下 Claude Code 安装配置指南:从 Node.js 到环境变量与常见问题

1. 装之前先搞清楚&#xff1a;Claude Code 在 Windows 上到底该怎么装很多人第一次看到 Claude Code 的安装命令&#xff0c;以为这就是一行npm install的事&#xff0c;结果在 Windows 上装了半天不是claude命令找不到&#xff0c;就是装完了卡在登录界面。先说结论&#xff…

作者头像 李华