首页 文章 精选 留言 我的

精选列表

搜索[官方镜像],共10000篇文章
优秀的个人博客,低调大师

macOS 官方共三年更新支持,但你知道苹果给固件提供多久的支持吗?

众所周知,Apple 支持每个版本的 macOS 整整一年的更新,然后再提供两年的安全更新。但它对每个型号的固件支持多长时间?现在更新 Mac 固件的唯一方法是安装 macOS 更新,这会如何影响支持期?本文试图回答这些问题,并在此过程中揭开这长达十多年的谜团。 数据 Apple 不会发布任何有关固件版本或更新的信息,甚至很少在安全更新的发布说明中提及它们。幸运的是,自从七年前 High Sierra 发布以来,我一直在跟踪每种型号的固件版本,因此我有自己的记录,这些记录来自 macOS 更新中包含的版本。我将这些记录与 Ian Page 的Mactracker 数据库中给出的型号推出和停产日期进行了匹配,并在此总结了结果。 更新的工作原理 每次 macOS 更新都可能带来固件更新,尽管在支持的第一年,纯安全补丁带来的更新往往较少。通常同时发布的三个 macOS 更新中的固件更新都是相同的。因此,最近更新到 14.6 带来的更新与 13.6.8 和 12.7.5 中的更新相同,适用于各自支持的型号,但每次更新只会安装其支持的型号的更新。借助示例,这一点变得更加清晰,这些示例也揭示了这些更新的内在奥秘。 2020 年 7 月 15 日,主要更新带来了 macOS 10.15.6,以及针对 macOS 10.13 和 10.14 的安全更新 (SU)。其中包括以下 EFI 固件版本: 适用于 iMac12,1 版本 87.0.0.0.0(2019 年 6 月 14 日) 适用于 iMac13,1 版本 292.0.0.0.0,发布日期:2020 年 6 月 10 日 适用于 MacBookPro8,1 版本 87.0.0.0.0(2019 年 6 月 13 日) 适用于 MacBookPro9,1 版本 233.0.0.0.0,发布日期为 2020 年 6 月 10 日。 这两个适用于 iMac12,1 和 MacBookPro8,1 的固件版本均为 2019 年,当时已经有一年的历史了,因为 Apple 已于 2019 年 6 月停止为这两款型号发布新固件版本。但是,如果 iMac13,1 和 MacBookPro9,1 型号安装了 macOS 10.15.6 或任一安全更新,则会收到新版本的固件。 一年后,即 2021 年 7 月 21 日,Apple 发布了 macOS 11.5 更新,并发布了 Mojave SU 2021-005。由于 iMac12,1 和 MacBookPro8,1 不再能够运行受支持的 macOS 版本,因此它们都没有固件更新,只能运行 2019 年 6 月的版本。随后,两款较新的型号进行了以下更新: 适用于 iMac13,1 版本 422.0.0.0.0,2021 年 6 月 4 日 适用于 MacBookPro9,1 版本 422.0.0.0.0,发布日期:2021 年 6 月 4 日。 又过了一年,即 2022 年 7 月 20 日,这两款型号仍可运行受支持的 macOS,并在 Catalina SU 2022-005 中进行了以下固件更新: 适用于 iMac13,1 版本 429.0.0.0.0,发布日期:2022 年 3 月 18 日 适用于 MacBookPro9,1 版本 429.0.0.0.0,发布日期:2022 年 3 月 18 日。 但这些并不是该 SU 中的新功能,因为那时这两种型号的固件更新已经停止,而在 2023 年 5 月 18 日的 Big Sur 11.7.7 中,这两种型号都没有任何可用的固件,因为它们不再受仍在接收更新的 macOS 版本的支持。 这个例子揭示了一个鲜明的事实:对于相隔一年多发布的 ​​iMac 和 MacBook Pro 的连续型号,上次发布的固件更新却相隔了近三年的时间: 对于 iMac12,1 最新发布于 2019 年 6 月,对于 iMac13,1 最新发布于 2022 年 3 月 对于 MacBookPro8,1 来说,最新发布于 2019 年 6 月,对于 MacBookPro9,1 来说,最新发布于 2022 年 3 月。 多久? 因此,我收集了 2009 年 10 月至今推出的 40 款未配备 T2 芯片的英特尔 Mac 的数据,每款产品显然都已通过了最终固件更新。这不包括目前仍在接收固件更新的少数型号。 此图表按型号推出日期显示了每个型号的最后一次固件更新日期。2012 年之前推出的大量 Mac 于 2019 年 6 月收到了最后一次固件更新,之后近两年的时间里,所有后续型号都收到了进一步的固件更新,之后的下一批旧型号(这次是 2012-13 年推出的)才收到了最终更新。右上角可见一个异常值,即 2019 年 3 月推出的 iMac19,1,但似乎在 2024 年 2 月进行了最后一次更新,非常早。虽然此后没有收到任何固件更新,但将来可能会收到更多固件更新。 此图表显示了该型号推出之日起固件支持的总长度(以年为单位)。有三个不同的组: 2012 年之前的模型,在左侧形成一条陡峭的线,支持时间从不到 8 年到近 10 年不等; 更新的模型,形成不太密集的散点,支持时间从不到 7.5 年到近 10 年; 右下角是 iMac19,1 异常值,其支持时间极短,约为 5 年。 这是同一张图表,但叠加了标签,标明了每种型号的名称。型号范围(例如 iMac)和支持期限之间似乎没有任何关联。 因此,对于自 2009 年以来推出的大多数不带 T2 芯片的英特尔型号,固件更新支持已延长至推出以来至少 8 年。由于型号的推出和停产之间的时间差异很大,因此在以停产日期表示时,分散性较大。 差距 有几个可能的原因可以解释 2012 年之前推出的 Mac 与最近推出的 Mac 之间的差异。这些包括: 2011-12 年推出的 Mac 电脑从 Sandy Bridge 过渡到 Ivy Bridge; 在推出 Apple 硅片型号期间,Intel Mac 预计会保持一段稳定期; 苹果选择在 Covid 大流行期间不停止固件支持,尽管我不记得曾经明确表示过这一点; 苹果固件支持政策的任意改变。 我赞成不再使用 Sandy Bridge,因为众所周知,Sandy Bridge 存在一些问题,可能导致固件支持比预期更早地结束。 值得注意的是,这种差距并不意味着在此期间没有发布固件更新,而只是意味着在此期间仍在更新的型号会继续更新,并且不会终止任何更新。 T2 和 Apple 芯片 这些较新的型号从 2017 年开始推出,完全改变了固件更新。所有配备 T2 芯片的 Mac 都会收到看似相同的固件更新。尽管 Mac 仍受 macOS 更新支持,但后续更新仍被放弃(在某些情况下会发生这种情况),但 T2 固件更新似乎只有在型号不再受 macOS 更新支持时才会停止。 由于 Apple 完全拥有 Apple Silicon Mac 的硬件和操作系统,因此可以决定对每款产品的支持期限。 结论 对于大多数未配备 T2 芯片的 Intel Mac,Apple 在该型号推出后至少 8 年内都提供了固件更新。对于许多型号,在它们无法运行受支持的 macOS 版本之前,就已经进行了固件更新。 2012 年之前推出的一些 Mac 搭载了 Sandy Bridge 芯片组,其固件支持早早被取消。原因尚不清楚,但可能与芯片组有关。 T2 和 Apple Silicon Mac 将会有所不同。 原文链接:https://eclecticlight.co/2024/08/06/how-long-does-apple-support-mac-firmware

优秀的个人博客,低调大师

🔥🔥🔥 最好用的跨平台 asiinema 终端录屏分享工具,比官方工具还强大

项目地址: https://github.com/gvcgo/asciinema 支持的平台:**MacOS/Linux/Windows** 支持的操作: 录制 播放 编辑(加速、剪切、整理延迟) 上传 asciinema.org 官网,并回显链接 转换为 gif 演示: 以下 gif 均使用本项目录制、编辑、转换,最终生成 gif 动图 1 、原速 gif( https://asciinema.org/a/651138) 2 、2 倍速 gif( https://asciinema.org/a/651140)

优秀的个人博客,低调大师

iPhone 13第三方换屏更难:Face ID会失效 即便官方配件也不行

苹果对 iPhone 13 系列屏幕的重新设计,意味着第三方维修公司现在更难更换屏幕。因为在尝试更换损坏掉的屏幕,可能会导致失去对 Face ID 的支持。碎屏是最常见的手机故障之一,任何意外的跌落、磕碰、挤压等等都会造成。 当 iPhone 出现故障,用户可以选择就近的 Apple Store 天才吧、或者苹果认证的维修网点、亦或者是第三方的维修中心。而如果是 iPhone 屏幕损坏,那么不能通过第三方维修中心进行维护。 油管频道 Phone Repair Guru 表示,虽然将屏幕替换成相同型号的 iPhone 13 屏幕在理论上可行的。但是在更换之后系统警告说,设备并没有使用正规的屏幕,而且 Face ID 也无法使用。 虽然更换屏幕是存在问题的,但是包括麦克风、接近传感器和环境光传感器依然可以更换的。尽管使用的是真正的苹果显示屏,但该通知基本上意味着在维修过程中有一个步骤必须执行,以使显示屏能够与特定的iPhone一起使用,而这一步骤是苹果授权的维修服务可以做的,但第三方不能做。 【责任编辑:未丽燕 TEL:(010)68476606】

优秀的个人博客,低调大师

官方发话了!5G网络全面覆盖太遥远,还要五到八年才能实现

中国的4G网络在全球虽然算不上第一,但考虑到中国面积大人口多,如今的覆盖率、速度能够达到这种水平,已经算得上是全球比较先进的4G覆盖大国了,目前中国4G网络覆盖率超过了95%,至少对比起土地面积差不多的美国来说已经是完胜了。 不过中国的用户现在关心的并不是4G网络覆盖率,而是火爆的5G网络,毕竟现在的5G手机用户正在飞速增长中。经历了去年下半年的4G到5G缓慢过渡期,今年开始5G手机正在以前所未有的速度普及,除了厂商们的上新以外,用户的换新也显得非常积极。 信通院公布的一份数据显示,仅4月份国内的5G手机出货量就多达1638.2万台,占手机出货量的39.3%,今年1月至4月份期间,国内5G手机累计出货3044.1万台,占手机出货量的33.6%,同时5G手机上新数量也达到了65款,意味着5G普及大潮正式拉开。 既然5G手机的用户量提升了,5G套餐用户数又是怎样一番表现呢?此前工信部公布了截至3月底的数据,截至3月底5G套餐用户规模超过了5000万,分别是中国移动的3172.3万、中国电信的1661万,中国联通未公布,可能只有几百万。 5月17日,中国移动在生态合作伙伴大会上又公布了取得的阶段性成绩,建成5G基站12.4万个,覆盖56个城市,5G套餐用户已经突破5000万户,同时中国移动还强调,会力争在2020年底前实现5G用户突破1亿。 5G手机和5G套餐用户数双双上涨,对于用户而言,最关心的无疑就是5G网络的覆盖了,因为想要用户买单5G网络还得靠服务说话,目前很多地区根本就无法连接到5G网络,原因自然是和5G基站数量有关,现阶段全国大概只有20多万个5G基站。 作为对比,全国目前有4G基站大概500万个,所有才有如此覆盖率。不过最近全国政协委员、中国联通产品中心总经理张云勇在受访时表示,5G网络建设的规模要比4G大2-3倍,成本也高,如果要达到4G旗鼓相当的网络覆盖,需要1000万台,至少要投入2万亿人民币。 同时他还透露,去年三大运营商共建立了十几万5G基站,今年总共会再建设100万个5G基站,如果以这样的速度发展下去,5G想要基本满足大城市深度覆盖,地市重点场景覆盖,企业无缝接入等基本覆盖需求,大概还需要等个五到八年。 从这点可以看出,虽然现在市面上的5G手机看起来很美好,三大运营商拿出来的5G套餐用户数也很漂亮,但5G网络全面覆盖依然离我们非常遥远,可能随着最近5G新基建的兴起,建设的节奏会快一些,但5G基站依然面临着众多问题需要解决。 一个是上面提到的成本问题,至少要投入2万亿人民币,还有一个就是功耗问题。此前有报道称,5G基站功耗是4G基站的3-4倍,可见电费成本也是相当大的支出,因此5G建设对于运营商而言,提出了巨大的挑战!

优秀的个人博客,低调大师

高可用性(High Availability):Redis 哨兵是Redis官方的高可用性解决方案

Redis主从复制的问题 Redis主从复制可将主节点数据同步给从节点,从节点此时有两个作用: 一旦主节点宕机,从节点作为主节点的备份可以随时顶上来。 扩展主节点的读能力,分担主节点读压力。 主从复制同时存在以下几个问题: 一旦主节点宕机,从节点晋升成主节点,同时需要修改应用方的主节点地址,还需要命令所有从节点去复制新的主节点,整个过程需要人工干预。 主节点的写能力受到单机的限制。 主节点的存储能力受到单机的限制。 原生复制的弊端在早期的版本中也会比较突出,比如:Redis复制中断后,从节点会发起psync。此时如果同步不成功,则会进行全量同步,主库执行全量备份的同时,可能会造成毫秒或秒级的卡顿。 Redis 的 哨兵(Sentinel)深入探究 Redis Sentinel的架构 Redis的哨兵机制就是解决我们以上主从复制存在缺陷(选举问题),保证我们的Redis高可用,实现自动化故障发现与故障转移。 该系统执行以下三个任务: 监控:哨兵会不断检查你的主服务器和从服务器是否运作正常。 提醒:当被监控的某个Redis服务器出现问题时,哨兵可以通过API给程序员发送通知 自动故障转移:主服务器宕机,哨兵会开始一次自动故障转移操作,升级一个从服务器为主服务器,并让其他从服务器改为复制新的主服务器. 配置 Sentinel Redis 源码中包含了一个名为 sentinel.conf 的文件, 这个文件是一个带有详细注释的 Sentinel 配置文件示例。 运行一个 Sentinel 所需的最少配置如下所示: 1)sentinel monitor mymaster 192.168.10.202 6379 2 Sentine监听的maste地址,第一个参数是给master起的名字,第二个参数为master IP,第三个为master端口,第四个为当该master挂了的时候,若想将该master判为失效, 在Sentine集群中必须至少2个Sentine同意才行,只要该数量不达标,则就不会发生故障迁移。 ----------------------------------------------------------------------------------------------- 2)sentinel down-after-milliseconds mymaster 30000 表示master被当前sentinel实例认定为失效的间隔时间,在这段时间内一直没有给Sentine返回有效信息,则认定该master主观下线。 只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线,将服务器标记为客观下线所需的 Sentinel 数量由对主服务器的配置决定。 ----------------------------------------------------------------------------------------------- 3)sentinel parallel-syncs mymaster 2 当在执行故障转移时,设置几个slave同时进行切换master,该值越大,则可能就有越多的slave在切换master时不可用,可以将该值设置为1,即一个一个来,这样在某个 slave进行切换master同步数据时,其余的slave还能正常工作,以此保证每次只有一个从服务器处于不能处理命令请求的状态。 ----------------------------------------------------------------------------------------------- 4)sentinel can-failover mymasteryes 在sentinel检测到O_DOWN后,是否对这台redis启动failover机制 ----------------------------------------------------------------------------------------------- 5)sentinel auth-pass mymaster 20180408 设置sentinel连接的master和slave的密码,这个需要和redis.conf文件中设置的密码一样 ----------------------------------------------------------------------------------------------- 6)sentinel failover-timeout mymaster 180000 failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。 执行故障迁移超时时间,即在指定时间内没有大多数的sentinel 反馈master下线,该故障迁移计划则失效 ----------------------------------------------------------------------------------------------- 7)sentinel config-epoch mymaster 0 选项指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步。这个数字越小, 完成故障转移所需的时间就越长。 ----------------------------------------------------------------------------------------------- 8)sentinel notification-script mymaster/var/redis/notify.sh 当failover时,可以指定一个"通知"脚本用来告知当前集群的情况。 脚本被允许执行的最大时间为60秒,如果超时,脚本将会被终止(KILL) ----------------------------------------------------------------------------------------------- 9)sentinel leader-epoch mymaster 0 同时一时间最多0个slave可同时更新配置,建议数字不要太大,以免影响正常对外提供服务。 主观下线和客观下线 主观下线:指的是单个 Sentinel 实例对服务器做出的下线判断。 客观下线:指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN主观下线判断。 Redis Sentinel的工作原理​​​​​​​ 1.每个 Sentinel 以每秒一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个 PING 命令。 2.如果一个实例距离最后一次有效回复 PING 命令的时间超过指定的值, 那么这个实例会被 Sentinel 标记为主观下线。 3.正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。 4.有足够数量的 Sentinel 在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。 5.每个 Sentinel 会以每 10 秒一次的频率向它已知的所有主服务器和从服务器发送 INFO 命令。 当一个主服务器被 Sentinel 标记为客观下线时, Sentinel 向下线主服务器的所有从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。 6.Sentinel和其他Sentinel协商主节点的状态,如果主节点处于SDOWN状态,则投票自动选出新的主节点。将剩余的从节点指向新的主节点进行数据复制。 7.当没有足够数量的Sentinel同意主服务器下线时,主服务器的客观下线状态就会被移除。当主服务器重新向Sentinel的PING命令返回有效回复时,主服务器的主观下线状态就会被移除。 ​​​​​​​自动发现 Sentinel 和从服务器 一个 Sentinel 可以与其他多个 Sentinel 进行连接, 各个 Sentinel 之间可以互相检查对方的可用性, 并进行信息交换。 你无须为运行的每个 Sentinel 分别设置其他 Sentinel 的地址, 因为 Sentinel 可以通过发布与订阅功能来自动发现正在监视相同主服务器的其他 Sentinel。 每个 Sentinel 会以每两秒一次的频率, 通过发布与订阅功能, 向被它监视的所有主服务器和从服务器的频道发送一条信息, 信息中包含了 Sentinel 的 IP 地址、端口号和运行 ID (runid)。 每个 Sentinel 都订阅了被它监视的所有主服务器和从服务器的频道, 查找之前未出现过的 sentinel 。 当一个 Sentinel 发现一个新的 Sentinel 时, 它会将新的 Sentinel 添加到一个列表中。 Sentinel 发送的信息中还包括完整的主服务器当前配置。 如果一个 Sentinel 包含的主服务器配置比另一个 Sentinel 发送的配置要旧, 那么这个 Sentinel 会立即升级到新配置上。 在将一个新 Sentinel 添加到监视主服务器的列表上面之前, Sentinel 会先检查列表中是否已经包含了和要添加的 Sentinel 拥有相同运行 ID 或者相同地址(包括 IP 地址和端口号)的 Sentinel , 如果是的话, Sentinel 会先移除列表中已有的那些拥有相同运行 ID 或者相同地址的 Sentinel , 然后再添加新 Sentinel 故障转移 一次故障转移操作由以下步骤组成: 发现主服务器已经进入客观下线状态。 对我们的当前纪元进行自增, 并尝试在这个纪元中当选。 如果当选失败, 那么在设定的故障迁移超时时间的两倍之后, 重新尝试当选。 如果当选成功, 那么执行以下步骤。 选出一个从服务器,并将它升级为主服务器。 向被选中的从服务器发送SLAVEOF NO ONE命令,让它转变为主服务器。 通过发布与订阅功能, 将更新后的配置传播给所有其他 Sentinel , 其他 Sentinel 对它们自己的配置进行更新。 向已下线主服务器的从服务器发送SLAVEOF命令, 让它们去复制新的主服务器。 当所有从服务器都已经开始复制新的主服务器时, 领头 Sentinel 终止这次故障迁移操作。 参考: https://redis.io/ https://www.cnblogs.com/bingshu/p/9776610.html

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:线程执行操作之定义线程执行代码

原文地址:http://android.xsoftlab.net/training/multiple-threads/index.html 引言 大量的数据处理往往需要花费很长的时间,但如果将这些工作切分并行处理,那么它的速度与效率就会提升很多。在拥有多线程处理器的设备中,系统可以使线程并行运行。比如,使用多线程将图像文件切分解码展示要比单一线程解码快得多。 这章我内容们将会学习如何设置并使用多线程及线程池。我们还会学习如何在一个线程中运行代码以及如何使该线程与UI线程进行通信。 定义在线程中运行代码 这节课我们会学习如何实现Runnable接口,该接口中的run()方法会在线程中单独执行。你也可以将Runnable对象传递给一个Thread类。这种执行特定任务的Runnable对象在某些时候被称为任务。 Thread类与Runnable类同属于基础类,它们仅提供了有限的功能。它们是比如HandlerThread, AsyncTask, 以及IntentService等线程功能类的基础核心。这两个类同样属于ThreadPoolExecutor的核心基础。ThreadPoolExecutor会自动管理线程以及任务队列,它甚至还可以使多个线程同时执行。 定义Runnable实现类 实现一个Runnable对象很简单: public class PhotoDecodeRunnable implements Runnable { ... @Override public void run() { /* * Code you want to run on the thread goes here */ ... } ... } 实现run()方法 在Runnable的实现类中,Runnable的run()方法中所含的代码将会被执行。通常来说,Runnable中可以做任何事情。要记得,这里的Runnable不会运行在UI线程,所以在它内部不能直接修改View对象这种UI对象。如果要与UI线程通讯,你需要使用到Communicate with the UI Thread课程中所描述的技术。 在run()方法的开头处设置当前的线程使用后台优先级。这种方式可以减少Runnable对象所属线程与UI线程的资源争夺问题。 这里还将Runnable对象所属的线程引用存储了起来。由Thread.currentThread()可以获得当前的线程对象。 下面是代码的具体实现方式: class PhotoDecodeRunnable implements Runnable { ... /* * Defines the code to run for this task. */ @Override public void run() { // Moves the current Thread into the background android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); ... /* * Stores the current Thread in the PhotoTask instance, * so that the instance * can interrupt the Thread. */ mPhotoTask.setImageDecodeThread(Thread.currentThread()); ... } ... }

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:布局性能优化之按需加载View

原文地址:http://android.xsoftlab.net/training/improving-layouts/loading-ondemand.html 有时应用程序中会有一些很少用到的复杂布局。在需要它们的时候再加载可以降低内存的消耗,同时也可以加快界面的渲染速度。 定义ViewStub ViewStub是一个轻量级的View,它没有高宽,也不会绘制任何东西。所以它的加载与卸载的成本很低。每个ViewStub都可以使用android:layout属性指定要加载的布局。 下面这个ViewStub用于一个半透明的ProgressBar的加载。它只有在新工作开始时才会显示。 <ViewStub android:id="@+id/stub_import" android:inflatedId="@+id/panel_import" android:layout="@layout/progress_overlay" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" /> 加载ViewStub 当需要加载由ViewStub所指定的布局时,可以使用setVisibility(View.VISIBLE)方法或者inflate()方法,两者效果相同。 ((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE); // or View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate(); Note: inflate()方法会在加载完毕的时候返回一个View。所以不需要使用findViewById()来查找这个布局的Root View。 一旦ViewStub所托管的View被加载,那么ViewStub将不再是View层级的一部分。它会被所加载的布局替换,并且会将该布局的ID更改为ViewStub的android:inflatedId属性所指定的ID。 Note: ViewStub的缺点是:它当前并不支持要加载布局的root View为< merge/>标签。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:后台加载数据之使用CursorLoader进行查询

原文地址:http://android.xsoftlab.net/training/load-data-background/index.html 引言 在ContentProvider中查询数据是需要花点时间的。如果你直接在Activity进行查询,那么这可能会导致UI线程阻塞,并会引起”Application Not Responding”异常。就算不会发生这些事情,那么用户也能感觉到卡顿,这会非常恼人的。为了避免这样的问题,应该将查询的工作放在单独的线程中执行,然后等待它执行完毕后将结果显示出来。 你可以使用一个异步查询对象在后台查询,然后等查询结束之后再与Activity建立连接。这个对象就是我们要说的CursorLoader。CursorLoader除了可以进行基本查询之外,还可以在数据发生变化后自动的重新进行查询。 这节课主要会学习如何使用CursorLoader在后台进行查询。 使用CursorLoader进行查询 CursorLoader对象在后台运行着一个异步查询,当查询结束之后会将结果返回到Activity或FragmentActivity。这使得查询在进行的过程中Activity或FragmentActivity还可以继续与用户交互。 定义使用CursorLoader的Activity 如果要在Activity中使用CursorLoader,需要用到LoaderCallbacks接口。CursorLoader会调用该接口中的方法,从而使得与Activity产生交互。这节课与下节课都会详细描述该接口中的回调。 举个例子,下面的代码演示了如何定义一个使用了CursorLoader的FragmentActivity。通过继承FragmentActivity,你可以获得CursorLoader对Fragment的支持: public class PhotoThumbnailFragment extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { ... } 初始化查询 使用LoaderManager.initLoader()可以初始化查询。它其实初始化了后台查询框架。可以将初始化这部分工作放在用户输入了需要查询的数据之后,或者如果不需要用户输入数据,那么也可以将这部分工作放在onCreate()或onCreateView()中执行: // Identifies a particular Loader being used in this component private static final int URL_LOADER = 0; ... /* When the system is ready for the Fragment to appear, this displays * the Fragment's View */ public View onCreateView( LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) { ... /* * Initializes the CursorLoader. The URL_LOADER value is eventually passed * to onCreateLoader(). */ getLoaderManager().initLoader(URL_LOADER, null, this); ... } Note: getLoaderManager()方法只对Fragment类可用。如果需要在FragmentActivity中获得LoaderManager,调用getSupportLoaderManager()方法即可。 开始查询 后台查询框架的初始化一旦完成,紧接着你所实现的onCreateLoader()就会被调用。如果要启动查询,需要在该方法内返回一个CursorLoader对象。你可以实例化一个空的CursorLoader,然后再使用它的方法定义查询,或者你也可以在实例化CursorLoader的时候定义查询。 /* * Callback that's invoked when the system has initialized the Loader and * is ready to start the query. This usually happens when initLoader() is * called. The loaderID argument contains the ID value passed to the * initLoader() call. */ @Override public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle) { /* * Takes action based on the ID of the Loader that's being created */ switch (loaderID) { case URL_LOADER: // Returns a new CursorLoader return new CursorLoader( getActivity(), // Parent activity context mDataUrl, // Table to query mProjection, // Projection to return null, // No selection clause null, // No selection arguments null // Default sort order ); default: // An invalid id was passed in return null; } } 一旦后台查询框架获得了该对象,那么它会马上在后台开始查询。当查询结果完成,后台查询框架会调用onLoadFinished(),该方法的具体内容会在下节课说明。

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:键盘输入处理之处理键盘按键

原文地址:http://android.xsoftlab.net/training/keyboard-input/commands.html 当用户将焦点给到可编辑文本的View时,例如EditText这种,并且该设备还拥有实体键盘,那么所有的输入都会被系统处理。然而,如果你希望可以拦截或者直接处理键盘的输入事件的话,你可以通过实现回调方法KeyEvent.Callback接口来做到。比如onKeyDown()和onKeyMultiple()。 Activity与View都实现了KeyEvent.Callback接口,所以一般情况下应该重写这两个类的回调方法。 Note: 当通过KeyEvent类或其它相关API处理键盘的输入事件时,应当认为这些键盘事件都来自于实体键盘。绝不要仰仗接收软键盘的按键事件。 处理单个按键事件 如果要处理独立的按键事件,需要恰当的使用onKeyDown()方法或者onKeyUp()方法。通常情况下,如果要确保只有一个按键被按下时,应当只使用onKeyUp()方法。如果用户按下并没有放开某个按钮的话,那么onKeyDown()将会被调用多次。 举个例子,下面的实现通过响应某些按键来控制游戏: @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_D: moveShip(MOVE_LEFT); return true; case KeyEvent.KEYCODE_F: moveShip(MOVE_RIGHT); return true; case KeyEvent.KEYCODE_J: fireMachineGun(); return true; case KeyEvent.KEYCODE_K: fireMissile(); return true; default: return super.onKeyUp(keyCode, event); } } 处理组合按键 为了响应组合按键事件,比如某些按键需要与Shift或者Control组合使用,你可以查询通过回调方法传回的KeyEvent对象。一些方法还为组合按键的提供了查询信息的功能,比如getModifiers()和getMetaState()。。不管如何,最简单的方案就是通过isShiftPressed()或者isCtrlPressed()检查你所关心的组合按键是否被按下了。 举个例子,下面是onKeyUp()方法的改良版本,增添了一些专门对于Shift键的额外处理: @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { ... case KeyEvent.KEYCODE_J: if (event.isShiftPressed()) { fireLaser(); } else { fireMachineGun(); } return true; case KeyEvent.KEYCODE_K: if (event.isShiftPressed()) { fireSeekingMissle(); } else { fireMissile(); } return true; default: return super.onKeyUp(keyCode, event); } }

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:手势处理之滚动动画及Scroller

原文地址:http://android.xsoftlab.net/training/gestures/scroll.html 在Android中,滑动经常由ScrollView类来实现。任何超出容器边界的布局都应该将自己内嵌在ScrollView中,以便提供可滚动的视图效果。自定义滚动只有在特定的场景下才会被用到。这节课将会描述这样一种场景:使用scroller显示一种可滚动的效果。 你可以使用Scroller或者OverScroller来收集一些滑动动画所需要的数据。这两个类很相似,但是OverScroller包含了一些用于指示已经到达布局边界的方法。示例InteractiveChart在这里使用了类EdgeEffect(实际上是类EdgeEffectCompat),在用户到达布局边界时会显示一种”glow“的效果。 Note: 我们推荐使用OverScroller。这个类提供了良好的向后兼容性。还应该注意,通常只有在实现滑动自己内部的时候,才需要使用到scroller类。如果你将布局嵌入到ScrollView或HorizontalScrollView中的话,那么它们会将滚动的相关事宜做好。 Scroller用于在时间轴上做动画滚动效果,它使用了标准的滚动物理学(摩擦力、速度等等)。Scroller本身不会绘制任何东西。Scroller会随着时间的变化追踪移动的偏移量,但是它们不会自动的将这些值应用在你的View上。你需要自己获取这些值并使用,这样才会使滑动的效果更流畅。 了解滑动术语 “Scrolling”这个词在Android中可被翻译为各种不同的意思,这取决于具体的上下文。 Scrolling是一种viewport(viewport的意思是,你所看到的内容的窗口)移动的通用处理过程。当scrolling处于x轴及y轴上时,这被称为“平移(panning)”。示例程序提供了相关的类:InteractiveChart,演示了滑动的两种不同类型,dragging(拖动)及flinging(滑动): Dragging 该滚动类型在这种情况下发生:当用户的手指在屏幕上来回滑动时。简单的Dragging由GestureDetector.OnGestureListener接口的onScroll()方法实现。 Flinging 该滚动类型在这种情况下发生:当用户快速在屏幕上滑动并离开屏幕时。在用户离开了屏幕之后,常理上应该保持滚动状态,并随之慢慢减速,直到viewport停止移动。Flinging由GestureDetector.OnGestureListener接口的onFling()方法及scroller对象所实现。 将scroller对象与滑动手势结合使用是一种共通的方法。你可以重写onTouchEvent()方法直接处理触摸事件,并降低滑动的速度来响应相应的触摸事件。 实现基础滚动 这部分章节将会描述如何使用scroller。下面的代码段摘自示例应用InteractiveChart。它在这里使用了一个GestureDetector对象,重写了GestureDetector.SimpleOnGestureListener的onFling()方法。它使用了OverScroller来追踪滑动中的手势。如果用户在滑动之后到达了内容的边缘,那么APP会显示一个”glow”的效果。 Note: 示例APP InteractiveChart 展示了一个图表,这个图标可以缩放、平移、滚动等等。在下面的代码段中,mContentRect代表了矩形的坐标点,而绘制图表的View则居于其中。在给定的时间内,一个总表的子集将会被绘制到这块区域内。mCurrentViewport代表了屏幕中当前可视的部分图表。因为像素的偏移量通常被当做整型,所以mContentRect的类型是Rect。因为图形的数据范围是小数类型,所以mCurrentViewport的类型是RectF。 代码段的第一部分展示了onFling()方法的实现: // The current viewport. This rectangle represents the currently visible // chart domain and range. The viewport is the part of the app that the // user manipulates via touch gestures. private RectF mCurrentViewport = new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); // The current destination rectangle (in pixel coordinates) into which the // chart data should be drawn. private Rect mContentRect; private OverScroller mScroller; private RectF mScrollerStartViewport; ... private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { // Initiates the decay phase of any active edge effects. releaseEdgeEffects(); mScrollerStartViewport.set(mCurrentViewport); // Aborts any active scroll animations and invalidates. mScroller.forceFinished(true); ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this); return true; } ... @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { fling((int) -velocityX, (int) -velocityY); return true; } }; private void fling(int velocityX, int velocityY) { // Initiates the decay phase of any active edge effects. releaseEdgeEffects(); // Flings use math in pixels (as opposed to math based on the viewport). Point surfaceSize = computeScrollSurfaceSize(); mScrollerStartViewport.set(mCurrentViewport); int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - AXIS_X_MIN) / ( AXIS_X_MAX - AXIS_X_MIN)); int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - mScrollerStartViewport.bottom) / ( AXIS_Y_MAX - AXIS_Y_MIN)); // Before flinging, aborts the current animation. mScroller.forceFinished(true); // Begins the animation mScroller.fling( // Current scroll position startX, startY, velocityX, velocityY, /* * Minimum and maximum scroll positions. The minimum scroll * position is generally zero and the maximum scroll position * is generally the content size less the screen size. So if the * content width is 1000 pixels and the screen width is 200 * pixels, the maximum scroll offset should be 800 pixels. */ 0, surfaceSize.x - mContentRect.width(), 0, surfaceSize.y - mContentRect.height(), // The edges of the content. This comes into play when using // the EdgeEffect class to draw "glow" overlays. mContentRect.width() / 2, mContentRect.height() / 2); // Invalidates to trigger computeScroll() ViewCompat.postInvalidateOnAnimation(this); } 当onFling()方法调用postInvalidateOnAnimation()方法时,它会调用computeScroll()来更新x及y的值。 大多数View会将scroller对象的x及y的属性值直接设置给方法scrollTo()。而下面的computeScroll()则采用了不同的方法:它调用computeScrollOffset()来获取x及y的当前坐标。当显示的区域到达边界时,这里就会展示一个”glow”的效果,代码会设置一个越过边缘的效果,并会调用postInvalidateOnAnimation()来使View重新绘制。 // Edge effect / overscroll tracking objects. private EdgeEffectCompat mEdgeEffectTop; private EdgeEffectCompat mEdgeEffectBottom; private EdgeEffectCompat mEdgeEffectLeft; private EdgeEffectCompat mEdgeEffectRight; private boolean mEdgeEffectTopActive; private boolean mEdgeEffectBottomActive; private boolean mEdgeEffectLeftActive; private boolean mEdgeEffectRightActive; @Override public void computeScroll() { super.computeScroll(); boolean needsInvalidate = false; // The scroller isn't finished, meaning a fling or programmatic pan // operation is currently active. if (mScroller.computeScrollOffset()) { Point surfaceSize = computeScrollSurfaceSize(); int currX = mScroller.getCurrX(); int currY = mScroller.getCurrY(); boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN || mCurrentViewport.right < AXIS_X_MAX); boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN || mCurrentViewport.bottom < AXIS_Y_MAX); /* * If you are zoomed in and currX or currY is * outside of bounds and you're not already * showing overscroll, then render the overscroll * glow edge effect. */ if (canScrollX && currX < 0 && mEdgeEffectLeft.isFinished() && !mEdgeEffectLeftActive) { mEdgeEffectLeft.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectLeftActive = true; needsInvalidate = true; } else if (canScrollX && currX > (surfaceSize.x - mContentRect.width()) && mEdgeEffectRight.isFinished() && !mEdgeEffectRightActive) { mEdgeEffectRight.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectRightActive = true; needsInvalidate = true; } if (canScrollY && currY < 0 && mEdgeEffectTop.isFinished() && !mEdgeEffectTopActive) { mEdgeEffectTop.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectTopActive = true; needsInvalidate = true; } else if (canScrollY && currY > (surfaceSize.y - mContentRect.height()) && mEdgeEffectBottom.isFinished() && !mEdgeEffectBottomActive) { mEdgeEffectBottom.onAbsorb((int) OverScrollerCompat.getCurrVelocity(mScroller)); mEdgeEffectBottomActive = true; needsInvalidate = true; } ... } 下面是执行实际放大的代码: // Custom object that is functionally similar to Scroller Zoomer mZoomer; private PointF mZoomFocalPoint = new PointF(); ... // If a zoom is in progress (either programmatically or via double // touch), performs the zoom. if (mZoomer.computeZoom()) { float newWidth = (1f - mZoomer.getCurrZoom()) * mScrollerStartViewport.width(); float newHeight = (1f - mZoomer.getCurrZoom()) * mScrollerStartViewport.height(); float pointWithinViewportX = (mZoomFocalPoint.x - mScrollerStartViewport.left) / mScrollerStartViewport.width(); float pointWithinViewportY = (mZoomFocalPoint.y - mScrollerStartViewport.top) / mScrollerStartViewport.height(); mCurrentViewport.set( mZoomFocalPoint.x - newWidth * pointWithinViewportX, mZoomFocalPoint.y - newHeight * pointWithinViewportY, mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX), mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY)); constrainViewport(); needsInvalidate = true; } if (needsInvalidate) { ViewCompat.postInvalidateOnAnimation(this); } 下面是computeScrollSurfaceSize()方法的内容。它计算了当前可滑动的界面的尺寸,以像素为单位。举个例子,如果整个图表区域是可见的,那么它的值就等于mContentRect。如果图标被放大了200%,那么返回的值就是水平及垂直方向值的两倍。 private Point computeScrollSurfaceSize() { return new Point( (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN) / mCurrentViewport.width()), (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN) / mCurrentViewport.height())); }

优秀的个人博客,低调大师

Android官方开发文档Training系列课程中文版:手势处理之记录手指移动的轨迹

原文地址:http://android.xsoftlab.net/training/gestures/movement.html 这节课将会学习如何在触摸事件中记录手指移动的轨迹。 当手指触摸的位置、压力或者尺寸发生变化时,ACTION_MOVE事件就会被触发。与Detecting Common Gestures中描述的一样,所有的事件都被记录在一个MotionEvent对象中。 因为基于手指的触摸并不是很精确的交互方式,所以检测触摸事件的行为需要更多的轨迹点。为了帮助APP区分基于轨迹的手势(比如滑动等移动的手势)与非轨迹手势(比如单点等不移动的手势),Android提出了一个名为”touch slop”的概念。Touch slop指的是用户按下的以像素为单位的距离。 这里有若干项不同的追踪手势轨迹的方法,具体使用哪个方法取决于应用程序的需求: 指针的起始位置与结束位置。 指针位移的方向,由X,Y的坐标判断。 历史记录,你可以通过getHistorySize()获得手势的历史尺寸。然后可以通过getHistorical(Value)方法获得这些历史事件的位置,尺寸,事件以及压力。当渲染手指的轨迹时,比如在屏幕上用手指画线条等,历史记录这时就会派上用场。 指针在屏幕上滑动的速度。 轨迹的速度 在记录手势的特性或者在检查何种手势事件发生时,除了要依靠手指移动的距离、方向这两个要素之外。还需要另外一个非常重要的因素就是速度。为了使速度计算更加容易,Android为此提供了VelocityTracker类以及VelocityTrackerCompat类。VelocityTracker用于辅助记录触摸事件的速度。这对于判断哪个速度是手势的标准部分,比如飞速滑动。 下面的例子用于演示在VelocityTracker API中方法的目的: public class MainActivity extends Activity { private static final String DEBUG_TAG = "Velocity"; ... private VelocityTracker mVelocityTracker = null; @Override public boolean onTouchEvent(MotionEvent event) { int index = event.getActionIndex(); int action = event.getActionMasked(); int pointerId = event.getPointerId(index); switch(action) { case MotionEvent.ACTION_DOWN: if(mVelocityTracker == null) { // Retrieve a new VelocityTracker object to watch the velocity of a motion. mVelocityTracker = VelocityTracker.obtain(); } else { // Reset the velocity tracker back to its initial state. mVelocityTracker.clear(); } // Add a user's movement to the tracker. mVelocityTracker.addMovement(event); break; case MotionEvent.ACTION_MOVE: mVelocityTracker.addMovement(event); // When you want to determine the velocity, call // computeCurrentVelocity(). Then call getXVelocity() // and getYVelocity() to retrieve the velocity for each pointer ID. mVelocityTracker.computeCurrentVelocity(1000); // Log velocity of pixels per second // Best practice to use VelocityTrackerCompat where possible. Log.d("", "X velocity: " + VelocityTrackerCompat.getXVelocity(mVelocityTracker, pointerId)); Log.d("", "Y velocity: " + VelocityTrackerCompat.getYVelocity(mVelocityTracker, pointerId)); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: // Return a VelocityTracker object back to be re-used by others. mVelocityTracker.recycle(); break; } return true; } Note: 注意,应当在ACTION_MOVE事件内部计算速度,不要在ACTION_UP内部计算,因为在ACTION_UP内部计算所得到的X与Y的速度值都是0.

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册