单例模式在api9上无法使用问题分析报告

鸿蒙小紫娃 2023-03-13 18:45:21

1 关键字

单例模式;打包;公共代码提取;webpack

2 问题描述

系统版本:Openharmony-3.1release

sdk版本:API9 - 3.1.5.5Release

问题现象:单例对象在Stage模型中无法使用,FA模型中可以使用

测试步骤:

  1. 创建单例对象类:
export default class Single {
    private static _routerUtil: Single = new Single();
    num: number = 0;
    static instance(): Single{
        return this._routerUtil
    }
    constructor() {
        console.log("Single constructor ");
    }
    add() {
        this.num ++;
    }
}
  1. 分别在pageA和pageB页面引入,调用instance方法获取对象。

  2. Stage模型下pageA和pageB通过instance方法获取的对象之间数据num是不互通的,在FA模型下pageA和pageB获取的对象是一个对象,Single的实例化对象的num属性在两个页面之间互通。

3 问题原因

两种模式打包时,webpack对两种模式打包进行了区分,Stage模型下没有提取公共部分代码,每次引入公共代码,最终会复制一份到使用的地方。FA模型下进行了公共部分代码提取,多次引用的代码会被单独打包到公共文件中。

4 定位过程

  1. 创建pageA和pageB,并引入单例对象Single
// pageA.ets
import router from '@ohos.router';
import Single from './Single';
const single = Single.instance();
@Entry
@Component
struct PageA {
    @State message: string = "Hello World"
    aboutToAppear() {
        console.log("进入 pageA 打印single.num");
        console.log("pageA single.num : " + single.num);
    }
    add() {
        console.log("pageA 点击 add 后打印single.num");
        single.add();
        console.log("pageA single.num : " + single.num);
    }
    build() {
        Row() {
            Column({ space: 20 }) {
                Button("pageA 点击 add").fontSize(32).onClick(() => {
                    this.add();
                })
                Button("跳转到 pageB").fontSize(32).onClick(() => {
                    console.log("pageB 跳转到 pageB");
                    router.push({
                        url: "pages/pageB"
                    })
                })
            }.width("100%")
        }.height("100%")
    }
}
// pageB.ets
import router from '@ohos.router';
import Single from './Single';
const single = Single.instance();
@Entry
@Component
struct PageB {
    aboutToAppear() {
        console.log("进入 pageB 打印single.num");
        console.log("pageB single.num : " + single.num);
    }
    add() {
        console.log("pageB 点击 add 后打印single.num");
        single.add();
        console.log("pageB single.num : " + single.num);
    }
    build() {
        Row() {
            Column({ space: 20 }) {
                Button("pageB 点击 add").fontSize(32).onClick(() => {
                    this.add();
                })
                Button("跳转到 pageA").fontSize(32).onClick(() => {
                    console.log("pageB 跳转到 pageA");
                    router.push({
                        url: "pages/pageA"
                    })
                })
            }.width("100%")
        }.height("100%")
    }
}
  1. FA模型下打开页面点击测试按钮后查看日志
// /entry/build-profile.json5
{
    "apiType": 'faMode', // FA模型
    "buildOption": {},
    "targets": [{
            "name": "default",
        },
        {
            "name": "ohosTest",
        }
    ]
}
// 第一次进入pageA页面打印了初始的 single.num 0
[phone][Console   DEBUG]  06/17 10:39:49 4008   app Log: Single constructor 
[phone][Console   DEBUG]  06/17 10:39:49 4008   app Log: 进入 pageA 打印single.num
[phone][Console   DEBUG]  06/17 10:39:49 4008   app Log: pageA single.num : 0

// 点击三次 pageA的add按钮,每次打印最新的single.num
[phone][Console   DEBUG]  06/17 10:39:58 4008   app Log: pageA 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 10:39:58 4008   app Log: pageA single.num : 1
[phone][Console   DEBUG]  06/17 10:39:58 4008   app Log: pageA 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 10:39:58 4008   app Log: pageA single.num : 2
[phone][Console   DEBUG]  06/17 10:39:58 4008   app Log: pageA 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 10:39:58 4008   app Log: pageA single.num : 3

// 点击 跳转到pageB 按钮,进入pageB页面后打印了 signle.num 为 3 
[phone][Console   DEBUG]  06/17 10:40:01 4008   app Log: pageB 跳转到 pageB
[phone][Console   DEBUG]  06/17 10:40:01 4008   app Log: 进入 pageB 打印single.num
[phone][Console   DEBUG]  06/17 10:40:01 4008   app Log: pageB single.num : 3

// 点击三次 pageB的add按钮,每次打印最新的single.num
[phone][Console   DEBUG]  06/17 10:40:09 4008   app Log: pageB 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 10:40:09 4008   app Log: pageB single.num : 4
[phone][Console   DEBUG]  06/17 10:40:10 4008   app Log: pageB 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 10:40:10 4008   app Log: pageB single.num : 5
[phone][Console   DEBUG]  06/17 10:40:11 4008   app Log: pageB 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 10:40:11 4008   app Log: pageB single.num : 6

// 点击 跳转到pageA 按钮,进入pageA页面后打印了 signle.num 为 6
[phone][Console   DEBUG]  06/17 10:40:14 4008   app Log: pageB 跳转到 pageA
[phone][Console   DEBUG]  06/17 10:40:14 4008   app Log: 进入 pageA 打印single.num
[phone][Console   DEBUG]  06/17 10:40:14 4008   app Log: pageA single.num : 6

经过日志可以看出在两个页面切换时获取到的single.num的数据是互通的,两个页面之间共用了一个single对象。

查看编译后的代码\entry\build\default\intermediates\assets\default\js\MainAbility\commons.js中将Single作为模块导出:

Object.defineProperty(exports, "__esModule", ({ value: true }));
class Single {
    constructor() {
        this.num = 0;
        console.log("Single constructor ");
    }
    static instance() {
        return this._routerUtil;
    }
    add() {
        this.num++;
    }
}
exports["default"] = Single;
Single._routerUtil = new Single();

在 \entry\build\default\intermediates\assets\default\js\MainAbility\pages\pageA.js中没有Single代码,Single通过引入的方式使用:

const Single_1 = __importDefault(__webpack_require__( /*! ./Single */ "D:\\codeOpenHarmony\\demo1\\entry\\src\\main\\ets\\MainAbility\\pages\\Single.ts"));
const single = Single_1.default.instance();

pageB.js与pageA.js一样是通过引入的方式使用。由此看出在FA模型下,公共使用的代码会打包到commons.js文件中。

  1. Stage模型下打开页面点击测试按钮后查看日志
// /entry/build-profile.json5
{
    "apiType": 'stageMode', // Stage模型
    "buildOption": {},
    "targets": [{
            "name": "default",
        },
        {
            "name": "ohosTest",
        }
    ]
}
// 第一次进入pageA页面打印了初始的 single.num 0
[phone][Console   DEBUG]  06/17 11:07:14 12072  app Log: Single constructor 
[phone][Console   DEBUG]  06/17 11:07:14 12072  app Log: 进入 pageA 打印single.num
[phone][Console   DEBUG]  06/17 11:07:14 12072  app Log: pageA single.num : 0

// 点击三次 pageA的add按钮,每次打印最新的single.num
[phone][Console   DEBUG]  06/17 11:07:24 12072  app Log: pageA 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 11:07:24 12072  app Log: pageA single.num : 1
[phone][Console   DEBUG]  06/17 11:07:25 12072  app Log: pageA 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 11:07:25 12072  app Log: pageA single.num : 2
[phone][Console   DEBUG]  06/17 11:07:26 12072  app Log: pageA 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 11:07:26 12072  app Log: pageA single.num : 3

// 点击 跳转到pageB 按钮,进入pageB页面后打印了 signle.num 为 0 
[phone][Console   DEBUG]  06/17 11:07:28 12072  app Log: pageB 跳转到 pageB
[phone][Console   DEBUG]  06/17 11:07:28 12072  app Log: Single constructor 
[phone][Console   DEBUG]  06/17 11:07:28 12072  app Log: 进入 pageB 打印single.num
[phone][Console   DEBUG]  06/17 11:07:28 12072  app Log: pageB single.num : 0

// 点击三次 pageB的add按钮,每次打印最新的single.num
[phone][Console   DEBUG]  06/17 11:07:31 12072  app Log: pageB 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 11:07:31 12072  app Log: pageB single.num : 1
[phone][Console   DEBUG]  06/17 11:07:32 12072  app Log: pageB 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 11:07:32 12072  app Log: pageB single.num : 2
[phone][Console   DEBUG]  06/17 11:07:32 12072  app Log: pageB 点击 add 后打印single.num
[phone][Console   DEBUG]  06/17 11:07:32 12072  app Log: pageB single.num : 3

// 点击 跳转到pageA 按钮,进入pageA页面后打印了 signle.num 为 0
[phone][Console   DEBUG]  06/17 11:07:34 12072  app Log: pageB 跳转到 pageA
[phone][Console   DEBUG]  06/17 11:07:34 12072  app Log: Single constructor 
[phone][Console   DEBUG]  06/17 11:07:34 12072  app Log: 进入 pageA 打印single.num
[phone][Console   DEBUG]  06/17 11:07:34 12072  app Log: pageA single.num : 0

由打印日志可以看出,每次进入页面signle.num 都为 0并且还打印了Single constructor ,由此判断出single在页面间并不互通。

查看编译后的文件,在\entry\build\default\intermediates\assets\default\ets\目录下没有发现common相关文件。

打开\entry\build\default\intermediates\assets\default\ets\pages\目录下的pageA.js和pageB.js,发现在两个文件中均存在以下代码:

Object.defineProperty(exports, "__esModule", ({ value: true }));
class Single {
    constructor() {
        this.num = 0;
        console.log("Single constructor ");
    }
    static instance() {
        return this._routerUtil;
    }
    add() {
        this.num++;
    }
}
exports["default"] = Single;
Single._routerUtil = new Single();

pageA.js和pageB.js中使用的Single均为自己创建的Single。由此判断出在Stage模式下没有提取公共使用的代码。

  1. 进一步深入查看编译的部分,找到SDK中的编译工具\SDK\ets\3.1.5.5\build-tools\ets-loader,此文件夹下存在webpack.config.js,判断出项目编译打包是基于webpack编译打包的,打开webpack.config.js。查找提取公共代码的配置项splitChunks,在setOptimizationConfig方法中对FA和Stage模型进行了区分。代码如下:
function setOptimizationConfig(config) {
    // 根据模型判断是否需要添加公共代码提取,moduleJson为Stage模型下的配置文件名称
    if (process.env.compileMode !== 'moduleJson') {
        config.optimization = {
            splitChunks: {
                // 屏蔽workers和TestAbility
                chunks(chunk) {
                    return !/^\.\/workers\//.test(chunk.name) && !/^\.\/TestAbility/.test(chunk.name);
                },
                // 生成 chunk 的最小体积
                minSize: 0,
                cacheGroups: {
                    // 提取node_modules中使用的库打包到vendors.js中
                    vendors: {
                        // 匹配的文件夹
                        test: /[\\/]node_modules[\\/]/,
                        // 优先级
                        priority: -10,
                        // 文件名称
                        name: "vendors",
                    },
                    commons: {
                        name: 'commons',
                        priority: -20,
                        // 拆分前必须共享模块的最小 chunks 数。
                        minChunks: 2,
                        // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块。
                        reuseExistingChunk: true
                    }
                }
            }
        }
    }
}

由代码可判断出,在FA模型中webpack配置了splitChunks,Stage模型中屏蔽了此部分配置。因此FA模型在打包时会提取公共代码,而Stage模型下没有提取公共部分代码。

5 知识分享

OpenHarmony应用开发在打包的时候是使用webpack进行代码的解析编译,目前webpack在公共代码提取的部分对FA和Stage两种模型做了一定的区分。

...全文
13 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

421

社区成员

发帖
与我相关
我的任务
社区描述
OpenHarmony开发者社区
其他 企业社区
社区管理员
  • csdnsqst0025
  • shewaliujingli
  • BaoWei
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧