【实时巴士源码】【源码接收确认】【研读netty源码】spinner源码

来源:海洋cms源码 手机

1.spinnerԴ??
2.autojs修改下拉框高度
3.android中spinner.setOnItemClickListener为什么不可以用啊?
4.Go并发编程:goroutine,channel和sync详解
5.monorepo多包管理架构实践

spinner源码

spinnerԴ??

       progress库安装和介绍

       progress是Python第三方库,用于在控制台显示进度条,安装方法为在终端执行pip命令。

       progress实现进度条

       使用progress库实现进度条非常简便,仅需从库中的bar.py模块导入Bar类,实例化后进行业务处理并在循环中调用next()方法,实时巴士源码处理完成后调用finish()方法结束进度条。

       Bar类的主要参数包括:message、width、max、suffix、fill、empty_fill、bar_prefix、bar_suffix和color,用于配置进度条显示信息、样式和颜色。

       PyCharm进度条显示问题解决

       在PyCharm中运行进度条代码时,若未显示进度条效果,可通过以下步骤解决:打开Run配置页面,确保Emulate terminal in output console选项被勾选,重新运行代码。

       在PyCharm中运行进度条时,可能会出现光标输出的异常现象。解决方法为:在Bar类所在的源码接收确认源文件bar.py中,找到继承自Progress类的Bar类,进入progress库的__init__.py文件,修改SHOW_CURSOR变量为空字符串,以阻止光标显示。

       进度条代码的另外两种写法

       1. 上下文管理器:使用with...as...上下文管理器编写进度条,进度条完成后自动结束。

       2. 使用iter()方法:简化迭代器操作,自动调用next()方法。

       实现更多种类的进度条

       1. Bar系列:Bar类及其子类如ChargingBar、FillingSquaresBar等,通过修改suffix、fill、empty_fill等参数,实现不同样式进度条。

       2. Spinner系列:实现的Spinner、PieSpinner等类提供不同动画效果的进度条。

       3.Counter系列:Counter、Countdown等类用于显示计数器或倒计时进度。

       汇总:将上述所有进度条类型整合至单个代码中,可实现全面的进度条功能。

autojs修改下拉框高度

       在自动脚本开发中,有时我们可能需要对UI组件进行微调以适应特定需求。例如,当使用AutoJS处理下拉框时,研读netty源码我们可能会遇到下拉框过长的问题。本文将探讨如何解决AutoJS中的下拉框高度修改问题。

       在AutoJS中,我们首先需要了解下拉框的两种模式:弹框模式(dialog)和下拉框模式(dropdown)。弹框模式下,下拉框的高度不能通过直接设置高度来改变,因为这会引发错误。相比之下,下拉框模式提供了更多的自定义选项,包括设置高度。

       要修改下拉框的高度,我们通常会尝试反射访问下拉框的实例,然后修改相关属性。然而,这一方法在AutoJS中可能无法正常工作。这是因为AutoJS是基于Android SDK构建的,但并非完全等同于原生的Android环境。在AutoJS中,`spinner`组件是对其原生Android组件的封装,这导致了某些行为上的差异。

       深入AutoJS的源码,我们发现`spinner`的下拉框实例`mPopup`实际上属于`androidx.appcompat.widget.AppCompatSpinner`类。这解释了为什么我们不能直接在AutoJS中修改`mPopup`的高度。`AppCompatSpinner`是斯凯源码`Spinner`的子类,它提供了更丰富的样式和行为。在AutoJS中,由于封装了原生的组件,我们实际上不能直接访问或修改`mPopup`实例。

       通过创建一个简单的测试环境来验证这一点,我们定义了一个父类和一个子类,并尝试访问`name`字段。在子类中,当`name`字段被定义为私有时,我们无法访问它。然而,如果我们将它改为公有,子类可以访问到父类的`name`值。这表明,当父子类具有同名字段时,子类会优先访问自己的字段,除非被覆盖。

       基于上述发现,我们可以通过直接修改`AppCompatSpinner`类的`mPopup`属性来改变下拉框的高度。在AutoJS环境中,这需要通过反射来实现。一旦成功修改了高度,下拉框的显示效果将得到优化,不再显得过长。分销测算源码

       这个解决方案不仅解决了下拉框高度调整的问题,还为我们提供了一个重要的学习点:在将Android代码转换为AutoJS时,需要充分考虑AutoJS与Android环境之间的差异。这有助于我们在未来避免类似的陷阱,更好地利用脚本语言进行UI操作的定制。

       通过实践与理解,我们可以更灵活地应用AutoJS,解决各种复杂的UI调整需求。记住,每次遇到问题时,首先尝试从基础知识出发,理解背后的原因,而不是盲目依赖外部资源。这样,我们不仅能够解决当前的问题,还能构建更坚实的编程技能基础。

android中spinner.setOnItemClickListener为什么不可以用啊?

       æºä»£ç ä¸­æ–‡æ¡£æç¤ºæ˜Žç¡®å†™ç€A spinner does not support item click events. Calling this method will raise an exception. Overrides: setOnItemClickListener(...) in AdapterView,意思就是说spinner不支持OnItemClickListener,因为它的实现是在AdapterView中来重写的。你可以用setOnItemSelectedListener 来代替。

Go并发编程:goroutine,channel和sync详解

       ä¼˜é›…的并发编程范式,完善的并发支持,出色的并发性能是Go语言区别于其他语言的一大特色。

       åœ¨å½“今这个多核时代,并发编程的意义不言而喻。使用Go开发并发程序,操作起来非常简单,语言级别提供关键字go用于启动协程,并且在同一台机器上可以启动成千上万个协程。

       ä¸‹é¢å°±æ¥è¯¦ç»†ä»‹ç»ã€‚

goroutine

       Go语言的并发执行体称为goroutine,使用关键词go来启动一个goroutine。

       go关键词后面必须跟一个函数,可以是有名函数,也可以是无名函数,函数的返回值会被忽略。

       go的执行是非阻塞的。

       å…ˆæ¥çœ‹ä¸€ä¸ªä¾‹å­ï¼š

packagemainimport("fmt""time")funcmain(){ gospinner(*time.Millisecond)constn=fibN:=fib(n)fmt.Printf("\rFibonacci(%d)=%d\n",n,fibN)//Fibonacci()=}funcspinner(delaytime.Duration){ for{ for_,r:=range`-\|/`{ fmt.Printf("\r%c",r)time.Sleep(delay)}}}funcfib(xint)int{ ifx<2{ returnx}returnfib(x-1)+fib(x-2)}

       ä»Žæ‰§è¡Œç»“果来看,成功计算出了斐波那契数列的值,说明程序在spinner处并没有阻塞,而且spinner函数还一直在屏幕上打印提示字符,说明程序正在执行。

       å½“计算完斐波那契数列的值,main函数打印结果并退出,spinner也跟着退出。

       å†æ¥çœ‹ä¸€ä¸ªä¾‹å­ï¼Œå¾ªçŽ¯æ‰§è¡Œæ¬¡ï¼Œæ‰“印两个数的和:

packagemainimport"fmt"funcAdd(x,yint){ z:=x+yfmt.Println(z)}funcmain(){ fori:=0;i<;i++{ goAdd(i,i)}}

       æœ‰é—®é¢˜äº†ï¼Œå±å¹•ä¸Šä»€ä¹ˆéƒ½æ²¡æœ‰ï¼Œä¸ºä»€ä¹ˆå‘¢ï¼Ÿ

       è¿™å°±è¦çœ‹Go程序的执行机制了。当一个程序启动时,只有一个goroutine来调用main函数,称为主goroutine。新的goroutine通过go关键词创建,然后并发执行。当main函数返回时,不会等待其他goroutine执行完,而是直接暴力结束所有goroutine。

       é‚£æœ‰æ²¡æœ‰åŠžæ³•è§£å†³å‘¢ï¼Ÿå½“然是有的,请往下看。

channel

       ä¸€èˆ¬å†™å¤šè¿›ç¨‹ç¨‹åºæ—¶ï¼Œéƒ½ä¼šé‡åˆ°ä¸€ä¸ªé—®é¢˜ï¼šè¿›ç¨‹é—´é€šä¿¡ã€‚常见的通信方式有信号,共享内存等。goroutine之间的通信机制是通道channel。

       ä½¿ç”¨make创建通道:

ch:=make(chanint)//ch的类型是chanint

       é€šé“支持三个主要操作:send,receive和close。

ch<-x//发送x=<-ch//接收<-ch//接收,丢弃结果close(ch)//关闭无缓冲channel

       make函数接受两个参数,第二个参数是可选参数,表示通道容量。不传或者传0表示创建了一个无缓冲通道。

       æ— ç¼“冲通道上的发送操作将会阻塞,直到另一个goroutine在对应的通道上执行接收操作。相反,如果接收先执行,那么接收goroutine将会阻塞,直到另一个goroutine在对应通道上执行发送。

       æ‰€ä»¥ï¼Œæ— ç¼“冲通道是一种同步通道。

       ä¸‹é¢æˆ‘们使用无缓冲通道把上面例子中出现的问题解决一下。

packagemainimport"fmt"funcAdd(x,yint,chchanint){ z:=x+ych<-z}funcmain(){ ch:=make(chanint)fori:=0;i<;i++{ goAdd(i,i,ch)}fori:=0;i<;i++{ fmt.Println(<-ch)}}

       å¯ä»¥æ­£å¸¸è¾“出结果。

       ä¸»goroutine会阻塞,直到读取到通道中的值,程序继续执行,最后退出。

缓冲channel

       åˆ›å»ºä¸€ä¸ªå®¹é‡æ˜¯5的缓冲通道:

ch:=make(chanint,5)

       ç¼“冲通道的发送操作在通道尾部插入一个元素,接收操作从通道的头部移除一个元素。如果通道满了,发送会阻塞,直到另一个goroutine执行接收。相反,如果通道是空的,接收会阻塞,直到另一个goroutine执行发送。

       æœ‰æ²¡æœ‰æ„Ÿè§‰ï¼Œå…¶å®žç¼“冲通道和队列一样,把操作都解耦了。

单向channel

       ç±»åž‹chan<-int是一个只能发送的通道,类型<-chanint是一个只能接收的通道。

       ä»»ä½•åŒå‘通道都可以用作单向通道,但反过来不行。

       è¿˜æœ‰ä¸€ç‚¹éœ€è¦æ³¨æ„ï¼Œclose只能用在发送通道上,如果用在接收通道会报错。

       çœ‹ä¸€ä¸ªå•å‘通道的例子:

packagemainimport"fmt"funccounter(outchan<-int){ forx:=0;x<;x++{ out<-x}close(out)}funcsquarer(outchan<-int,in<-chanint){ forv:=rangein{ out<-v*v}close(out)}funcprinter(in<-chanint){ forv:=rangein{ fmt.Println(v)}}funcmain(){ n:=make(chanint)s:=make(chanint)gocounter(n)gosquarer(s,n)printer(s)}sync

       sync包提供了两种锁类型:sync.Mutex和sync.RWMutex,前者是互斥锁,后者是读写锁。

       å½“一个goroutine获取了Mutex后,其他goroutine不管读写,只能等待,直到锁被释放。

packagemainimport("fmt""sync""time")funcmain(){ varmutexsync.Mutexwg:=sync.WaitGroup{ }//主goroutine先获取锁fmt.Println("Locking(G0)")mutex.Lock()fmt.Println("locked(G0)")wg.Add(3)fori:=1;i<4;i++{ gofunc(iint){ //由于主goroutine先获取锁,程序开始5秒会阻塞在这里fmt.Printf("Locking(G%d)\n",i)mutex.Lock()fmt.Printf("locked(G%d)\n",i)time.Sleep(time.Second*2)mutex.Unlock()fmt.Printf("unlocked(G%d)\n",i)wg.Done()}(i)}//主goroutine5秒后释放锁time.Sleep(time.Second*5)fmt.Println("readyunlock(G0)")mutex.Unlock()fmt.Println("unlocked(G0)")wg.Wait()}

       RWMutex属于经典的单写多读模型,当读锁被占用时,会阻止写,但不阻止读。而写锁会阻止写和读。

packagemainimport("fmt""sync""time")funcmain(){ varrwMutexsync.RWMutexwg:=sync.WaitGroup{ }Data:=0wg.Add()fori:=0;i<;i++{ gofunc(tint){ //第一次运行后,写解锁。//循环到第二次时,读锁定后,goroutine没有阻塞,同时读成功。fmt.Println("Locking")rwMutex.RLock()deferrwMutex.RUnlock()fmt.Printf("Readdata:%v\n",Data)wg.Done()time.Sleep(2*time.Second)}(i)gofunc(tint){ //写锁定下是需要解锁后才能写的rwMutex.Lock()deferrwMutex.Unlock()Data+=tfmt.Printf("WriteData:%v%d\n",Data,t)wg.Done()time.Sleep(2*time.Second)}(i)}wg.Wait()}总结

       å¹¶å‘编程算是Go的特色,也是核心功能之一了,涉及的知识点其实是非常多的,本文也只是起到一个抛砖引玉的作用而已。

       æœ¬æ–‡å¼€å§‹ä»‹ç»äº†goroutine的简单用法,然后引出了通道的概念。

       é€šé“有三种:

       æ— ç¼“冲通道

       ç¼“冲通道

       å•å‘通道

       æœ€åŽä»‹ç»äº†Go中的锁机制,分别是sync包提供的sync.Mutex(互斥锁)和sync.RWMutex(读写锁)。

       goroutine博大精深,后面的坑还是要慢慢踩的。

       æ–‡ç« ä¸­çš„脑图和源码都上传到了GitHub,有需要的同学可自行下载。

       åœ°å€ï¼šgithub.com/yongxinz/gopher/tree/main/sc

       ä½œè€…:yongxinz

monorepo多包管理架构实践

       平时开发项目时,我们会积累和沉淀许多通用的组件、方法等,它们通常适用于多个项目。但由于不同的项目具有不同的代码库,这些通用组件或方法不能简单地复制粘贴,这也不利于统一维护和更新。

       如果我们要在一个代码库中维护一套通用的组件、方法、模板等可能服务于其他项目的 package,这些 package 可以独立发布且互不干扰,我们应该如何操作呢?

       最初,我并不知道该如何进行调研,于是翻看了一些优秀库(如 vant、element-plus)的底层架构,大致了解了一些情况。结合自己的理解和认知,我使用 pnpm + vite 搭建了一个可供参考使用的 monorepo 多包管理框架。

       为何选择 pnpm:

       pnpm 相较于 npm、yarn 可以有效节省磁盘空间并提升安装速度。

       pnpm 内置了对单个代码仓库包含多个软件包的支持,是 monorepo 架构模式的不二之选。

       原理概括:pnpm 将所有的依赖包的层次提升到同一层次并安装存放在一个统一的地方(.pnpm),创建非扁平的 node_modules 目录,包之间的依赖关系可以很清楚地看到,一个包的依赖包以符号链接的形式存在,符号链接会指向当前包的原始位置,即所在的 .pnpm 的顶层位置。

       为何选择 vite:

       vite 随着 vue 3.x 而来,极大改善了开发体验,冷启动让人惊艳,再也不用担心项目越来越大启动速度越来越慢了。

       平时开发项目基本是 vue 3.x + vite + typescript 的组合,vite 有一个库模式的构建方式,这意味着我们在开发项目和通用包时,都可以使用 vite,而无需引入第三方构建工具,使用 vite 无疑具有天然优势。

       这里存在一个问题,现在普遍使用 typescript 来开发项目,开发的通用库在发布构建时也需要生成相应的 .d.ts 类型声明文件,vite 的库模式构建是不会自动生成 .d.ts 文件的,为了能自动生成类型声明文件我需要用到 tsc 和 vue-tsc,这里先简单提下,后面会用到。

       基本骨架:

       包的开发阶段可能需要在一个包中引用另一个包以方便调试,单包开发的话可能需要用 npm link 辅助调试,比较麻烦,多包模式下可以简单地使用 workspace。

       组件库:

       1. 我们需要在项目的根目录下执行 scripts 命令,以执行对应包中的 scripts 命令,需要用到 pnpm --dir 定位到需要执行命令的目录。

       注意:为了更加细粒度地控制构建的过程,这里封装了构建的脚手架命令 vswift-cli build,简单直接点的话可以直接用 "build": "rm -r ./dist && vite build && vue-tsc"。

       2. 组件库中有独立的 package.json、tsconfig.json、vite.config.ts 配置,package.json 中有几个关键配置:files、main、module、types、exports、publishConfig。

       files - 发布的包所包含的内容,默认会带上 package.json

       main - require 的入口文件,如 dist/index.umd.cjs

       module - import 的入口文件,如 dist/index.js

       types - 类型声明的入口文件,如 dist/types/index.d.ts

       exports - 定义导出模块的路径别名

       publishConfig - 发布配置

       3. tsconfig.json 主要是配置 typescript 的作用范围 include,以及类型声明文件的编译输出。因为是组件库,所以会有 .vue 文件,vue-tsc 可以生成对应的 .vue.d.ts 类型声明文件。

       4. vite.config.ts 是库模式 build 的配置,基本配置如下:

       5. 最终输出

       原始的文件目录

       打包输出的文件目录

       组件库打包可能存在的问题

       解决方法:引入 vite-plugin-css-injected-by-js 插件解决。

       以 element-plus 开发场景为例,如果开发的组件库是在 element-plus 组件的基础上开发的,打包后用到的样式会被提取到一个通用的 .css 文件中,这其中包括用到的 element-plus 根(:root)样式变量,这可能会覆盖掉项目中的 :root 样式变量。

       解决方法:一个不错的解决方法是根据组件库的名称自定义 element-plus 命名空间,如何自定义命名空间,element-plus 官方文档有详细说明,这里不做赘述。这里有一点需要注意下,我们是打包自己的组件库,命名空间的设置不能是在 App.vue 根组件中了,而在你需要打包的组件中。

       方法库:

       不同于组件库是以 .vue 文件为主的视觉交互,通用方法中主要是以 .ts 文件为主的通用逻辑方法等,所以它和组件库最大的区别在于生成类型声明文件的工具,组件库是用 vue-tsc,通用方法库则是用 tsc,可参见上面的 vswift-cli build 脚本构建命令,简单直接点的话可以直接用 "build": "rm -r ./dist && vite build && tsc"。

       脚手架:

       封装自己的脚手架,有 3 个关键要点:bin 配置、files 配置、cli 顶部声明。

       脚手架 package 基本结构预览 ------------>

       这里解释下 "build": "pnpm clean && tsc && scp -r ../vswift-templates/src/templates ./dist" 的含义-----------> “pnpm clean && tsc” 无需多说,“scp -r ../vswift-templates/src/templates ./dist” 是将 vswift-templates/src/templates 中维护的通用模板拷贝到打包输出目录 dist 中,并随着 @vswift/cli 包一起发布,使用脚手架命令快速创建基础页面的模板将从 “dist/templates” 中获取。

       name - 发布后的包名,全局安装:npm install @vswift/cli -g

       bin.vswift-cli - 全局安装 @vswift/cli 后,即可在命令行使用 vswift-cli

       files - 发布的文件别漏掉 bin.js

       bin.js - 顶部需加 #!/usr/bin/env node 声明

       注意:脚手架不需要 vite.config.ts 配置,只需在 tsconfig.json 中稍加区别修改,执行 tsc 使用。

       outDir - 编译后的输出目录

       declaration - 执行 tsc 时输出 .d.ts 声明文件

       declarationDir - .d.ts 声明文件的输出目录

       最终输出目录 ----------------->

       这里推荐几个开发脚手架时常用的 package -------------------->

       commander - Node.js 命令行插件,可以格式化输出友好的命令符提示

       nanospinner - Node.js 终端微调器,进度、成功、失败等状态友好展示,也可用 ora 代替

       consola - 用于 Node.js 和浏览器的优雅控制台记录器

       chalk - 终端样式粉笔,着色高亮

       vswift-cli build 构建命令 ------------------>

       vswift-cli create 根据模板创建基础页面命令 --------------------->

       vswift-cli dev 启动组件库预览调试命令 -------------------->

       cli 命令行逻辑 --------------->

       自动生成的 vswift-cli 命令行 help ----------------------->

       发布包:

       每个 package 的 package.json 中都有 release 命令,用于发布当前 package,package 会在发布前先 build 操作生成 dist 打包目录,最终发布的内容为 package.json 中 files 配置目录文件,当前 package 的 package.json 会被自动添加进去。

       这里使用了 release-it 插件,以方便快速地进行发布。

       还需注意,package.json 中要有相应的发布配置 --------------->

       架构源码参考:

文章所属分类:焦点频道,点击进入>>