业务代码参数透传满天飞?(二)

AI落地实操导航指南:从业务问题出发的零代码落地路径 AI落地不是技术选型竞赛,而是面向真实业务场景的问题求解过程。理解‘AI能力边界’与‘人类任务流’的映射关系,是避免概念空转、资源错配的前提;掌握‘5W1H-AI’诊断法和‘精度-速度-可控性’黄金三角选型逻辑,能显著提升工具匹配效率。尤其对非技术背景的决策者与一线执行者而言,关键不在于理解大模型原理,而在于识别可被自动化替代的重复性任务节点——如发票识别、会议纪要生成、合同条款比对等高频场景。本文聚焦‘开箱即用’的低门槛方案,融合RAG知识库、云OCR API、低代码平台等成熟能力,提供可验证、可复用、可 阅读详情

引子

项目中参数多级透传满天飞的情况很常见,增加了开发的复杂度、出错的可能、及维护的的难度。

透传包括两种形式:

  1. 不同界面之间参数透传。
  2. 同一界面中不同层级控件间透传。

该系列的目标是消除这两种参数透传,使得不同界面以及同一界面内各层级间更加解耦,降低参数传递开发的复杂度,减少出错的可能,增加可维护性。

上一篇通过向前查询参数的方式解决了第一个 case,本篇先聚焦在第二个 case,即同一界面不同层级控件间的参数透传。

透传举例

比如下面这个场景:

特效卡片的点击事件需要传入私参“type”,以表示属于哪个 tab 页。

界面层级如下:素材集市用 EffectActivity 来承载,其中的标签栏下方是一个子 Fragment ,其中包含了 ViewPager 控件,该控件内部的每一个页又是一个 Fragment。

埋点私参和上报时机分处于两个不同的页面层级。上报时机在最内层 Fragment 触发,而私参在 Activity 层级生成,遂需通过两层 Fragment 的透传。

于是就会出现如下代码:

// EffcetActivity.kt
override fun showEffectListContent(index: Int, from: String?) {
    mVpEffectContent.adapter = SimpleFragmentStatePagerAdapter(supportFragmentManager).apply {
        mTitles = arrayOf("视频库", "音乐", "音效", "贴纸", "转场", "特效", "滤镜", "背景", "字幕", "字体")
        mCount = mTitles!!.size
        createFragment = { position ->
            when (position) {
                0 -> {
                    val fragment = RemoteCenterFragment.newInstance( -1, true, 0, MaterialProtocol.SOURCE.MATERIAL_MARKET, 1, from, 0, 0)
                    val paramsParser = DeepLinkParamsParser(compileDeepLinkParams())
                    (fragment as IDeepLinkPage).setDeepLinkParams(paramsParser.deeplinkParams)
                    fragment
                }
                1 -> {
                    MaterialMusicFragment.newInstance(from,mSelectedTabId,mSelectedModelId)
                }
                2 -> {
                    MaterialAudioFragment.newInstance(mSelectedTabId,mSelectedModelId)
                }
                // 索引值到类型值的映射
                3 -> {
                    EffectListFragment.newInstance(CommonConstant.SERVER.TYPE_VSTICKER)
                }
                4 -> {
                    EffectListFragment.newInstance(CommonConstant.SERVER.TYPE_TRANSITION)
                }
                5 -> {
                    EffectListFragment.newInstance(CommonConstant.SERVER.TYPE_EFFECT)
                }
                6 -> {
                    EffectListFragment.newInstance(CommonConstant.SERVER.TYPE_FILTER)
                }
                7 -> {
                    EffectListFragment.newInstance(CommonConstant.SERVER.TYPE_BACKGROUND)
                }
                8 -> {
                    EffectListFragment.newInstance(CommonConstant.SERVER.TYPE_SUBTITLE)
                }
                else -> {
                    EffectListFragment.newInstance(CommonConstant.SERVER.TYPE_FONT)
                }
            }
        }
    }
    mVpEffectContent.currentItem = index
    mVpEffectContent.offscreenPageLimit = 10
    mTlEffectTabs.setupWithViewPager(mVpEffectContent)
}

EffectListFragment即使承载 ViewPager 的 Fragment,上述代码在构建其实例时做了分类讨论,目的是为了根据不同类型的 tab 透传相应的 type 值。

EffectListFragment 不得不先接受透传参数并继续传递到下一个层级:

class EffectListFragment : BaseMvpFragment{
    // 保存透传参数的变量
    private var mCurrentType: Int = -1
    override fun initConfig(savedInstanceState: Bundle?) {
        // 获取透传参数
        mCurrentType = arguments?.getInt(CommonConstant.EFFECTCENTER.TYPE) ?: 0
        selectHotTab()
        showEffectDetails()
    }

    override fun showEffectDetails() {
        mVpEffectDetails?.adapter = SimpleFragmentPagerAdapter(childFragmentManager).apply {
            mCount = 1
            createFragment = { position ->
                // 参数继续透传到下一个层级
                EffectDetailsFragment.newInstance(mCurrentType, CommonConstant.EFFECTCENTER.ORDER_HOT).also {
                    currentFragments[0] = it
                }
            }
        }
    }

    companion object {
        fun newInstance(type: Int): EffectListFragment {
            // 参数透传
            return EffectListFragment().apply {
                arguments = Bundle().apply { putInt(CommonConstant.EFFECTCENTER.TYPE, type) }
            }
        }
    }

最后接受并消费透传 type 的是 EffectDetailsFragment,即纵向滚动列表的承载页:

class EffectDetailsFragment : BaseMvpFragment<EffectDetailsContract.IView, EffectDetailsPresenter>() {
    // 保存透传参数的变量
    private var mCurrentType: Int = 0
    override fun initConfig(savedInstanceState: Bundle?) {
        // 接受透传参数
        mCurrentType = arguments?.getInt(CommonConstant.EFFECTCENTER.TYPE) ?: 0
        mOrder = arguments?.getInt(ORDER) ?: 0
        initDetailsContent()
    }

    override fun initEvent() {
        mTvNetworkRetry.setOnClickListener(this)
        mEffectDetailsAdapter.addOnItemClickListener(object : EffectDetailsAdapter.OnItemClickListener {
            override fun onItemClick(entity: EffectDataEntity?) {
                entity?.let {
                    // 消费透传参数,上报埋点
                    StudioReport.reportClickAlbumClick(it.type, it.id, mOrder, "all", mCurrentType)
                }
            }
        })
    }

    companion object {
        const val ORDER: String = "order"
        fun newInstance(type: Int, order: Int): EffectDetailsFragment {
            return EffectDetailsFragment().apply {
                // 接收透传
                arguments = Bundle().apply {
                    putInt(CommonConstant.EFFECTCENTER.TYPE, type)
                    putInt(ORDER, order)
                }
            }
        }
    }
}

整个界面层级以及参数传递路径如下:

Activity 中有一个 Fragment,而它内部又嵌套了一个 Fragment。

中间的 Fragment 很无辜,因为它并不需要消费 type 参数,而只是做一个快递员。

当前只有两层,如果层级再增多,因此而增加的复杂度和工作量让人难以接受。

向上查询

如果把上述传参的方式叫做 “自顶向下透传” 的话,下面要介绍的这个方案可以称为 “自底向上查询”。

自顶向下透传是容易实现的,因为父亲总是持有孩子的引用,向孩子注入参数轻而易举。

有没有一种方案可以实现反向的参数查询,即当孩子触发埋点事件时,逐级向上查询父亲生成的参数。

Android 中的控件是持有父亲的:

// android.view.View.java
public final ViewParent getParent() {
    return mParent;
}

通过一个循环不停地获取当前控件的父控件,就能从 View 树的叶子结点遍历到树根:

var viewParent: View?
do {
    viewParent = viewParent?.parent as? View
} while(viewParent != null)

对于 Activity 来说,树根就是 DecorView。对于 Fragment 来说,树根就是 onCreateView() 中创建的视图。

Fragment 最终会以一个 View 的形式嵌入到 Activity 的 View 树中。所以对于当个 Activity 来说,不管嵌套几层 Fragment,其视图结构最终都可以归为一棵 View 树。

如何让 Activity View 树中的每一个控件都能携带业务参数?

需要定义一个接口:

// 可跟踪的结点
interface TrackNode {
    fun fillTrackParams(): HashMap<String, String>?
}

为 View 新增一个扩展属性,让每个控件都持有一个 TrackNode:

var View.trackNode: TrackNode?
    get() = this.getTag(R.id.spm_id_tag) as? TrackNode
    set(value) {
        this.setTag(R.id.spm_id_tag, value)
    }

将携带参数的能力存放在 View.tag 中,这样任何控件都可以携带参数了。

让 Activity 携带参数体现为让其根视图 DecorView 携带参数:

// 在所有 Activity 的基类中实现 TrackNode,则所有 Activity 都具备了携带参数的能力
open class BaseActivity : AppCompatActivity(), TrackNode{
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // Activity 携带参数表现为其根视图携带参数
        window.decorView.rootView.trackNode = this
    }

    override fun fillTrackParams(): HashMap<String, String>? {
        return null
    }
}

同样地,Fragment 也有类似的实现:

open class BaseFragment : Fragment(), TrackNode {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // Fragment 携带参数表现为其根视图携带参数
        getView()?.trackNode = this
    }

    override fun fillTrackParams(): HashMap<String, String>? {
        return null
    }
}

这样一来,一个窗口中整个 View 树的任何一个结点都具备了携带参数的能力,从最顶层的 Activity,到其内部的 Fragment,再到任何一个控件。参数就不再需要自顶向下透传,而是可以自底向上查询:

fun View.getTrackNode(): HashMap<String, String> {
    val map = hashMapOf<String, String>()
    // 获取当前结点的参数
    trackNode?.fillTrackParams()?.also { map.putAll(it) }
    // 不断获取父亲以向上查询
    var viewParent = parent as? View
    do {
        // 查询父控件是否携带参数
        val info = viewParent?.trackNode?.fillTrackParams() 
        // 若父控件携带参数则将其拼接
        info?.also { map.putAll(it) }
        // 继续获取父控件
        viewParent = viewParent?.parent as? View
    } while (viewParent != null) // 直到回溯到了整个界面的根视图
    return map
}

为 View 自定义了一个扩展方法,该方法返回一个 Map,该 Map 中包含了从当前界面向上到树根整个链路中所有携带的参数集合。

重构透传

先在 Activity 层级将标签页的 type 拼接到 TrackNode 中,而不是作为参数传递给 EffectListFragment:

// EffectActivity.kt
override fun showEffectListContent(index: Int, from: String?) {
    mVpEffectContent.adapter = SimpleFragmentStatePagerAdapter(supportFragmentManager,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT).apply {
        mTitles = arrayOf("视频库", "音乐", "音效", "贴纸", "转场", "特效", "滤镜", "背景", "字幕", "字体")
        mCount = mTitles!!.size
        createFragment = { position ->
            when (position) {
                0 -> {
                    val fragment = RemoteCenterFragment.newInstance(-1,true,0,MaterialProtocol.SOURCE.MATERIAL_MARKET,1,from,0,0)
                    val paramsParser = DeepLinkParamsParser(compileDeepLinkParams())
                    (fragment as IDeepLinkPage).setDeepLinkParams(paramsParser.deeplinkParams)
                    fragment
                }
                1 -> MaterialMusicFragment.newInstance(from, mSelectedTabId, mSelectedModelId)
                2 -> MaterialAudioFragment.newInstance(mSelectedTabId, mSelectedModelId)
                // 可以无差别地构造 EffectListFragment 实例
                else -> EffectListFragment.newInstance()

            }
        }
    }
    mVpEffectContent.currentItem = index
    mVpEffectContent.offscreenPageLimit = 10
    mTlEffectTabs.setupWithViewPager(mVpEffectContent)
}
// 索引和常量的映射
private val tabMap = mapOf(
    3 to CommonConstant.SERVER.TYPE_VSTICKER,
    4 to CommonConstant.SERVER.TYPE_TRANSITION,
    5 to CommonConstant.SERVER.TYPE_EFFECT,
    6 to CommonConstant.SERVER.TYPE_FILTER,
    7 to CommonConstant.SERVER.TYPE_BACKGROUND,
    8 to CommonConstant.SERVER.TYPE_SUBTITLE,
    9 to CommonConstant.SERVER.TYPE_FONT,
)

// Activity 层级的参数拼接
override fun fillTrackParams(): HashMap<String, String>? {
    // 只拼接当前显示页的常量
    return hashMapOf("type" to tabMap[mVpEffectContent.currentItem].toString()
}

第二个层级的 Fragment 在构建实例时不再接受传参(更加单纯):

class EffectListFragment : BaseFragment() {
    companion object {
        // 没有参数传入的构造方法
        fun newInstance(): EffectListFragment = EffectListFragment()
    }
}

在最内层的 Fragment 消费参数:

class EffectDetailsFragment : BaseFragment(){
    // 向上查参         
    private val type: Int
        get() = view?.getTrackNode()?.getOrElse("type") { "" }?.safeToInt() ?: 0          
    override fun initEvent() {
        mEffectDetailsAdapter.addOnItemClickListener(object : EffectDetailsAdapter.OnItemClickListener {
            override fun onItemClick(entity: EffectDataEntity?) {
                // 消费参数进行埋点
                entity?.let {
                     ReportUtil.reportClick(it.id, type)
                }
            }
        })
    }

    companion object {
        // 没有type 传入的构造方法
        fun newInstance(): EffectDetailsFragment = EffectDetailsFragment()
    }
}

消费参数时不再通过上一个界面透传,而是通过自底向上的查询。

因为约定的参数是 HashMap<String, String> 类型的,而消费的参数是 Int 类型的所以得进行类型转换

如果强制的使用如下方式进行转换,则可能发生运行时崩溃,比如下面这个场景:

" " as Int

为了避免这类崩溃,有必要做一个统一处理:

fun String?.safeToInt(): Int = this?.let {
    try {
        Integer.parseInt(this)
    } catch (e: NumberFormatException) {
        e.printStackTrace()
        0
    }
} ?: 0

为 String 定义一个扩展方法,该方法返回 Int 值,在内部调用Integer.parseInt(this)将当前的 String 转换为 Int,并在其外层包裹了 try-catch 以捕获非数字字串转换异常的情况。

使用 Kotlin 中的预定义let()方法配合try-catch表达式以及 Evis 运算符,让这个方法的表达异常简洁。

其中let()的定义如下:

public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

let 也是一个扩展方法,被扩展对象是泛型,表示它可以被任何对象调用。let 接收一个 lambda,该 lambda 会将类型 T 变换为 类型 R,let 方法内部只是通过block(this)执行了该 lambda 并返回,遂 let 的返回值即是 lambda 的值(lambda 最后一条语句的值)。

从 let 的定义可以看出,它通常用于将一个对象转换为另一个对象。当前场景中它被用于将 String 转换为 Int。

String 转换为 Int 是可能抛异常的,遂用 try-catch 包裹之。Kotlin 中try-catch是一个表达式,它是有值的,等于每个分支最后一条语句的值。这个特性使得不必多声明一个局部变量:

int result = 0;
try {
    result = Integer.parseInt(str);
} catch (Exception e) {
    result = -1
}
return result;

所以整个 safeToInt() 的返回值是 let 的返回,而 let 的返回值是 try-catch 的返回值。

最后因为被扩展的对象是 String?,所以返回值是可空的,方法内部通过?:处理了这种情况。表达式1 ?: 表达式2意思是当表达式1为空时,执行表达式2。

适用场景

自底向上查询参数方案适用于同一窗口的任何层级之间的参数传递。

当在 Fragment 中向上查询时,要在onCreateView()之后,因为在此之前,Fragment 的视图层级还未生成,getView()会返回 null。

RecyclerView 中 ItemView 无法使用自底向上查询,因为ItemView.parent为空。

可以在 inflate ItemView 布局时将 attachToRoot 设置为 true:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val itemView = LayoutInflater.from(parent.context).inflate(
        R.layout.material_item_effect_details, 
        parent, 
        true)// 将 attachToRoot 设置为 true
    return ViewHolder(itemView)
}

这样 ItemView 的 parent 就不为空了,但是 ItemView 的 LayoutParam 就会被其父控件的 LayoutParam 覆盖,使得 ItemView 的布局样式不符合预期。

列表项参数透传解决方案

那列表相关的参数透传路径就一定得是 Activity -> Adapter -> ViewHolder ?

Adapter 的语义是完成数据到视图的转换。ViewHolder 的语义是描述如何构建表项视图及其交互。

如果将透传逻辑和表项的构建及交互逻辑耦合在一起,除了增加了透传参数的复杂度,还使得后者无法被独立复用。

更好的做法是将表项的曝光和点击事件上移到 Activity/Fragment 处理,为此新增了两个扩展法方法:

fun RecyclerView.setOnItemClickListener(listener: (View, Int, Float, Float) -> Boolean) {
    addOnItemTouchListener(object : RecyclerView.OnItemTouchListener {
        val gestureDetector = GestureDetector(context, object : GestureDetector.OnGestureListener {
            override fun onShowPress(e: MotionEvent?) {
            }

            override fun onSingleTapUp(e: MotionEvent?): Boolean {
                e?.let {
                    findChildViewUnder(it.x, it.y)?.let { child ->
                        val realX = if (child.left >= 0) it.x - child.left else it.x
                        val realY = if (child.top >= 0) it.y - child.top else it.y
                        return listener( child, getChildAdapterPosition(child), realX, realY )
                    }
                }
                return false
            }

            override fun onDown(e: MotionEvent?): Boolean {
                return false
            }

            override fun onFling( e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float ): Boolean {
                return false
            }

            override fun onScroll( e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float ): Boolean {
                return false
            }

            override fun onLongPress(e: MotionEvent?) {
            }
        })

        override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {

        }

        override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
            gestureDetector.onTouchEvent(e)
            return false
        }

        override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
        }
    })
}

通过判断触点坐标落在 RecyclerView 的哪个孩子上进行点击事件的回调。详细分析可以点击读源码长知识 | 更好的 RecyclerView 表项点击监听器

以及 RecyclerView 表项百分比曝光扩展方法:

fun RecyclerView.onItemVisibilityChange(percent: Float = 0.5f, block: (itemView: View, adapterIndex: Int, isVisible: Boolean) -> Unit) {
    val rect = Rect() // reuse rect object rather than recreate it everytime for a better performance
    val visibleAdapterIndexs = mutableSetOf<Int>()
    val scrollListener = object : OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)
            // iterate all children of RecyclerView to check whether it is visible
            for (i in 0 until childCount) {
                val child = getChildAt(i)
                val adapterIndex = getChildAdapterPosition(child)
                val childVisibleRect = rect.also { child.getLocalVisibleRect(it) }
                val visibleArea = childVisibleRect.let { it.height() * it.width() }
                val realArea = child.width * child.height
                if (visibleArea >= realArea * percent) {
                    if (visibleAdapterIndexs.add(adapterIndex)) {
                        block(child, adapterIndex, true)
                    }
                } else {
                    if (adapterIndex in visibleAdapterIndexs) {
                        block(child, adapterIndex, false)
                        visibleAdapterIndexs.remove(adapterIndex)
                    }
                }
            }
        }
    }
    addOnScrollListener(scrollListener)
    addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(v: View?) {
        }

        override fun onViewDetachedFromWindow(v: View?) {
            if (v == null || v !is RecyclerView) return
            if (ViewCompat.isAttachedToWindow(v)) {
                v.removeOnScrollListener(scrollListener)
            }
            removeOnAttachStateChangeListener(this)
        }
    })
}

通过监听列表滚动事件,并在其中遍历列表所有的孩子,同时计算每个孩子矩形区域在列表中展示的百分比判断其可见性,详细分析可以点击

总结

通过思路的转变,将“自顶向下透传参数”转变为“自顶向上查询参数”,降低了同一界面层级中各控件之间的耦合,使得每个控件都更加单纯。

推荐阅读

业务代码参数透传满天飞?(一)

业务代码参数透传满天飞?(二)

全网最优雅安卓控件可见性检测

全网最优雅安卓列表项可见性检测

页面曝光难点分析及应对方案

你的代码太啰嗦了 | 这么多对象名?

你的代码太啰嗦了 | 这么多方法调用?

作者:唐子玄
链接:https://juejin.cn/post/7165427216589783076

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?
  • …

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末优快云官方认证微信卡片免费领取↓↓↓

DMXAPI:面向业务的通用大模型调用协议 大模型API调用不是简单发请求,而是涉及鉴权、错误处理、上下文管理、参数适配等系统性工程问题。DMXAPI作为一种标准化协议层,将异构模型能力抽象为统一RESTful接口,实现跨模型、跨厂商、跨技术栈的语义兼容。其核心价值在于解耦业务逻辑与底层AI实现——开发者无需关注qwen3.7-max的reasoning_effort参数约束或DeepSeek的token计数差异,只需按预定义Schema发起调用。该协议已在CRM增强、销售日报自动生成、客服工单结构化等真实场景落地,显著降低AI集成门槛。本文聚焦DM 阅读详情

相关推荐

BK3431 BLE透传模块低功耗与OAD升级实战解析

BLE透传模块是物联网设备中常见的通信方案,通过UART与主控交互,实现数据无线收发。工程实践中,仅跑通Demo远远不够,真正影响产品落地的是低功耗设计和远程固件升级能力。低功耗需要从睡眠模式、广播间隔、连接参数、GPIO状态等多个维度系统调优,而OAD空中升级则依赖Bootloader与App分区、固件校验和断点续传机制。基于BK3431芯片的实战经验,全面解析串口透传、低功耗改造以及OAD升级的关键步骤与常见坑点,为开发者提供一条从能通到能用的完整路径。

weixin_42550597的博客 286

微服务参数透传实现

微服务参数透传实现

小何的博客 2080

IEEE Access投稿全流程:从LaTeX模板到Overleaf实战(附避坑指南)

本文详细解析了向IEEE Access期刊投稿的完整流程,重点介绍了如何使用LaTeX模板在Overleaf平台上高效撰写和格式化论文。内容涵盖模板选择、作者信息填写、公式图表排版、参考文献管理(BibTeX)以及投稿前的最终检查清单,旨在帮助研究者,尤其是LaTeX新手,规避常见技术问题,顺利完成符合期刊严格要求的稿件提交。

open4的博客 574

解惑-1 关于透传与透传参数

上次很巧合的听大佬提到了透传和透传参数。 原来使用个推的时候也有是否透传的选项,当时对于透传就只有一个模糊的概念 早上让大佬详细讲解了一波: 透传在个推的例子是:  A ->个推服务器 个推服务器->B 定义 A-个推服务器发送一个自定义对象Obj 如果A不设置透传,那么在个推->B的过程中可能传递经过个推服务器包装的对象,这个对象可能就不是obj 如果设置了透传,在

Jy_wangchou的博客 9948

word 2013 长篇文档排版案例教程

office 2013 制作长文格式模板

1万+

闲鱼虚拟商品自动发货神器XianYuAutoDeliveryX:从配置到对接大模型的完整指南

本文提供了一份关于闲鱼虚拟商品自动发货神器XianYuAutoDeliveryX的完整配置与实战指南。详细介绍了从环境搭建、账号安全配置到核心自动化流程的设置,并重点讲解了如何对接大语言模型API,实现智能对话回复,从而构建一套无人值守的闲鱼店铺自动化运营系统。

gray5的博客 1369

全链路参数透传

背景 现在的系统大多是基于SOA的分布式应用。跨服务调用的时候,存在线程上线文里的重要信息如traceId,租户id无法传递给下一个应用。 要解决的问题 内部系统通过RocketMQ交互时,traceId如何透传到消息消费应用 内部系统通过Dubbo调用时, traceId如何透传到服务提供方 解决方案 1 rocketMQ异步透传解决方案 发消息时,通过切面拦截 DefaultMQProducer.send 方法,将traceId从threadLocal写入Message的properties里面

bruce128的专栏 3913

Vue全局前置守卫的应用1——参数透传

前概:我们经常遇到需要将参数带在URL进行后台请求这样的需求,笔者在项目中遇到需要一直使用某个参数进行请求,以获得相应的数据,实现方式不外乎两种:1.将这个参数变成全局变量 (借助store) 2.使用路由守卫将参数一直进行传递。方案1的缺点在于如果在中间的页面打开时,此时这个参数就不存在了,导致页面异常,故采用了方案2实现。 实现方法: 你可以使用 router.beforeEach 注册一...

cas_nj的博客 2551

基于STM32和K210的自动驾驶小车设计

自动驾驶技术正在迅速发展,成为了汽车行业和智能交通领域的热点之一。随着科技的发展,STM32和K210这两款芯片为自动驾驶小车提供了优秀的硬件支持。本文我们将学习如何使用STM32和K210打造一个简单的自动驾驶小车。提示:以下是本篇文章正文内容,下面案例可供参考通过这篇文章,掌握了如何使用STM32和K210构建一个简单的自动驾驶小车。通过了解基本的电路设计,并编写了STM32和K210的代码。通过这个例子,可以更好地理解如何在嵌入式系统中使用STM32和K210,以及如何实现自动驾驶小车的简单功能。

Kyle_Chan_zMH的博客 2215

iOS逆向实战:绕过SSL Pinning抓取ChatGPT设备令牌

在移动应用安全与网络协议分析领域,中间人攻击是一种核心的流量拦截与分析技术。其原理是在客户端与服务器之间插入一个代理节点,通过伪造数字证书来解密和窥探HTTPS加密流量。这项技术的工程价值在于,它使开发者能够调试API、分析通信协议以及实现自动化集成。然而,许多高安全要求的应用(如金融、社交类App)会采用SSL Pinning(证书绑定)技术来抵御此类攻击,这为逆向工程带来了挑战。本文聚焦于一个具体的应用场景:如何针对启用了SSL Pinning的ChatGPT iOS客户端,通过特定的工具链(如Trol

weixin_28235889的博客 969

Android业务代码参数透传

项目中参数多级透传满天飞的情况很常见,增加了开发的复杂度、出错的可能、及维护的的难度。通过将“显式向后透传参数”转变为“隐式向前查询参数”完全避免了界面间的参数透传,使得各界面间更加耦合,参数更容易维护。同时通过 Kotlin 的抽象属性,扩展方法,对象声明、类型转换 as?这些语法糖将向前查询参数的复杂度隐藏起来,使得上层能以最简洁的方式查询参数。

金戈鐡馬 590

假新闻检测论文分享(17)CAFE(Cross-modal Ambiguity Learning for Multimodal Fake NewsDetection)

本文首先从信息论的角度对跨模态歧义性学习问题进行形式化描述,利用不同单模态特征之间的分布散度量化其歧义性;然后,提出了一种模糊度感知的多模态假新闻检测方法CAFE。CAFE通过自适应地聚合单模态特征和跨模态关联,即在跨模态歧义性较弱时依赖单模态特征,在跨模态歧义性较强时参考跨模态关联,从而提高假新闻检测精度。提出了跨模态歧义学习问题,这是多模态假新闻检测的一个关键挑战,并提出了一种基于KL散度的方法,通过估计其特征分布的散度来量化文本和图像之间的歧义。

weixin_41964296的博客 1724

为什么你的代码漏洞满天飞?看看这个

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33872660的博客 328

Autosar进阶专栏索引

Autosar进阶专栏索引

赞的博客 3534

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

低代码平台能承载复杂业务吗?我用接口引擎验证了一下

坑1:数组类型判断前端传来的数组,在 V8.Param 里拿到的是对象,但 `Array.isArray()` 返回 `false`。官方建议用 `typeof == ‘object’` 来判断。这个坑比较隐蔽,因为 JavaScript 里数组确实是对象的一种,但 `isArray` 的行为确实让人困惑。坑2:事务回滚的自动机制前面说了,`Code:1` 自动提交,其他自动回滚。但如果你在代码里先查了数据再更新,一定要确保所有分支都正确返回。

开源低代码平台-Microi吾码-框架C#.NET8 + Vue3 552

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

java每日精进 01.08【吃透 5 大创建型设计模式!从业务场景到代码落地,新手也能秒懂】

🔥 写代码还在无脑 new 对象?学会这 5 种创建型模式,让你的代码优雅 10 倍,扩展性拉满!

weixin_51721783的博客 1331

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

SpringBoot 全局异常处理 + 参数校验,企业级规范写法(代码直接复制)

在 SpringBoot 前后端分离项目里,这两个东西几乎是必写基础:1.接口参数乱传,直接报错到前端2.异常满天飞,前端各种无法解析3.每个接口都写 try-catch,代码又臭又长4.参数校验逻辑重复,维护成本极高今天给你一套企业级标准方案:统一返回 + 全局异常处理 + 参数校验,一次写完,全项目通用,代码直接复制就能用。这套代码是SpringBoot 项目标配:所有异常统一处理前端只需要解析一种格式代码干净、规范、易维护完全符合企业真实开发标准。

lhyACl的博客 560

AI Agent 概念满天飞,但真正的工业智能体已经在中国工厂跑起来了

这篇文章揭示了AI Agent技术在工业领域的落地现状与挑战。尽管"Agentic AI"概念火热,但实际制造业面临老系统难改造、数据孤岛、经验难数字化等痛点。文章列举了美的、晶圆厂、裕同包装等成功案例,指出"RPA+LLM数字员工"模式(如实在Agent)因能兼容老旧系统、低成本部署而成为最可行的路径。这类方案通过智能屏幕理解、流程大模型和决策引擎,在不改造原有IT架构的前提下实现跨系统协同,已在多家工厂实现业务自动化和效率提升。文章认为,工业智能体的真正门槛不在于模型先进性,而在于能否适配现有生产系统,

记录Agent的使用。 381

第5章:FastAPI 依赖注入入门——从公共参数到业务服务

本文针对FastAPI项目中存在的样板代码重复问题,提出采用依赖注入(Dependency Injection)进行优化。通过分析订单系统中重复出现的用户认证、数据库连接和分页参数等样板代码,揭示了其带来的修改困难、测试复杂、职责混乱等问题。 文章采用师徒对话形式,生动阐述了依赖注入的核心优势: 自动解析请求参数 实现依赖缓存 支持嵌套依赖 重点介绍了yield依赖的资源生命周期管理机制,并对比了依赖注入与中间件的区别(接口级粒度vs应用级粒度)。

davidwang456的专栏 319

浅谈团队代码风格/结构一致性

我所在的团队,一部分职责是开发维护直播业务中主播直播售卖商品的时候的CPS佣金体系,完成消费者点击商品的行为跟踪,以及下单后把订单归因到主播,以及后续的计费、扣佣过程。轮子是2022.06开始造的,作为技术演进的项目,推进方式上主要是两个点:新的服务、应用直接采用规范的代码模式,老的应用和服务,随着业务需求的演进和人力情况逐步迭代。当然也可以做复杂的编排。珠玉在前,所以去解决前面提到的问题,很好的思路就是在应用中以某个框架形式的代码约束,统一代码分层风格,并逐步推广到域内的多个应用,实现规模化的效果收益。

淘系技术 1372

《代码之丑》学习总结

在我看来命名做到不言自喻就可,让后来者看到你的命名就大致知道这段代码的大致详情即可。

逾越的博客 5906

别再被VO、BO、PO、DTO、DO绕晕!今天用一篇文章把它们讲透

/ DTO对象定义// 创建用户请求DTO@NotBlank(message = "用户名不能为空")@Size(min = 3, max = 20, message = "用户名长度必须在3-20之间")@NotBlank(message = "邮箱不能为空")@Email(message = "邮箱格式不正确")@NotBlank(message = "密码不能为空")@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")

我爱娃哈哈的专栏 196

Vue组件通信本质:从Props/Events到Pinia的分层协作协议

Vue组件通信不是简单的数据传递,而是围绕状态归属、变更意图、副作用隔离和响应可追溯构建的一套分层协作协议。其底层原理基于响应式系统与事件驱动模型的协同——Props定义不可变输入契约,Events表达语义化业务动作,Provide/Inject实现作用域隔离,Pinia则承担跨组件状态事务协调。这种设计赋予Vue应用高可预测性、强可调试性与清晰边界控制能力,广泛应用于中后台系统、微前端集成、跨团队组件库等工程场景。本文深入剖析Vue 3生态下各类通信模式的本质分工与实战约束,尤其聚焦Props类型校验、e

weixin_33423874的博客 302

Python结构化日志替代print()的工程实践指南

结构化日志是一种将日志输出转化为机器可解析键值对(如JSON)的技术范式,其核心原理在于解耦日志内容与格式、统一上下文注入、支持字段级查询与链路追踪。相比传统print()或基础logging,它显著提升可观测性、降低SRE排查成本、增强CI/CD稳定性,并天然适配ELK/Loki/Prometheus等现代日志与监控体系。在微服务、高并发及生产级交付场景中,结构化日志已成为保障系统可维护性与故障响应时效性的关键技术基础设施。本文聚焦Python生态,详解如何以最小侵入方式实现从print()到produc

weixin_30819085的博客 413

成为不可取代的程序员的编码方式

3.如果你是写Java那就太好了,能用Map,JSONObject前往不要定义实体,一个复杂的请求,从前端到后端,再到调用内部方法,外部依赖(越多越好),所有的参数传递或者返回值全部定义为Map,只要你能记住结构那就都不是问题,这种情况,最好还不要写文档,保持神秘,如果在核心链路上那就真该恭喜你了,你懂得!2.方法越长越好,不要轻易拆方法,当一个方法超过500行之后,而且没有任何注释,没有人能轻易搞定这段代码,如果还是比较核心的功能,那么恭喜你,你的不可替代性就大大增强!

u010830473的博客 800
上一篇: 零基础转行车载工程师,薪资年包40k,就靠这份Android车载开发指南
下一篇: 如何阅读源码?这篇《Android开发源码精编解析》助你进阶提升
Agent学习路线
博客等级 码龄6年 1万+粉丝 1902原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值