本质上,webpack 基于node平台,利用 node 的各种api来实现 javascript 应用程序的一个静态模块的打包工具。
在打包过程中,构建依赖关系,并且实现模块引用预处理,以及缓存等。
2、分析
1、人口文件
// mian.js
const a = require('./m1')
const b= require('./m2')
import { test } from './m1'
console.log(test)
//m2.js
export default {
b:2
}
//m1.js
export const test = {test:1}
export default {
a:1
}
2、生产的文件
(function (modules) {
var installedModules = {}//缓存
/*
* 加载模块函数
* 传入模块id
* */
function __webpack_require__(moduleId) {
// 检查缓存中是否有模块
if (installedModules[moduleId]) {
return installedModules[moduleId].exports
}
// 创建一个新模块,并缓存起来
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
}
// 调模块的函数,modules
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
module.l = true
// 返回对应模块
return module.exports
}
__webpack_require__.m = modules
__webpack_require__.c = installedModules
__webpack_require__.d = function (exports, name, getter) {
if (!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, {
configurable: false,
enumerable: true,
get: getter
})
}
}
__webpack_require__.n = function (module) {
var getter = module &&module.__esModule ?
function getDefault() {
return module['default']
} :
function getModuleExports() {
return module
}
__webpack_require__.d(getter, 'a', getter)
return getter
}
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property)
}
__webpack_require__.p = ""
// 加载入口文件
return __webpack_require__(__webpack_require__.s = 0)
})
([
(function (module, exports, __webpack_require__) {
const a = __webpack_require__(1)
const b = __webpack_require__(2)
}),
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
Object.defineProperty(__webpack_exports__, "__esModule", {value: true})
__webpack_exports__["default"] = ({
a: 1
})
}),
(function (module, __webpack_exports__, __webpack_require__) {
"use strict"
Object.defineProperty(__webpack_exports__, "__esModule", {value: true})
__webpack_exports__["default"] = ({
b: 2
})
})
])
观察以上代码得到结果:
1、打包后的代码是一个立即执行函数,且传入的参数为一个数组
2、参数数组就是我们引用的模块
3、每一个模块对应着数组的位置就是那么的id
4、在立即函数中加载入口文件,并执行
__webpack_require__ : 加载并执行某一个模块并将模块缓存在 installedModules 中。
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__)
这里是执行引用的某一个模块。
并将module,exports,require 加入模块中。
这也是为什么我们在模块中有全局变量 module/exports/require
通过对打包后的文件分析,基本可以完全理解打包过程。
SpringBoot 提供了 Maven 插件 spring-boot-maven-plugin,将 Spring Boot 项目打成 jar 包或者 war 包。只需要在pom.xml文件中加入下面这个插件配置,再通过mvn clean package获取jar包即可。
打包后 通过下面的命令即可启动一个服务。
可以看到,主要有三个大目录META-INF,BOOT-INF以及org,
比较重要的是MAINIFEST.MF文件:
该文件声明了Main-Class 配置项:可以理解为jar包的启动类,这里设置为 spring-boot-loader 项目的 JarLauncher类,进行 Spring Boot 应用的启动。
还有一个Start-Class 配置项:配置的内容是我们springboot项目的主启动类。
classes文件中保存了 Java 类所编译的 .class文件以及配置文件等。
lib目录中保存了我们项目所依赖的jar包。
该文件中即springboot为我们提供的jar包启动类,亦即JarLauncher.class
当使用 java -jar filename.jar 命令启动时,会执行封装在 JAR 文件中的程序。JAR 文件需包含 manifest,其中一行格式为 Main-Class:classname,指定了一个包含 public static void main(String[] args) 方法的类,作为该程序的启动点。
对应在示例的这个项目,问题可以翻译为为什么不可以直接使用com.jsvc.jarlearn.JarlearnApplication类作为启动类?
主要是因为,Java 没有提供任何加载嵌套 jar 文件的标准方法(即加载本身包含在 jar 中的 jar 文件)。当需要分发一个可以从命令行运行而不需要解压缩的自包含应用程序时 , 会出现问题。
同时,我试了下,直接运行application类的话,是找不到主类的:
因为在文件目录中,JarlearnApplication实际上是在META-INF/maven/... 中的,所以会找不到。
所以,springboot以 org.springframework.boot.loader.JarLauncher 为启动类,
又自定义了 LaunchedURLClassLoader 用来加载BOOT-INF中的class文件以及BOOT-INF/lib中的嵌套jar包。
我这边通过引入 spring-boot-loader 模块来看下JarLaunch的源码:
可以看到main方法中,执行了launch方法,改方法由JarLaunch的父类Launcher提供:
launch方法主要分为三步:
基本思路就是将 org.springframework.boot.loader 包路径添加到 java.protocol.handler.pkgs 环境变量中,从而使用自定义的 URLStreamHandler 实现类 Handler处理 jar: 协议的 URL。
关于handler 可以自行百度下。
这里有两个主要方法:
也就是 getClassPathArchivesIterator 以及 createClassLoader
首先是 getClassPathArchivesIterator :
首先是isSearchCandidate,在JarLaunch中实现:
可以看出是只处理BOOT-INF/文件夹下的内容。
然后会通过 getNestedArchives 获取到嵌套的Archive,其中的 isNestedArchive 方法也由JarLaunch实现:
基本就是获取 BOOT-INF/classes/ 下的目录以及 BOOT-INF/lib/ 下的jar文件,最终通过 getNestedArchives 将其封装为对应的Archive并返回。
然后就是 createClassLoader 方法:
基本上就是通过archives获取到所有的URL,然后创建处理这些URL的ClassLoader。
主要就是通过 getMainClass 方法获取到manifest文件中配置的 Start-Class :
然后通过另一个launch方法,开始执行:
这里createMainMethodRunner创建出来的是什么呢?
最终调用的其实就是MainMethodRunner的run方法了,其实也就是通过反射调用Application的main方法了。
1.传统打包:传统的打包方法都是在AndroidManifest添加渠道标示,每打一次包修改一次标示的名称。效率特别的低,一个稍微大一点的项目打上几十个渠道包可能需要几个小时半天的时间。
2.由于传统的打包方式每次修改渠道都需要重新的构建项目,时间都浪费构建上面了,美团提供了一种新的打包方案:
Android应用使用的APK文件就是一个带签名信息的ZIP文件,根据 ZIP文件格式规范,每个ZIP文件的最后都必须有一个叫 Central Directory Record 的部分,这个CDR的最后部分叫”end of central directory record”,这一部分包含一些元数据,它的末尾是ZIP文件的注释。注释包含Comment Length和File Comment两个字段,前者表示注释内容的长度,后者是注释的内容,正确修改这一部分不会对ZIP文件造成破坏,利用这个字段,我们可以添加一些自定义的数据,Packer-Ng方式打包就是在这里添加和读取渠道信息。打包神器,100个渠道包只需5s 哈哈 。
原理很简单,就是将渠道信息存放在APK文件的注释字段中。
第一步:直接将PackerNg作为Utils拷贝到项目中。
第二步:创建一个保存渠道包名的txt文件,可以放在项目主目录下:比如命名market.txt
渠道名可以按照需求随便添加
anzhi
baidu
huawei
legend
letv
meizu
oppo
PC
sougou
UC
update
update1
vivo
wandoujia
woshangdian
xiaomi
第三步:ChannelUtil这个工具类是用于取出文件里的渠道名
第四步:打开第二步中的PackerNg类,首先配置一下此类main函数中接受的参数信息。本事例通过Android Studio的方式进行配置直接上图:
图中标注3的位置就是PackerNg类配置main函数中接受的两个参数: 第一个参数为默认的release包的apk源文件,包名为ChannelUtil起初默认的包名
拿到这个包名可以传给后台进行统计或进行其它的 *** 作。
第六步:运行PackerNg类,会在项目目录下自动生成文件夹apks(在PackerNg.java文件中配置好的apk渠道包存储路径)
注意点:第四步中ChannelUtil起初默认的包名为源文件,其它所有的的渠道包都是通过PackerNg打包方式都是以这个源文件为模版,进行复制,将不同的渠道名复制给这个源文件。如果是360渠道上线的话需要将这个包名默认改为360的渠道单独打包,因为360上线需要加固,会把之前通过源文件复制渠道名给抹掉,所以对于360加固的文件需要单独把360作为源文件来打包不改为360默认的渠道包后会统计不到360渠道的信息。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)