最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Android、RN打包原理分析

    正文概述 掘金(江伟斌123)   2021-08-03   27

    目录:

    Android打包原理

    Android apk文件夹目录结构

    Android打包脚本gradle执行流程流程

    App安装流程

    RN打包原理

    RN bundle文件结构

    metro打包流程

    RN拆包原理

    Android、RN打包比较

    Android、RN打包比较

    在分析一件事物原理的时候我喜欢从结果开始逆向推理过程,这样更容易理解

    Android打包原理

    Android apk文件夹目录结构

    Android、RN打包原理分析

    Android、RN打包原理分析

    无图无真相,通过AS或者解压打包好的apk目录如上图

    lib

    lib目录下存放的是底层代码C/C++编译出来的so文件,如音视频、人脸识别等,其中x86、arm代表兼容不同的芯片类型。

    由于java性能有限,很多对性能要求比较高的功能都需要通过C/C++实现,so同样增强了app的安全性,不容易被反编译。

    so库由于无法被压缩是整个apk包体积中占比最大的,很多app只兼容armev7来减少包大小(google play开放了bundle功能,可根据用户手机选择下载对应型号的app),也有通过动态下发部分功能不是很重要的so库来达到优化包大小的目的。

    res

    Android的资源文件目录,包含布局、动画

    assets

    这里保存的是打包过程不能被压缩需要保留的原始文件,比如字体文件,RN的本地bundle文件也经常放这里。

    dex文件

    .dex文件是Android系统运行在Dalvik Virtual Machine上的可执行文件,也是Android爱普的核心。项目的Java源码通过javac生成class文件,在通过dx工具生成为classes.dex文件。

    AndroidManifest.xml

    这个文件可以意为清单文件或者全局配置文件。里面有很多应用的配置信息,权限、版本号、四大组件的注册也在其中。

    META-INF文件夹

    该目录主要作用就是用于保证APK的完整性和安全性。主要有三个文件:

    MANIFEST.MF:保存了整个apk文件中所有文件的文件名+SHA-1后的base64编码值。象征着apk的完整性。

    CERT.RSA:保存了公匙和加密方式的信息。

    CERT.SF:这个文件与MANIFEST.MF的结构一样,只是其编码会被私匙加密。每次安装时,通过该文件夹中的文件,就可以完成验证的过程。如果apk包被改变了,而篡改者没有私匙生成的CERT.SF,则无法完成校验。

    resource.arsc文件

    该文件是所有文件中结构最复杂的。

    它记录了资源文件,资源文件位置和资源id的映射关系。并且将所有的string都存放在了string pool中,节省了在查找资源时,字符串处理的开销。

    Android打包脚本gradle执行流程流程

    先借用一张官方Android打包图镇楼

    Android、RN打包原理分析

    如上图所示:

    1.打包资源文件,生成R.java文件

    R.java是资源文件引用的一个映射,没一个资源都会在编译的过程中生成一个唯一id

    打包资源文件的工具是aapt(The Android Asset Packing Tool),位于android-sdk/platform-tools目录下。 在这个过程中,项目中的AndroidManifest.xml文件和布局文件xml都会编译生成相应的R.java。

    同时还有编译生成resources.arsc和uncompiled res文件(二进制文件 & 非二进制文件) 
    非二进制文件(eg:res/raw、res/pic)保持原样。

    assets资源文件内容保持原样。

    2.处理AIDL文件,生成相应的java文件

    这个过程使用的工具是aidl(Android Interface Definition Language),位于android-sdk/platform-tools目录下。

    aidl工具解析接口定义文件,然后生成相应的java接口,供程序调用。
    如果项目中没有使用到aidl文件,那么这个过程可以跳过。
    

    3.编译项目源代码,生成.class文件

    项目中所有的java文件,包括R.java文件和**.aidl文件,都会被java编译器(Java Compiler)编译成.class文件。

    生成的class文件位于工程中的bin/classes目录下。
    

    4.转换所有的class文件,生成classes.dex文件

    这个过程使用的工具是dx,该工具位于android-sdk/platform-tools。

    该工具可以生成供Android系统虚拟机的执行文件 classes.dex。

    dx工具主要工作就是将java字节码转换成Dalvik字节码、压缩常量池以及消除冗余信息等。

    任何第三方的lib和.class文件都会被转换成.dex文件

    5.打包生成Apk文件

    所有没有编译过的资源(eg: images)、编译过的资源和.dex文件都会被 apkbuilder 工具打包到最终的.apk文件中去。

    打包工具apkbuilder位于android-sdk/tools目录下。
    
    apkbuilder实际上是一个脚本文件,调用的是android-sdk/tools/lib/sdklib.jar文件中的 com.android.sdklib.build.ApkbuilderMainl类。
    

    6.对Apk文件签名

    apk文件只有被签名才能被安装在设备上。

    签名文件(keystore)有2种

     一种是用于调试的 debug.keystore,开发工具中Run以后在设备上运行的Apk就是debug.keystore签名,在Android sdk中可以找到,是固定的
     一种是用于发布正式版本的keystore,属于开发自行创建申请的证书,起到防止app被冒名顶替的作用
    

    7.对签名后的文件进行对齐处理

    在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,位于android-sdk/tools目录下。

    对齐的主要过程是:
    
    > 将Apk包中的所有资源文件距离文件起始位置偏移4字节整数倍。
    > 对齐之后可以减少运行时内存的使用。
    

    App安装流程

    Android、RN打包原理分析

    1. 复制APK到/data/app目录下,解压并扫描安装包。
    2. 资源管理器解析APK里的资源文件。
    3. 解析AndroidManifest文件,并在/data/data/目录下创建对应的应用数据目录。
    4. 然后对dex文件进行优化,并保存在dalvik-cache目录下。
    5. 将AndroidManifest文件解析出的四大组件信息注册到PackageManagerService中。
    6. 安装完成后,发送广播。

    RN打包原理

    RN bundle文件结构

    Android、RN打包原理分析

    Android、RN打包原理分析

    如上图,生成的bundle大致分为四层

    var层

    包含了当前进程,当前运行环境,bundle启动时间等

    polyfill层

    文件中!(function(r)开头的部分代表的是polyfill层,定义了对 define(__d)、 require(__r)clear(__c) 的支持,以及 module(react-native 及第三方 dependences 依赖的 module) 的加载逻辑;

    模块定义层:

    __d 定义的代码块,包括 RN 框架源码 js 部分、自定义 js 代码部分、图片资源信息,供 require 引入使用

    require层

    r 定义的代码块,找到 d 定义的代码块 并执行

    metro打包流程

    metro 打包的整个流程大致分为:

    Android、RN打包原理分析

    1.命令参数解析

    react-native bundle --dev false  --platform android  --entry-file index.js --config bundle.main.js --bundle-output ./CodePush/index.android.bundle --assets-dest ./CodePush --sourcemap-output ./CodePush/index.android.bundle.map "
    

    platform:对应的平台,android/ios --entry-file: 入口文件 --config:额外配置,拆包有用到 --bundle-output:生成的bundle文件输出位置 --assets-dest:图片等资源文件输出位置 --sourcemap-output:sourcemap映射文件输出位置

    2.metro 打包服务启动

    • 合并 metro 默认配置和自定义配置,并设置 maxWorkers,resetCache,--config就属于用户的额外配置
    • 根据解析得到参数,构建 requestOptions,传递给打包函数
    • 实例化 metro Server
    • 启动 metro 构建 bundle
    • 处理资源文件,解析
    • 关闭 Metro Server

    3.解析和转化

    Metro Server 使用IncrementalBundler进行 js 代码的解析和转换**

    在 Metro 使用IncrementalBundler进行解析转换的主要作用是:

    • 返回了以入口文件为入口的所有相关依赖文件的依赖图谱和 babel 转换后的代码
    • 返回了var 定义部分及 polyfill 部分所有相关依赖文件的依赖图谱和 babel 转换后的代码

    Android、RN打包原理分析

    生成的依赖关系图谱如下

    [
    {
      dependencies: Map(404) { // 入口文件下每个文件所依赖其他文件的关系图谱
        '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js' => {
         {
        inverseDependencies: Set(1) {
          '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js'
        },
        path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js',
        dependencies: Map(8) {
    
          '@babel/runtime/helpers/createClass' => {
            absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/@babel/runtime/helpers/createClass.js',
            data: {
              name: '@babel/runtime/helpers/createClass',
              data: { isAsync: false }
            }
          },
          // ....
          'react' => {
            absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/react/index.js',
            data: { name: 'react', data: { isAsync: false } }
          },
          'react-native' => {
            absolutePath: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/node_modules/react-native/index.js',
            data: { name: 'react-native', data: { isAsync: false } }
          }
        },
        getSource: [Function: getSource],
        output: [
          {
            data: {// 对应文件转换后的代码
              code: `__d(function(g,r,i,a,m,e,d){var t=r(d[0]);Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0;var n=t(r(d[1])),u=t(r(d[2])),l=t(r(d[3])),c=t(r(d[4])),f=t(r(d[5])),o=t(r(d[6])),s=r(d[7]);function y(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}var p=(function(t){(0,l.default)(R,t);var p,h,x=(p=R,h=y(),function(){var t,n=(0,f.default)(p);if(h){var u=(0,f.default)(this).constructor;t=Reflect.construct(n,arguments,u)}else t=n.apply(this,arguments);return(0,c.default)(this,t)});function R(){return(0,n.default)(this,R),x.apply(this,arguments)}return(0,u.default)(R,[{key:"render",value:function(){return o.default.createElement(o.default.Fragment,null,o.default.createElement(s.View,{style:v.body},o.default.createElement(s.Text,{style:v.text},"\u4f60\u597d\uff0c\u4e16\u754c")))}}]),R})(o.default.Component);e.default=p;var v=s.StyleSheet.create({body:{backgroundColor:'white',flex:1,justifyContent:'center',alignItems:'center'},text:{textAlign:'center',color:'red'}})});`,
              lineCount: 1,
              map: [
                [ 1, 177, 9, 0, '_react' ],
                [ 1, 179, 9, 0, '_interopRequireDefault' ],
                [ 1, 181, 9, 0, 'r' ],
                [ 1, 183, 9, 0, 'd' ],
                [ 1, 185, 9, 0 ],
                [ 1, 190, 10, 0, '_reactNative' ],
                // .....
              ],
              functionMap: {
                names: [ '<global>', 'App', 'render' ],
                mappings: 'AAA;eCW;ECC;GDQ;CDC'
              }
            },
            type: 'js/module'
          }
        ]
      }
        },
    
        '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js' => {
          inverseDependencies: [Set],
          path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/App.js',
          dependencies: [Map],
          getSource: [Function: getSource],
          output: [Array]
        },
        '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/app.json' => {
          inverseDependencies: [Set],
          path: '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/app.json',
          dependencies: Map(0) {},
          getSource: [Function: getSource],
          output: [Array]
        }
      },
      entryPoints: [ //入口文件
        '/Users/alexganggao/Desktop/react-native-ssr/ReactNativeSSR/index.js'
      ],
      importBundleNames: Set(0) {}
    }
    
    ]
    

    4.生成

    metro 代码生成部分使用 baseJSBundle 得到代码,并使用 baseToString 拼接最终 Bundle 代码

    在 baseJSBundle 中:

    • baseJSBundle整体调用了三次 processModules分别用于解析出: preCode , postCode 和 modules 其对应的分别是var 和 polyfills 部分的代码 , require 部分的代码 ,  _d 部分的代码
    • processModules 经过两次 filter 过滤出所有类型为 js/类型的数据,第二次过滤使用用户自定义 filter 函数;过滤完成之后使用 wrapModule 转换成_d(factory,moduleId,dependencies)的代码,类似于设计模式中的责任链模式

    baseToString中:

    • 先将 var 及 polyfill 部分的代码使用\n 进行字符串拼接;
    • 然后将_d 部分的代码使用 moduleId 进行升序排列并使用字符串拼接的方式构造_d 部分的代码;
    • 最后合如_r部分的代码

    5.停止打包服务

    停止打包服务

    总结如下几点:

    1. 整个 metro 进行依赖分析和 babel 转换主要通过了JestHasteMap (opens new window)去做依赖分析;
    2. 在做依赖分析的通过,metro 会监听当前目录的文件变化,然后以最小变化生成最终依赖关系图谱;
    3. 不管是入口文件解析还是 polyfill 文件的依赖解析都是使用了JestHasteMap (opens new window);

    RN拆包原理

    在打包生成过程中processModules 经过两次 filter 过滤出所有类型为 js/类型的数据,第二次过滤使用用户自定义 filter 函数;过滤完成之后使用 wrapModule 转换成_d(factory,moduleId,dependencies)的代码,类似于设计模式中的责任链模式

    function createModuleIdFactory() {
      const fileToIdMap = new Map();
      let nextId = 0;
      return path => {
        let id = fileToIdMap.get(path);
    
        if (typeof id !== "number") {
          id = nextId++;
          fileToIdMap.set(path, id);
        }
    
        return id;
      };
    }
    
    module.exports = createModuleIdFactory;
    

    逻辑比较简单,如果查到 map 里没有记录这个模块则 id 自增,然后将该模块记录到 map 中,所以从这里可以看出,官方代码生成 moduleId 的规则就是自增,所以这里要替换成我们自己的配置逻辑,我们要做拆包就需要保证这个 id 不能重复,但是这个 id 只是在打包时生成,如果我们单独打业务包,基础包,这个 id 的连续性就会丢失,所以对于 id 的处理,我们还是可以参考上述开源项目,每个包有十万位间隔空间的划分,基础包从 0 开始自增,业务 A 从 1000000 开始自增,又或者通过每个模块自己的路径或者 uuid 等去分配,来避免碰撞,但是字符串会增大包的体积,这里不推荐这种做法。

    在基础包生成以后,打业务包的时候过滤所有基础包moduleId即可

    function postProcessModulesFilter(module) {
      const path = module["path"];
      for (let i = 0, len = excludeFiles.length; i < len; i++) {
        if (path.indexOf(excludeFiles[i]) >= 0) {
          return false;
        }
      }
      return true;
    }
    

    Android、RN打包比较

    apk,bundle文件都是能够在对应平台安装运行的压缩文件,不管是gradle还是metro都只是担任打包角色,换一种工具一样可行

    两者都提供了开发可以介入打包流程的入口,都是责任链+拦截器模式

    RN的hermes原理是提前预编译减少启动时间,同样Android在5.0以上引入了art也是对apk中的dex文件进行预编译,来减少app启动时长

    RN打包原文参考: blog.gaogangsever.cn/react/react…


    起源地 » Android、RN打包原理分析

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元