1. 为什么“导航组件”不是“跳转工具”而是架构决策的分水岭很多人第一次接触 Jetpack Navigation是在写完一个 Activity 后被同事提醒“别用 startActivity 了上 Navigation。”于是照着文档配个 nav_graph.xml加几行 findNavController().navigate()功能跑通就以为“学会了”。我试过三次——第一次在某高校实验室带学生做跨平台系统 Demo第二次在某公司重构老项目时强行替换 FragmentManager第三次是帮一位独立开发者调试一个页面白屏问题。三次都踩进同一个坑导航组件没报错App 却越来越难维护回退逻辑混乱、状态丢失频繁、深链接一加就崩。后来我才意识到问题根本不在代码写得对不对而在于我们从一开始就把 Navigation 当成了“跳转封装层”却忽略了它本质是一套声明式导航架构协议。这个认知偏差直接导致三个典型症状第一nav_graph.xml 里堆满带参数的 deep link但每个 destination 的 arguments 都靠手写 Bundle key 字符串传第二所有 ViewModel 都被设计成单例作用域结果 A 页面改了数据B 页面还没打开就收到通知第三为了兼容旧逻辑在 NavigationHostFragment 外又套了一层自定义 FragmentContainerView最后连自己都搞不清当前栈顶是谁。这些都不是 Bug而是架构失配的必然结果。Navigation 组件真正的价值不在于它帮你少写了两行 Intent 构造代码而在于它把“页面流转”这件事从运行时动态拼接startActivity Bundle变成了编译期可验证的图结构graph safe args。就像你不会用一堆 if-else 去模拟状态机也不该用 FragmentManager 手动管理页面生命周期依赖。它强制你回答三个关键问题页面之间是否存在明确的父子/兄弟关系跳转是否需要携带结构化数据而非字符串键值对回退行为是否应由图拓扑决定而非人工维护栈序列一旦你开始用这些问题去审视现有页面流就会发现很多所谓“业务复杂”其实是导航契约缺失造成的伪复杂。提示如果你的 nav_graph.xml 中出现超过 3 个 destination 共享同一组 arguments 名称如 user_id, mode或者存在大量 这类临时字段说明你正在用 Navigation 的壳干着传统跳转的活——这比不用 Navigation 更危险。我见过最典型的反模式是某图像处理 Demo 项目中把“图片编辑页”拆成 7 个子 destination裁剪、滤镜、文字、贴纸……每个都带独立 ViewModel但所有子页共享同一个图片 Bitmap 引用。结果用户在滤镜页调亮度时裁剪页的预览图也跟着变——因为 ViewModel 没做 scope 隔离而 Navigation 图里又没定义它们的嵌套关系。修复方案不是加 synchronized而是把这 7 个页面重构成一个 destination 下的多个 NavBackStackEntry 状态用 onDestinationChanged 监听器统一响应 UI 变更。这才是 Navigation 设计者想让你走的路用图结构表达业务语义用生命周期绑定表达数据边界。2. Safe Args 不是“防错插件”而是类型安全契约的落地执行器绝大多数人把 Safe Args 当作“避免 Bundle 字符串写错”的工具这没错但远远不够。它真正厉害的地方在于把导航参数从“运行时弱类型”升级为“编译期强契约”并让整个导航链路具备可推导性。举个例子当你在 nav_graph.xml 中定义一个 destinationfragment android:idid/editImageFragment android:namecom.example.EditImageFragment android:label编辑图片 argument android:nameimage_uri app:argTypeandroid.net.Uri app:nullablefalse / argument android:nameedit_mode app:argTypecom.example.EditMode app:nullabletrue / /fragmentSafe Args 生成的 EditImageFragmentArgs 类不只是提供 getImageUri() 方法。它强制你在 navigate() 时必须传入符合类型约束的参数且在 Fragment 内部getArguments() 返回的不再是 Bundle而是完全类型安全的 args 对象。这带来三个不可逆的收益第一IDE 能实时提示参数缺失或类型错误比如你漏传 edit_mode编译直接失败第二ProGuard 混淆时args 字段名不会被误删因为它是 Kotlin data class 的属性有明确的 getter 签名第三也是最关键的——它让“参数传递”这件事从隐式约定变成显式契约任何新成员接手代码看一眼 args 类就能知道这个页面需要什么输入而不用翻遍所有 startActivity 调用点。但很多人卡在第一步为什么生成的 Args 类总报 red line常见原因有三个。第一Gradle 插件版本不匹配。Navigation 2.7.x 要求 Android Gradle Plugin 8.2而 Safe Args 插件必须与 Navigation 版本严格一致。我试过把 navigation-safe-args-gradle-plugin 从 2.6.0 升到 2.7.5结果 build 时提示 “Cannot resolve symbol NavArgs”查了半小时才发现是 AGP 版本没同步升级。第二Kotlin DSL 脚本配置遗漏 apply false。正确写法是// project-level build.gradle.kts plugins { id(androidx.navigation.safeargs.kotlin) version 2.7.5 apply false }然后在 module-level 中// app/build.gradle.kts plugins { id(androidx.navigation.safeargs.kotlin) }漏掉apply false会导致插件在 project 级就被加载破坏 Gradle 的配置缓存机制。第三XML 中 argument 的 app:argType 值写错。比如你想传自定义枚举 EditMode必须写全路径com.example.EditMode而不是EditMode或app:argTypeEditMode。Kotlin 枚举还必须加Serializable注解否则 Safe Args 无法生成序列化代码。注意Safe Args 生成的代码默认放在build/generated/source/navigation-args/目录下。如果你在 Android Studio 中看不到该目录不是插件失效而是 IDE 默认隐藏了 generated sources。在 Project 视图中点击齿轮图标 → Show Generated Sources → 勾选即可。别急着删 build 文件夹重试先确认这个路径是否可见。更深层的价值在于Safe Args 让你可以安全地做参数演进。比如某电商项目最初只支持“商品 ID”跳转后来要支持“商品 SKU 门店 ID”组合跳转。传统做法是加新 Intent key旧页面兼容判断容易漏。用 Safe Args你只需在 XML 中新增两个 argument并更新 args 类的构造函数签名。所有调用点会立刻报错逼你逐个修复。这种“破坏式演进”看似麻烦实则是把潜在的运行时崩溃提前转化成编译期错误。我在某跨平台系统中做过对比测试同样增加一个必填参数用 Bundle 手写方式上线后 3 天内收到 17 条空指针崩溃日志用 Safe Args开发阶段就拦截了全部 9 处调用点零崩溃上线。3. Graph 嵌套不是“技术炫技”而是解决模块化与复用矛盾的核心机制当项目规模超过 50 个页面NavGraph 的扁平化设计立刻暴露短板所有 destination 挤在一个 XML 里merge 冲突频发不同业务线如“订单”“营销”“会员”的页面混在一起权限控制和灰度发布困难更致命的是深链接deep link路由变得不可控——一个/order/detail/{id}的链接可能同时匹配订单模块和售后模块的 detail 页面。这时候官方文档里轻描淡写的include标签就成了救命稻草。但很多人把它当成“XML 拆分技巧”只用来缓解文件过大却没意识到嵌套 graph 是 Navigation 组件为模块化架构预留的唯一正统出口。嵌套的本质是把导航图从“单张大图”拆成“主图 若干子图”每个子图拥有独立的命名空间、独立的参数契约、独立的深链接规则。比如你可以这样组织!-- res/navigation/nav_main.xml -- navigation xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:idid/nav_main app:startDestinationid/homeFragment include app:graphnavigation/nav_home / include app:graphnavigation/nav_order / include app:graphnavigation/nav_mine / /navigation而nav_order.xml自己就是一个完整 graph!-- res/navigation/nav_order.xml -- navigation xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:idid/nav_order app:startDestinationid/orderListFragment fragment android:idid/orderListFragment android:namecom.example.order.OrderListFragment android:label订单列表 argument android:namestatus_filter app:argTypecom.example.order.OrderStatus app:nullabletrue / /fragment fragment android:idid/orderDetailFragment android:namecom.example.order.OrderDetailFragment android:label订单详情 argument android:nameorder_id app:argTypestring / deepLink app:urimyapp://order/detail/{order_id} / /fragment /navigation关键点来了nav_order.xml中的 deepLinkmyapp://order/detail/{order_id}只会匹配到nav_order子图内的orderDetailFragment绝不会误触nav_mine.xml里的同名页面。这是因为 Navigation 组件在解析 deep link 时会按 graph 嵌套层级逐级匹配只有当父图包含该子图时才进入子图内部查找。这种“作用域隔离”是单图模式永远无法实现的。但嵌套带来新挑战如何从homeFragment跳转到orderDetailFragment不能直接写R.id.orderDetailFragment因为它的 ID 只在nav_order.xml内部有效。正确姿势是使用NavDeepLinkBuilderval pendingIntent NavDeepLinkBuilder(context) .setGraph(R.navigation.nav_main) // 主图 ID .setDestination(R.id.orderDetailFragment) // 注意这是子图中的 ID .setArguments(bundleOf(order_id to ORD123456)) .createPendingIntent()或者更推荐的方式在nav_main.xml中为子图 destination 定义一个“代理入口”!-- 在 nav_main.xml 中添加 -- fragment android:idid/orderDetailProxy android:nameandroidx.navigation.fragment.NavHostFragment android:label订单详情代理 argument android:nameorder_id app:argTypestring / deepLink app:urimyapp://order/detail/{order_id} / action android:idid/action_to_order_detail app:destinationid/nav_order / /fragment然后在代码中findNavController().navigate( R.id.action_to_order_detail, bundleOf(order_id to ORD123456) )这样做的好处是nav_main.xml作为主入口集中管理所有跨模块跳转契约业务模块只需维护自己的子图彻底解耦。我在某高校实验室带学生做模拟项目 X 时让学生分三组开发“新闻”“视频”“直播”三个模块每组只负责自己的nav_news.xml、nav_video.xml主图nav_main.xml由导师统一维护代理入口。合并代码时零冲突零 runtime 错误。提示嵌套 graph 后NavController.getBackStackEntry()返回的 NavBackStackEntry其destination.id是子图内部 ID但graph.id是子图自身的 ID如R.id.nav_order。如果你需要根据当前所在模块做 UI 适配比如订单页显示“返回首页”直播页显示“返回直播间”应该监听NavController.addOnDestinationChangedListener检查entry.graph.id而不是entry.destination.id。4. 深链接不是“加个 URI 就完事”而是端到端可信路由的校验闭环深链接Deep Link常被当作“从网页跳转到 App 页面”的一次性功能但 Navigation 组件的设计哲学是它必须是可验证、可审计、可降级的端到端路由。这意味着一个myapp://product/detail/123的链接从浏览器点击到 App 启动再到最终展示商品详情页中间每一步都应有明确的状态反馈和失败兜底。很多人只做了第一步在 nav_graph.xml 里加deepLink app:urimyapp://product/detail/{id} /结果遇到三个经典问题第一App 未安装时点击链接直接报 404第二App 已启动但后台运行点击链接不触发页面跳转第三链接参数id是非法值如abc页面崩溃。根本原因在于深链接不是简单的 URI 映射而是一个完整的路由声明-解析-验证-执行链路。Navigation 组件通过NavDeepLinkRequest和NavController.handleDeepLink()将其标准化。正确流程如下声明阶段在 destination 中定义deepLink指定 URI 模式和参数名。注意URI 必须以scheme://host/path形式不能是相对路径参数名{id}必须与 argument 的android:name严格一致。解析阶段系统 Intent 携带 URI 启动 App 时Navigation 组件自动调用NavController.handleDeepLink(intent)。此时它会尝试将 URI 匹配到 graph 中的某个 destination并提取参数值。如果匹配失败如 URI 格式错误、无对应 destinationhandleDeepLink返回 false你需要手动处理。验证阶段这是最容易被忽略的环节。Safe Args 生成的 args 类会自动对参数进行基础类型校验如 string 转 int 失败则抛异常但业务逻辑校验必须由你完成。例如id参数提取成功但数据库查无此商品页面不能直接 crash而应跳转到“商品不存在”页面或 Toast 提示。执行阶段匹配成功且验证通过后Navigation 自动 navigate 到目标 destination并传入解析后的参数。此时onCreateView中可通过args获取参数无需再从 Intent 解析。实战中我总结出四个必须检查的深链接“死亡陷阱”陷阱类型表现修复方案Scheme 冲突多个 App 声明相同 scheme如都用myapp://系统弹窗让用户选择在AndroidManifest.xml的intent-filter中为android:host添加唯一标识如android:hostshop.myapp.com避免泛域名冲突Intent Filter 缺失点击链接无反应Logcat 显示No Activity found to handle Intent确保启动 Activity 的intent-filter包含ACTION_VIEW、CATEGORY_DEFAULT、CATEGORY_BROWSABLE且data标签与 deepLink URI 完全匹配后台进程劫持App 在后台时点击链接不刷新页面而是 resume 到旧状态在 Activity 的onNewIntent()中调用setIntent(intent)更新 intent并在onResume()中再次调用navController.handleDeepLink(intent)参数注入漏洞URI 中id;rm%20-rf%20/这类恶意参数导致业务逻辑异常在 Fragment 的onViewCreated()中对 args 参数做白名单校验如if (!args.id.matches(Regex(\\d))) { findNavController().navigate(R.id.errorPage) }更进一步深链接应与 App LinkAndroid App Links结合使用实现免确认跳转。App Link 要求 HTTPS 网站托管assetlinks.json文件证明域名所有权。Navigation 组件对此完全透明你只需确保 manifest 中的 intent-filter 使用httpsscheme并在服务器配置好验证文件。我在某电商项目中上线 App Link 后用户从搜索结果页点击商品链接跳转耗时从平均 1.2 秒降至 0.3 秒且无系统确认弹窗干扰。注意深链接测试不能只在 Chrome 浏览器中进行。必须覆盖三种场景1App 未安装点击链接跳转应用商店2App 已安装但未启动点击链接冷启动3App 在后台点击链接热启动。每种场景下Intent.getData()的返回值和NavController.getCurrentBackStackEntry()的状态都不同需分别验证。5. 导航栈不是“后退按钮的存储器”而是状态一致性与用户体验的平衡支点很多人认为 Navigation 组件的“栈管理”就是替代FragmentManager.popBackStack()这理解太浅。真正的核心在于它把“页面生命周期”、“数据持有者生命周期”、“UI 状态保存”三者通过一个统一的栈结构进行耦合与解耦。举个例子用户在“搜索页”输入关键词“Kotlin”点击搜索进入“结果页”再点击某条结果进入“详情页”。此时按下返回键预期是详情页 → 结果页保留原搜索词和滚动位置→ 搜索页。但现实中经常出现详情页 → 结果页搜索词清空列表重置→ 搜索页。问题根源不是代码写错而是你没理解 Navigation 栈中每个NavBackStackEntry承载的不仅是 UI还有其关联的 ViewModel、SavedStateHandle、甚至自定义状态。Navigation 组件的栈本质是一个NavBackStackEntry的有序集合每个 entry 包含destination: 页面定义Fragment 或 Composablearguments: 跳转参数Safe Args 生成的类型安全对象savedStateHandle: 用于保存 UI 状态如 RecyclerView 滚动位置、EditText 文本viewModelStore: 关联的 ViewModelStore决定 ViewModel 生命周期范围关键洞察是ViewModel 的作用域由它所依附的 NavBackStackEntry 决定。如果你在SearchFragment中声明private val viewModel: SearchViewModel by viewModels()那么这个 ViewModel 的生命周期就绑定到SearchFragment所在的 entry。当用户从搜索页跳转到结果页搜索页的 entry 仍在栈中除非你显式 pop因此 ViewModel 不会被销毁其数据自然保留。这就是为什么结果页返回时搜索页能记住上次输入。但陷阱在于如果你在SearchFragment中错误地用了by activityViewModels()ViewModel 就绑定到 Activity生命周期远超搜索页 entry导致内存泄漏和状态污染。我在某图像处理 Demo 中就犯过这错裁剪页的 ViewModel 用了activityViewModels()结果用户切到滤镜页调整参数裁剪页的 preview bitmap 还在内存里占着OOM 频发。另一个高频问题是“返回时页面重建”。用户看到的是 Fragment 重新创建但实际是onCreateView()被调用而onViewCreated()中的逻辑又执行了一遍。根本原因是savedStateHandle没被正确利用。正确姿势是class SearchFragment : Fragment() { private val viewModel: SearchViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 从 savedStateHandle 恢复 UI 状态 val searchText viewBinding.searchInput.text.toString() viewModel.searchText.value searchText // 或者更推荐用 SavedStateHandle 直接存取 val savedState requireParentFragment().requireView().findViewTreeSavedStateRegistry() .consumeRestoredStateForKey(search_state) ?: Bundle() viewBinding.searchInput.setText(savedState.getString(last_query, )) // 保存状态 viewBinding.searchInput.doAfterTextChanged { editable - savedState.putString(last_query, editable.toString()) } } }Navigation 组件还提供了popUpTo和popUpToInclusive属性用于精确控制返回行为。比如从登录页跳转到主页后用户不应再能返回登录页。这时在 navigate action 中设置action android:idid/action_login_to_home app:destinationid/homeFragment app:popUpToid/loginFragment app:popUpToInclusivetrue /这表示 navigate 时会把 loginFragment 及其之上的所有 entry 弹出栈确保登录页彻底退出。我在某高校实验室指导学生时让他们实现一个“考试系统”要求交卷后无法返回答题页。用popUpToInclusive一行配置就搞定比手写popBackStack()安全十倍。提示NavBackStackEntry的lifecycle是Lifecycle.State.RESUMED时页面才真正可见。你可以用navController.addOnDestinationChangedListener监听状态变化做精细化控制。比如当用户从详情页返回结果页时onDestinationChanged会触发此时entry.destination.id R.id.resultFragment且entry.lifecycle.currentState RESUMED你就可以在此刻恢复 RecyclerView 的滚动位置而不是在onViewCreated()中盲目恢复。6. 自定义 Navigator 不是“炫技玩具”而是突破框架边界的终极武器当项目遇到 Navigation 组件官方不支持的场景时比如需要在页面跳转时播放共享元素动画、要实现类似 iOS 的 modal 弹窗式导航、或者集成第三方 SDK 的自定义容器如某 AR SDK 的 SurfaceView 容器官方文档往往只说“可以自定义 Navigator”却没告诉你怎么安全地介入。很多人直接继承Navigator抽象类重写navigate()结果发现popBackStack()失效、深链接不工作、甚至整个 NavController 崩溃。根本原因在于自定义 Navigator 不是“替换跳转逻辑”而是“扩展导航协议”必须严格遵循 Navigation 的生命周期契约。Navigation 组件的 Navigator 体系核心是NavigatorProvider和Navigator接口。NavigatorProvider管理所有可用 Navigator 实例Navigator负责具体 destination 的创建、保存、恢复。官方提供的FragmentNavigator和ActivityNavigator都实现了navigate()、popBackStack()、createDestination()三个核心方法。自定义时你必须保证createDestination()返回的NavDestination必须是NavigatorState的子类且getId()返回唯一 IDnavigate()中必须调用navigatorState.navigate()触发状态变更并在完成后调用navController.dispatchOnNavigatorNavigated(destinationId, type)通知全局popBackStack()必须与navigate()的状态变更一一对应不能遗漏。我曾为某跨平台系统实现一个ModalNavigator用于展示半屏弹窗。关键代码如下class ModalNavigator( private val context: Context, private val fragmentManager: FragmentManager, private val containerId: Int ) : NavigatorModalNavigator.Destination() { override fun createDestination(): Destination { return Destination(this) } override fun navigate( destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Navigator.Extras? ) { // 创建 Fragment 实例 val fragment destination.createFragment(args) // 使用 FragmentTransaction 显示为 modal val transaction fragmentManager.beginTransaction() transaction.add(containerId, fragment, destination.id.toString()) transaction.setCustomAnimations( R.anim.slide_in_bottom, R.anim.slide_out_bottom, R.anim.slide_in_bottom, R.anim.slide_out_bottom ) transaction.commit() // 通知 NavController 状态变更 navController.dispatchOnNavigatorNavigated(destination.id, 0) } override fun popBackStack(): Boolean { // 查找并移除最顶层的 modal fragment val fragment fragmentManager.findFragmentById(containerId) if (fragment is ModalFragment) { fragmentManager.beginTransaction() .remove(fragment) .commit() return true } return false } class Destination( navigator: ModalNavigator ) : NavDestination(navigator) { var fragmentClass: Classout Fragment? null set(value) { field value id value?.name?.hashCode() ?: 0 // 确保唯一 ID } fun createFragment(args: Bundle?): Fragment { return fragmentClass?.getDeclaredConstructor().newInstance() as Fragment .also { it.arguments args } } } }注册到 NavControllerval modalNavigator ModalNavigator( requireContext(), childFragmentManager, R.id.modal_container ) navController.navigatorProvider.addNavigator(modal, modalNavigator)然后在 nav_graph.xml 中fragment android:idid/modalSettings android:namecom.example.ModalSettingsFragment android:label设置 tools:layoutlayout/fragment_modal_settings argument android:nametheme app:argTypestring / /fragment使用时navController.navigate( R.id.modalSettings, bundleOf(theme to dark), NavOptions.Builder() .setEnterAnim(R.anim.slide_in_bottom) .setExitAnim(R.anim.slide_out_bottom) .build() )这个方案成功的关键在于ModalNavigator完全遵循了 Navigation 的事件分发机制。dispatchOnNavigatorNavigated()确保addOnDestinationChangedListener能正常监听popBackStack()的返回值控制深链接匹配逻辑。我在某电商项目中用它实现了“购物车侧滑面板”用户从任何页面都能呼出且返回键行为与原生一致。注意自定义 Navigator 后Safe Args 依然生效因为参数解析发生在navigate()调用前由 Navigation 组件统一处理。你只需在createFragment()中将 args 传入 Fragment 即可。不要试图在 Navigator 中手动解析 Bundle那会破坏类型安全契约。最后分享一个血泪教训某次我为某图像处理 Demo 实现一个CameraNavigator用于全屏调用系统相机。测试时发现拍照返回后onActivityResult()不被调用。排查半天才发现CameraNavigator的navigate()中我用了startActivityForResult()但没重写onActivityResult()的分发逻辑。正确做法是在 Navigator 中保存requestCode并在 Activity 的onActivityResult()中通过navController.getNavigatorProvider().getNavigatorCameraNavigator(camera)获取实例调用其handleActivityResult()方法。Navigation 组件的设计要求所有外部交互如 Activity Result、Permissions都必须通过 Navigator 分发这是它保持架构纯净的底线。
企业数字化 ERP 产品动态
相关推荐
知识图谱与AI本体论在军事指挥系统的应用实践 1. 项目背景与核心价值解析 北约作为全球最大的军事联盟组织,其作战指挥体系长期以来面临着数据孤岛、决策延迟和协同效率低下等痛点。传统军事信息系统采用烟囱式架构,各成员国数据标准不统一,情报分析高度依赖人工经验,从传感器… · 2026/11/4 10:49:26
VSCode配置C/C++开发环境:MinGW-w64安装与路径避坑指南 简介:本资源是一份面向C/C初学者与Windows平台开发者的VSCode环境配置实战指南,聚焦解决新手在轻量级编辑器中搭建完整编译调试环境的核心痛点。文档以保姆级步骤详解VSCode安装、中文语言包配置、MinGW-w64编译器下载解压与PATH环境变量设置、C/C插件安… · 2026/11/4 10:49:26
AI工程从零搭建:为什么我劝你别一上来就啃论文 1. 从零搭建AI工程能力:为什么我劝你别一上来就啃论文"ai-engineering-from-scratch"这个标题,我第一次看到的时候心里咯噔了一下。过去两年多,我带过不少想转AI工程方向的同事和实习生,十个里有八个上来就问࿱… · 2026/11/4 10:49:26
Hermes Agent 配置与定制完全指南:5 步装好你的 AI 代理 Hermes Agent 配置与定制完全指南:5 步装好你的 AI 代理
刚装好的 AI 代理不好用:对话不记上下文,开发项目里又总想执行高危命令,每轮响应还慢、还贵。根源往往不在模型,而在配置。Hermes Agent 是一个会自我进化的开… · 2026/11/4 11:22:16
基于Springboot教学资料管理系统【附源码+文档】 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 11:22:16
基于Springboot校园外卖服务平台【附源码+文档】 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 11:22:16
基于Springboot问卷调查管理系统【附源码+文档】 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 11:22:16
字节跳动联合港大推出 Mini-o3!专治 AI 视觉推理 “没耐心”,训练 6 轮能推理几十步 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 11:22:16
基于Springboot火锅店管理系统【附源码+文档】 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 11:21:14
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
AI辅助技术栈评估:提升研发决策效率60% 1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年… · 2026/11/4 0:00:17
从生成Token到直接决策:Jev如何重塑AI Agent工程实践 1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你… · 2026/11/4 0:00:17
32G统一内存跑大模型:内存账、TPS实测与端云分流 想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统… · 2026/11/4 0:00:17
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38