https://avatars.githubusercontent.com/u/22124534?v=4

Watson Wang

OnClickListener2048

把一件事坚持30天会怎么样?

30天,一件小事:它能如何改变你?

在快节奏的现代生活中,我们常常渴望掌握新技能,无论是编程、乐器、绘画还是运动,但又苦于时间稀缺,迟迟不敢开始。你是否曾想过,仅仅坚持一件事30天,会带来怎样的改变?

在YouTube上,有一个累积观看接近4000万的TED演讲,它提出了一个引人深思的理论:每天投入40分钟,坚持30天,累积大约20小时,你就能学会任何一门新技能。

是的,你没听错,不是200小时,也不是2000小时,仅仅是20小时。当然,这20小时并非让你成为奥运冠军或行业专家,而是让你能够比较体面地使用某项技能,具备初步的实践能力和信心。演讲者本人就用这个“20小时理论”,成功学会了瑜伽、编程、盲打、围棋、弹琴、乃至冲浪等多项技能。

那么,这20小时究竟是如何“炼成”的,以及这30天的坚持会带给我们什么?

20小时学习的四大核心步骤

这并非盲目的“苦练”,而是有策略的“巧练”。演讲者强调了四个关键步骤,它们是高效学习的基石:

1. 分解技能:化繁为简

任何一项看似复杂的技能,都可以被分解成一系列更小、更具体的子技能。例如,学习编程可能包括“理解变量”、“编写循环”、“调试错误”等。你的任务是找出其中最常用、最能快速产生效果的核心小技能,并优先练习它们。这能让你迅速建立起“我能行”的信心,并看到初步的成果。

2. 学习足够理论以自我纠正:拒绝“理论陷阱”

不要陷入无休止的理论学习中。目标是阅读3-4本(或更少)关于这项技能的基础教材或教程,学习到能够让你在练习时,可以识别出自己哪里做得不够好、需要进步的地方即可。过多的理论会拖延你的实践进程,而真正的学习和提升往往发生在动手的那一刻。

3. 移除学习障碍:创造无干扰环境

“磨刀不误砍柴工”,清除一切可能让你分心的因素是成功的关键。在练习时,请关掉手机通知,准备好所有需要的工具(例如,如果你要学习吉他,确保你的吉他已调好音,乐谱和教程就在手边)。为自己打造一个专注、高效的学习环境,让你的每一分钟都物有所值。

4. 投入20小时专注练习:日积月累,水滴石穿

这是最核心的部分:积累满20小时的专注练习时间。 按照每天40分钟的节奏,这恰好是30天的持续投入。每天坚持,哪怕只是一小段固定的时间,其累积效应是惊人的。你的大脑会逐渐建立新的神经通路,肌肉会形成记忆,技能会在无形中得到强化。

坚持30天,你会看到这些变化:

当你按照这个方法,真正将一件事坚持30天后,你会体验到以下几个层面的蜕变:

  • 技能的初步掌握与自信心的飞跃: 经过20小时的有效练习,你会发现自己真的能“体面地”使用这项技能了。无论是流利地敲出代码、流畅地弹奏一段乐曲,还是自信地用新语言进行日常交流,这种从“完全不会”到“基本掌握”的跨越,将极大地增强你的自我效能感和学习信心。你会发现,学习新事物并非遥不可及。

  • 习惯的初步养成与惰性的减弱: 30天的坚持,足以让这项活动在你生活中占据一席之地。最初的抵触和拖延会逐渐消退,它会变得不再那么像一项“任务”,而更像你日常生活的一部分。你的大脑会开始期待并适应这种规律,从而为你未来更长久的坚持打下坚实基础。

  • 时间管理能力的提升: 你会发现,即使每天只拿出40分钟,你也能“创造”出时间来学习。这会让你对自己的时间分配有更深的理解和更强的掌控感,从而优化生活中的其他方面,发现原来有这么多“碎片时间”可以利用。

  • 韧性与自我纪律的培养: 坚持30天并非一帆风顺,你可能会遇到身体不适、情绪低落、突发事件等干扰。每一次克服困难、重新回到轨道上的努力,都是对你韧性和自我纪律的磨砺。你会变得更强大,更不容易被挫折打倒。

  • 身份认同的转变: 当你每天都进行这项活动时,你会开始在潜意识中认同自己是“一个会编程的人”、“一个热爱跑步的人”或者“一个弹吉他的人”。这种积极的身份认同会进一步强化你的行为,形成良性循环。

你的20小时,从何开始?

30天,20小时,这是一个既有挑战性又充满可能性的数字。它提醒我们,学习不一定要漫长而痛苦,只要方法得当,持续投入,即使是看似微不足道的努力,也能汇聚成改变的力量。

现在,是时候问问自己:有什么新技能是你一直渴望掌握的? 拿起你的笔记本,按照这四个步骤,为自己开启一个为期30天的学习之旅吧!相信我,当30天结束时,你会对自己的潜能感到惊喜。


深入理解 Java GC:核心算法与常见收集器

前言

Java 开发者通常不需要像 C/C++ 开发者那样手动管理内存分配和释放,这得益于 Java 虚拟机(JVM)强大的**垃圾回收(Garbage Collection, GC)**机制。GC 自动地查找并回收不再被程序使用的内存(即“垃圾”),从而避免了内存泄漏和野指针等问题。

然而,GC 并非没有代价。不合适的 GC 配置或算法选择可能导致应用暂停(Stop-the-World, STW),影响性能和用户体验。因此,理解 GC 的基本原理和常用算法对于 Java 性能调优至关重要。

GC 的核心任务:识别垃圾

GC 的首要任务是找出哪些内存可以被回收。现代 JVM 主要采用**可达性分析(Reachability Analysis)**算法来判断对象是否存活:

  1. GC Roots: 首先确定一系列必须存活的“根”对象(GC Roots)。常见的 GC Roots 包括:
    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中常量引用的对象。
    • 本地方法栈中 JNI(即 Native 方法)引用的对象。
    • 活动线程。
    • 同步锁(synchronized 关键字)持有的对象。
  2. 可达性分析: 从 GC Roots 开始,沿着引用链向下搜索。如果一个对象可以通过任何引用链从 GC Roots 到达,则称该对象是可达的 (Reachable),意味着它正在被使用,不能回收。
  3. 不可达对象: 如果一个对象无法从任何 GC Roots 通过引用链到达,则认为该对象是不可达的 (Unreachable),即为“垃圾”,可以被回收。

注意: 即使是不可达对象,也至少要经历两次标记过程才真正被回收(涉及 finalize() 方法,但此方法已不推荐使用)。

基本 GC 算法

基于可达性分析找到垃圾后,就需要具体的算法来回收这些空间。以下是几种基础的 GC 算法思想,它们是现代复杂 GC 收集器的基石:

面试官:Android中加载大图和长图的正确方式是什么?

面试官您好,加载大图(高分辨率图)和长图(超高图)是 Android 开发中常见的性能和内存挑战。处理它们的核心原则是 避免一次性将整个原始尺寸的图片完整加载到内存中。我会根据图片类型采用不同的策略:

1. 加载“大图”(高分辨率图片 High-Resolution Images)

这里的“大”通常指图片的像素尺寸远超需要显示它的 ImageView 或屏幕的尺寸。

  • 核心技术:降采样 (Downsampling)
  • 目标: 在解码图片时就只加载一个缩小版的、内存占用更小的 Bitmap 到内存中,而不是加载完整大图后再缩放。
  • 实现步骤:
    1. 仅获取图片边界信息:
      • 创建 BitmapFactory.Options 对象。
      • 设置 options.inJustDecodeBounds = true
      • 调用 BitmapFactory.decodeStream(), decodeFile(), decodeResource() 等方法。此时,解码器只会读取图片的宽度、高度和 MIME 类型等元数据到 options 中(outWidth, outHeight),不会真正分配 Bitmap 内存
    2. 计算采样率 inSampleSize
      • 获取目标 ImageView 的尺寸(reqWidth, reqHeight)。如果 View 尚未布局完成,可能需要通过 View.post()ViewTreeObserver 等方式获取。
      • 比较图片原始尺寸 (options.outWidth, options.outHeight) 和目标显示尺寸 (reqWidth, reqHeight)。
      • 计算一个合适的 inSampleSize 值。该值表示缩小的倍数(宽和高都将缩小 inSampleSize 倍)。关键点inSampleSize 应该是 2 的整数次幂 (1, 2, 4, 8…),这样解码效率最高,效果最好。计算逻辑通常是找到一个最小的 2 的幂,使得解码后的图片尺寸(原始尺寸 / inSampleSize)略大于或等于目标尺寸。
      • 例如,可以封装一个 calculateInSampleSize(options, reqWidth, reqHeight) 的工具方法来实现这个计算逻辑。
    3. 实际解码缩小后的图片:
      • options.inJustDecodeBounds 设置回 false
      • 设置计算得到的 options.inSampleSize 值。
      • (可选优化)设置 options.inPreferredConfig 为更节省内存的格式,如 Bitmap.Config.RGB_565 (如果不需要 Alpha 通道)。
      • 再次调用 BitmapFactory.decodeXXX() 方法。这次会根据 inSampleSize 加载一个缩小版的 Bitmap 对象,内存占用大大降低。
  • 最佳实践:使用图片加载框架
    • Glide, Coil, Picasso 这样的成熟图片加载框架,内部已经完美封装了降采样的逻辑。它们会自动根据目标 ImageView 的尺寸(或通过 override() API 指定的尺寸)来计算并应用合适的 inSampleSize
    • 因此,在绝大多数场景下,直接使用这些优秀的图片库是加载“大图”的最佳且最简单的方式

2. 加载“长图”(Very Tall Images / Scrolling Long Screenshots)

这里的“长”指的是图片的高度远超屏幕高度,通常需要用户滚动来查看完整内容。

HTTP vs. HTTPS:深入理解差异与 HTTPS 全流程

前言

当我们在浏览器地址栏输入网址时,通常会看到 http://https:// 开头。虽然只差一个 “S”,但它们代表着截然不同的网络通信方式,尤其在安全性方面。理解 HTTP 与 HTTPS 的区别以及 HTTPS 的工作原理,对于 Web 开发者和关心网络安全的用户都至关重要。

什么是 HTTP?

HTTP (Hypertext Transfer Protocol),即超文本传输协议,是用于从万维网(WWW)服务器传输超文本到本地浏览器的传送协议。它是互联网上应用最为广泛的一种网络协议,所有的 WWW 文件都必须遵守这个标准。

HTTP 的主要特点:

  1. 无连接: 限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接(现代 HTTP 版本如 HTTP/1.1 支持持久连接 Keep-Alive)。
  2. 无状态: 协议对于事务处理没有记忆能力。每个请求都是独立的,服务器不知道客户端之前的请求历史。这通常需要 Cookie 或 Session 等机制来维持状态。
  3. 明文传输: 这是 HTTP 最致命的缺点。所有传输的数据(包括用户名、密码、银行卡信息等)都是未加密的,在传输过程中容易被窃听、截取和篡改。

什么是 HTTPS?

HTTPS (Hypertext Transfer Protocol Secure),即安全超文本传输协议。简单来说,它就是 HTTP 的安全版本。HTTPS 在 HTTP 的基础上加入了 SSL/TLS 协议,依靠 SSL/TLS 来加密数据包、验证服务器身份和保证数据完整性。

HTTPS 的核心优势:

  1. 数据加密: 通信内容通过对称加密和非对称加密技术进行加密,即使被截获,攻击者也无法轻易解密获取真实内容。
  2. 身份认证: 通过数字证书验证服务器的真实身份,防止用户访问到仿冒的钓鱼网站。
  3. 数据完整性: 通过消息认证码(MAC)校验数据在传输过程中是否被篡改。

HTTP 与 HTTPS 的主要区别

特性HTTPHTTPS
安全性明文传输,不安全使用 SSL/TLS 加密,安全
协议层应用层协议HTTP + SSL/TLS 协议(在传输层和应用层之间)
URL 前缀http://https://
默认端口80443
证书不需要需要 CA 颁发的 SSL/TLS 证书
连接过程简单,TCP 三次握手后直接传输 HTTP 报文TCP 三次握手后,还需要进行 SSL/TLS 握手
性能性能开销小建立连接(握手)有一定性能开销,但传输过程影响不大
SEO不利于 SEO对 SEO 友好,被搜索引擎(如 Google)推荐

HTTPS 的网络全流程详解

HTTPS 的通信过程比 HTTP 复杂,主要增加了 SSL/TLS 握手 阶段。以下是简化但关键的步骤:

Android冷启动优化

[toc]

1、冷启动时间检测

通过adb命令来检测应用冷启动时间

adb shell am start -W package/Activity路径

运行结果如下所示

  • TotalTime:应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面显示。
  • WaitTime:一般比TotalTime大点,是AMS启动Activity的总耗时。
  • Android 5.0以下没有WaitTime,所以我们只需要关注TotalTime即可。
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=***/***.SplashActivity }
Warning: Activity not started, intent has been delivered to currently running top-most instance.
Status: ok
LaunchState: UNKNOWN (0)
Activity: ***/***.MainActivity
TotalTime: 785
WaitTime: 787
Complete

2、冷启动时间消耗在哪

2.1、MultiDex耗时

apk的编译过程

  • 1、打包资源文件,生成R.java文件(使用工具AAPT)
  • 2、处理AIDL文件,生成java代码(没有AIDL则忽略)
  • 3、编译 java 文件,生成对应.class文件(java compiler)
  • 4、.class 文件转换成dex文件(dex)
  • 5、打包成没有签名的apk(使用工具apkbuilder)
  • 6、使用签名工具给apk签名(使用工具Jarsigner)
  • 7、对签名后的.apk文件进行对齐处理,不进行对齐处理不能发布到Google Market(使用工具zipalign)

为什么需要使用MultiDex

在apk编译流程的第4步,将class文件转换成dex文件,默认只会生成一个dex文件,单个dex文件中的方法数不能超过65536,不然编译会报错:

理解 Flutter 的三棵树

Flutter 的核心架构依赖于三棵紧密协作的树:Widget 树 (Widget Tree)Element 树 (Element Tree)RenderObject 树 (RenderObject Tree)。它们共同构成了 Flutter UI 的声明、管理和渲染机制,是 Flutter 高性能渲染的关键。

下面我分别解释一下它们的作用:

1. Widget 树 (Widget Tree) - UI 的蓝图 📜

  • 作用: 这是我们开发者 最常接触 的树。它由我们编写的 Widget 对象构成,完全是 UI 界面在特定状态下的 配置信息描述蓝图
  • 特点:
    • 不可变性 (Immutable): Widget 本身是不可变的。每次 UI 需要更新时(例如调用 setState),Flutter 会重新构建 Widget 树(或其一部分)。
    • 轻量级: Widget 只是配置数据,创建和销毁它们的成本相对较低。
  • 类比: 就像建筑的 设计图纸,它描述了建筑应该是什么样子,用了什么材料(配置),但它不是建筑本身。

2. Element 树 (Element Tree) - 连接者与状态管理者 🔗🧠

  • 作用: 这是连接 Widget 树和 RenderObject 树的 关键桥梁。它持有 UI 的 实际结构可变状态
  • 特点:
    • 可变性 (Mutable): 与 Widget 不同,Element 是可变的,它在 Widget 树多次重建之间 保持稳定
    • 生命周期管理: 每个 Widget 在树中的特定位置都有一个对应的 ElementElement 负责管理 WidgetRenderObject 的生命周期(挂载、更新、卸载)。
    • 更新决策: 当 Widget 树重建时,Element 会比较新旧 Widget (canUpdate 方法)。如果可以更新(例如类型和 Key 相同),它会更新自己的配置并指示 RenderObject 更新;否则,会创建新的 ElementRenderObject。这种机制是 Flutter 高效更新 的核心,避免了不必要的重建。
    • 状态持有: 对于 StatefulWidget,对应的 StatefulElement 会持有 State 对象。
  • 类比: 像是建筑工地的 项目经理施工队长。他拿着设计图纸 (Widget),知道当前的施工状态 (State),并指导工人 (RenderObject) 如何建造或修改建筑。他会尽量复用现有的结构 (Element/RenderObject),而不是每次都推倒重来。

3. RenderObject 树 (RenderObject Tree) - 渲染的执行者 🖌️📐

  • 作用: 这是 真正负责 UI 布局和绘制 的树。它包含了 UI 元素的几何信息(尺寸、位置)和绘制逻辑。
  • 特点:
    • 重量级: RenderObject 包含了复杂的布局和绘制计算逻辑,创建和操作它们的成本较高。
    • 核心渲染: RenderObject 负责执行 layout(计算大小和位置)、paint(在画布上绘制)和 hit testing(处理用户交互事件的命中判断)等底层操作。
  • 类比: 就像是建筑的 实际物理结构 以及负责 粉刷、装修的工人。他们负责具体的测量、定位、砌墙、刷漆等实际工作,最终呈现出可见的建筑。

三者如何协作?

  1. 构建: 开发者编写 Widget 构成 Widget 树 (蓝图)。
  2. 实例化与关联: Flutter 框架根据 Widget 树创建对应的 Element 树 (项目经理团队)。每个 Element 会创建并持有一个 RenderObject (或复用),形成 RenderObject 树 (实际施工)。
  3. 更新: 当 setState 被调用时:
    • 新的 Widget 树 (新蓝图) 被创建。
    • Element 树被遍历,每个 Element 对比新旧 Widget
    • 如果 Widget 可以更新 (类型和 Key 相同),Element 会更新其持有的 RenderObject 的属性 (项目经理指导工人微调)。
    • 如果 Widget 不能更新,旧 Element 会被卸载,新的 ElementRenderObject 会被创建 (项目经理决定拆除部分旧结构,按新图纸建新的)。
    • Element 树通过这种方式 最大限度地复用 RenderObject,减少了昂贵的 RenderObject 创建、布局和绘制操作,从而实现了高性能。

总结来说: