1.代码拆分-使用SplitChunks
2.ãPocoç¬è®°ã线ç¨Thread
3.知物由学 | 端游代码保护:从原生代码到游戏引擎
4.[Angular 组件库 NG-ZORRO 基础入门] - 源码初窥: core
5.Lifecycle源码解析
代码拆分-使用SplitChunks
前言
探索代码优化的源码抽离世界,最近开始接触项目优化工作,源码抽离其中涉及三方组件的源码抽离拆分。在未进行拆分前,源码抽离可能存在两个场景:单一js文件过大,源码抽离影响缓存效率;无法有效管理第三方库。源码抽离mcgs代码实例源码利用`splitChunks`工具,源码抽离可以将模块进行分割,源码抽离并提取重复代码,源码抽离解决上述问题。源码抽离
概念区分 - module、源码抽离bundle、源码抽离chunk
深入理解`splitChunks`之前,源码抽离先梳理几个概念。源码抽离module:模块,源码抽离在webpack中,任何文件都可视为模块,需要配置loader将其转换为支持打包的文件。chunk:编译完成待输出时,webpack将module按特定规则组合成一个个chunk。bundle:webpack处理完chunk文件后,生成供浏览器运行的代码。
chunk与bundle的关系
探析chunk的构成与bundle之间的关联。chunk有两种形式:初始化(initial)chunk,即入口起点的主chunk,包含入口起点及其依赖的所有模块;非初始化(non-initial)chunk,用于延迟加载,可能在使用动态导入或`SplitChunksPlugin`时出现。傻瓜指标公式源码
通过入口产生的chunk
假设目录结构如下:index.js, another-module.js, webpack.config.js, package.json添加script配置,运行webpack并使用ndb追踪代码执行。通过命令启动浏览器,点击播放按钮执行build命令,追踪chunk到bundle的流转。
chunk处理步骤概览
从`Compilation`类的`seal`方法出发,首先搜集chunks,然后调用`createChunkAssets`方法生成source,为输出文件做准备;通过`compilation.emitAssets`方法记录资源信息到`compilation.assets`对象;一系列回调最终调用`onCompiled`方法,将assets信息写入输出目录,生成bundle文件。
Demo2 - 动态导入
将`index.js`中的lodash通过`import`方式导入,动态导入返回promise,通过`then`获取导入信息。修改`webpack.config.js`入口为单个`index.js`。源码追踪显示,初始化文件新增一个名为`index`的chunk,但在模块分析中识别到`import`方式,为`index.js`模块增加了`AsyncDependenciesBlock`标记,经过处理生成一个名为`null`的chunk。
总结:`chunk`是源代码中的抽象,封装定义如何将模块组写入文件,而`bundle`则是输出目录的文件。
解决隐患 - `splitChunks`配置
在上述示例中,存在三方模块重复引用的问题。通过简单的`optimization.splitChunks`配置,实现了lodash的六六均线源码抽离,降低了单个入口文件的大小。总结使用心得,`splitChunks`主要用于代码优化,针对不同场景配置`chunks`选项,如`all`、`async`、`initial`以及自定义函数,以达到高效拆分效果。
比较`async`、`initial`、`all`的区别
在示例中增加`another.js`,静态导入lodash,对比`async`、`all`、`initial`的不同效果。默认情况下,`initial`影响HTML文件中的脚本标签,而`async`仅针对动态导入,`all`则考虑更多场景,适合存在复用模块的情况,但需权衡动态导入及其内部依赖的抽离。
splitChunks.cacheGroups
在使用`splitChunks`基础上,通过`cacheGroups`实现更细粒度的代码拆分,进一步优化项目结构。
总结
通过`splitChunks`配置,实现三方组件的高效管理与拆分,优化代码结构与加载效率。创梦源码账号理解模块、bundle、chunk之间的关系,以及如何利用`splitChunks`与`cacheGroups`进行代码拆分与优化,是提升项目性能的关键步骤。
ãPocoç¬è®°ã线ç¨Thread
PocoçThreadæ¯å¯¹æ ååºstd::threadçå°è£ ï¼åæ¶å®ç±»ä¼¼Javaä¸æ ·ï¼æä¾äºRunnableæ¥å£ãæ以使ç¨ä¸æ¯å¯¹æ Javaçãä¸æ ååºä¸åçæ¯ï¼Poco::Threadå建åè¿è¡æ¶ç¸å离çãè¿ä¸ç¹æ ååºè®¾è®¡ç¡®å®ä¸å¤ªå好ãä¾å¦ä¸é¢ä¾åã
åæ ·çä¾å
ç±ä¸é¢å¯è§ï¼ä½¿ç¨åºæ¬è·Java类似ãå建ä¸è¿è¡ä¹å离äºã
çä¸ä¸ä¸»è¦çè¿è¡æ¥å£ï¼æèªPoco1.9æºç
æºç æ件主è¦å å«
1.Thread.h/Thread.cpp
æä¾å¤é¨è°ç¨æ¥å£
å¨Thread.cppä¸å®ä¹äºä¸¤ç§Holder, RunnableHolderåCallableHolderãHolderææ¯æ¯Pocoæ¡æ¶ä¸ç»å¸¸ç¨å°çï¼æ¯å¯¹æä¸ç§ç±»å对象çæéå è£ ã
Runnable为线ç¨è¿è¡ç±»çåºç±»ï¼
Callable为带ä¸ä¸ªåæ°çæ¹æ³
2.Thread_POSIX.h/Thread_POSIX.cpp
3.Thread_VX.h/Thread_VX.cpp
4.Thread_WIN.h/Thread_WIN.cpp
5.Thread_WINCE.h/Thread_WINCE.cpp
è¿å 个æ件ï¼æ¯ä¸ªæ件ä¸é½å®ä¹äºThreadImplï¼ç¨äºä¸åå¹³å°ä¸çå ·ä½å®ç°ï¼Threadç§æ继æ¿ThreadImpï¼ThreadImpç¨äºåªä¸ä¸ªæ件ç±ç¼è¯å®å³å®ã
顺便说ä¸ä¸POSIXç³»ç»ä¸çå®ç°ãå 为使ç¨çæ¯c++ï¼å½æ¶æ²¡æthreadç±»ï¼æ以ææçå®ç°é½æ¯ä½¿ç¨pthreadåºæ¥å®ç°çãå ·ä½ç使ç¨è¯·åèpthreadææ¯ææ¡£ã
6.ThreadLocal.h/ThreadLocal.cpp
ThreadLocalä¸å®ä¹äºä¸ä¸ªç±»ï¼ TLSAbstractSlotç±»ï¼ TLSSlotç±»ï¼ ThreadLocalStorageç±»
TLSAbstractSlotæ¯åºç±»ï¼TLSSlotæ¯æ¨¡æ¿ç±»ï¼éè¿æ¨¡æ¿ææ¯å 裹äºå ·ä½çç±»åãThreadLocalStorageæ¯ç¨äºçº¿ç¨åå¨ï¼å ·ä½æ¯éè¿ä¸ä¸ªmapæ¥å®ç°ã
å 为1.9使ç¨çæ¯c++ï¼è¿æ²¡æå¼ç¨local_threadå ³é®åï¼æ以è¿éæ¯éè¿è¿ç§æ¹å¼å®ç°ã
ThreadLocalStorageå®ä¹å¦ä¸
é£ä¹Poco::Threadçtlsæ¯å¦ä½å®ä¹çï¼
æºç æ件æ¯è¾å°ï¼ä¸»è¦å¦ä¸æ件
1.Thread.h/Thread.cpp
2.Thread_STD.h/Thread_POSIX.cpp/Thread_VX.cpp/Thread_WIN.cpp
Thread.h 主è¦å¯¹å®ç°ç±»ThreadImpçå è£ ï¼å¹¶å®ä¹äºå¯¹å¤æ¥å£ã
Thread_STD.hå®ä¹äºå é¨å®ç°,主è¦æä¾äºThreadImpç±»
Thread_POSIX.cpp/Thread_VX.cpp/Thread_WIN.cppåå«å®ä¹ä¸åå¹³å°ä¸çå ¼å®¹å®ç°
å¨Thread_STD.hä¸å®ä¹äºå 个éè¦ç±»å
å¨Thread.cppä¸å¢å äºä¸¤ç§
private修饰çThreadDataï¼å®ä¹äºçº¿ç¨å é¨æ°æ®ã 1.9ä¸æºç åå«å®ä¹å¨å个平å°å®ç°ç±»ä¸ï¼è¿éæ½ç¦»åºæ¥å®ä¹å¨Thread.cppä¸ãè¾ä¹åçå®ä¹ï¼è¿éé¢å¤çæ¯æ°å¢äºstd::threadæéãå 为ç´æ¥å¼ç¨äºc++ä¸çthreadï¼æäºå®ç°ç´æ¥åå©äºå®ã
知物由学 | 端游代码保护:从原生代码到游戏引擎
近年来,移动端游戏迅速崛起,凭借其便利性和趣味性,吸引了海量玩家。然而,为了追求更佳的游戏体验,部分玩家选择在PC上使用模拟器操控手游,虽在操作和沉浸感上有改善,但性能方面仍有所欠缺。
为了满足这类玩家需求,游戏厂商尝试将手游与PC整合,实现跨平台体验。如《阴阳师》、《第五人格》、《荒野行动》等游戏支持PC端运行,相较于手机或模拟器,体验更佳。
然而,外挂现象也随PC端游戏的扩展而日益严重。PC版游戏面临应用权限混乱、取证困难等问题,令厂商陷入困境,趣步PHP源码同时为外挂提供了可乘之机。
反外挂通常涉及静态代码保护与动态运行对抗两大部分。本文聚焦静态代码保护,探索在易盾端游反外挂代码保护中,PE代码保护的应用及面向游戏引擎的代码保护策略,进而提出一种通用游戏逻辑代码保护方案。
1. 通用代码保护
1.1 PE代码保护概述
PE代码保护聚焦于原生代码保护,针对x架构的二进制文件(PE文件)进行保护。该技术已有多年历史,从世纪初开始发展,催生了如“UPX”壳等具有影响力的加固思路与加密算法。
PE加固技术主要分为整体加密、混淆与虚拟机保护三类,旨在对抗静态分析与动态调试。
1)整体加密:通过压缩/加密壳与附属功能实现,如IAT加密、反调试与完整性校验。
2)混淆:包括花指令、指令变形、代码乱序与字符串加密等,旨在提升静态分析与动态调试难度。
3)虚拟机保护:引入私有指令集,将原生汇编指令转化为虚拟运行时指令,以实现保护。
1.2 游戏逻辑外挂原理
从攻击者视角,实现游戏外挂,主要关注两点:关键数值修改与关键函数操纵。传统代码保护对静态分析与动态调试具有较好防御效果,但针对特定游戏逻辑篡改类外挂,效果有限。
不同游戏引擎(如Unity3D、UE4)引入了运行时解释器,使得传统PE保护方案难以覆盖,从而提出了针对游戏引擎的保护方案。
2. 游戏引擎保护
易盾端游代码保护方案针对Unity3D引擎,包括Mono DLL整体加密、方法级加密、格式私有化、IL2CPP global-metadata 加密与指令抽取等技术。
2.1 Unity3D端游代码保护
1)Mono DLL整体加密:加密后的DLL格式改变,使用反编译工具无法解析。
2)方法级加密:关键IL指令抽离至外部,内存中无法完整逻辑。
3)格式私有化:关键加密信息用私有格式存储,运行时不会恢复。
4)IL2CPP global-metadata 加密:通过自定义加密算法保护解析文件,破坏符号解析。
5)IL2CPP 指令抽取:游戏核心代码抽离外部,配合乱序变形引擎,内存中无法完整获取。
2.2 通用游戏引擎保护方案
针对不同游戏引擎与开发语言,需设计通用且性能良好的保护方案。方案需兼顾通用性、性能与安全性,采用定制AST引擎解析源码,并结合混淆与少量核心代码虚拟化,以实现轻量级虚拟机保护。
3. 端游代码保护总结
代码保护是端游反外挂的关键,影响游戏体验与安全性。然而,仅依赖代码保护不足以应对所有外挂挑战。未来,将深入探讨运行时反外挂策略,为游戏安全提供更全面的解决方案。
[Angular 组件库 NG-ZORRO 基础入门] - 源码初窥: core
在探索和了解了典型组件的源码之后,我们进一步深入 NG-ZORRO 组件库的核心结构,发现了一个关键的策略来解决组件间共用属性、功能导致的重复编写问题。NG-ZORRO 支持近 种组件,为避免每种组件都需要重复定义相同的属性或功能,开发团队采用了将公共方法和定义抽离至 `core` 文件夹的策略。
当处理组件的通用属性时,我们发现像 `nzSize` 这样的属性在多个组件如 `Input` 和 `Button` 中被广泛使用。解决这一问题的方法在于引入 `types` 文件夹,这个文件夹记录了哪些组件支持特定属性,便于我们查询和重复利用。
动画效果是 Angular 开发中常见的元素,Angular 官方文档提供了详尽的指南。NG-ZORRO 提供了多样化的动画,使页面元素呈现丰富的动态变化。例如在 `Collapse` 折叠面板组件中,通过 `nzActive` 属性操控动画状态,实现元素的展开与收起效果。这一功能在实际开发中非常实用,使用动画使页面交互更加直观。
某些组件,如 `Tag`,在其动态删除操作中应用了淡入淡出动画,该动画机制相较于需要单独配置的状态传递更为简便,直接提升视觉效果和用户体验。NG-ZORRO 内含多种动画类型,如 `moveUpMotion` 和 `slideMotion`,通过探索源码可以轻易找到使用方式。
对于不希望使用动画的场景,NG-ZORRO 提供了 `NzNoAnimationDirective`,允许开发者在模板层面对特定元素禁用动画效果。通过替换 `BrowserAnimationsModule` 为 `NoopAnimationsModule`,可实现全局禁用动画。
总结这一系列核心文件夹——`core` 包含了如 `types` 和 `animations` 等内容,对于项目开发而言,应考虑抽离公共部分,实现跨组件复用,以减少代码冗余和提高开发效率。通过借鉴 NG-ZORRO 的实践,开发者可以优化代码结构,提升组件复用性,同时保持代码的简洁性和易维护性。
Lifecycle源码解析
作者:Gs 转载地址: /post/
1、猜想
如果是我们实现Lifecycle的功能,我们会如何设计?
2、入口
既然Activity或者Fragment作为生命周期的所有者,并且在他们中增加了LifecycleObserver,那么我们就从Activity或者Fragment作为探索Lifecycle原理的入口。在Activity或者Fragment中使用Lifecycle时,我们通常会看到如下代码:
我们进入getLifecycle()方法。注:以Activity中的代码为例。
这是Activity的父类ComponentActivity中的代码:
getLifecycle()返回的mLifecycleRegistry,直接使用new创建。
LifecycleRegistry的构造方法必须传递LifecycleOwner参数。而ComponentActivity已经实现了LifecycleOwner接口,所以可以直接
LifecycleOwner接口很简单,只有一个getLifecycle()抽象方法。
所以我们的Activity或者Fragment作为生命周期的所有者,同时也实现了LifecycleOwner接口,通过getLifecycle()方法获取LifecycleRegistry对象,LifecycleRegistry也就是实现生命周期分发的类。
LifecycleRegistry在lifecycle-runtime包中。
3、生命周期事件分发
我们看到Activity的父类ComponentActivity实现了LifecycleOwner接口,并且创建了LifecycleRegistry对象。那么生命周期的分发也应该在ComponentActivity的各个生命周期方法中吧。然而,我们看到ComponentActivity中只复写了onCreate()方法,没有其他生命周期方法。
里面有一句代码
ReportFragment不是在上面中和LifecycleRegistry在lifecycle-runtime包中一起出现的吗?所以ReportFragment一定是为了实现Lifecycle功能。
injectIfNeededIn()方法很简单,就是创建ReportFragment加入到Activity中。但是它里面包含了各个生命周期方法,而且都调用了分发方法dispatch()。参数就是我们在自定义LifecycleObserver中给方法加的注释事件。
至此,我们找到了生命周期事件的分发方法dispatch(Event event),方法内部使用LifecycleRegistry的handleLifecycleEvent(event)分发事件。上面我们也说过LifecycleRegistry就是实现生命周期分发的类。而ReportFragment的作用就是获取生命周期而已,因为Fragment生命周期是依附Activity的。好处是把这部分逻辑抽离出来,实现Activity的无侵入。如果你对加载库Glide比较熟悉,就会知道它也是使用透明Fragment获取生命周期的。
4、生命周期事件处理
LifecycleRegistry继承自Lifecycle。
Lifecycle使用两种主要枚举跟踪其关联组件的生命周期状态:
Event触发的时机:
您可以将状态看作图中的节点,将事件看作这些节点之间的边。上一节中,我们知道ReportFragment生命周期发生变化时,都会调用LifecycleRegistry中的handleLifecycleEvent()方法。因此,我们先看一下handleLifecycleEvent()方法。