【问题标题】:Angular 4.x + Cordova : FileReader fails silently (white screen of death)Angular 4.x + Cordova:FileReader 静默失败(白屏死机)
【发布时间】:2018-01-14 11:56:04
【问题描述】:

我有一个以前运行良好的 Angular 4.3 + Cordova 应用程序。但是现在,我在应用启动时出现空白屏幕,并且什么也没有发生。

挖掘了一段时间后,我意识到它来自哪里:

我的主页受到CanActivate 保护,如果这是第一次运行或缺少所需的首选项,它将检查一些文件系统持久的首选项并将用户重定向到另一个页面,以填写必需的属性。

所以应用程序的启动取决于我的CanActivate 守卫,而PreferenceService 本身又取决于我自己实现的FileSystemService。问题是,当我尝试读取存储用户首选项的文件时,没有触发任何回调,没有任何反应,甚至没有错误

这是我的FileSystemService 中没有任何错误的部分:

read(file: FileEntry, mode: "text" | "arrayBuffer" | "binaryString" | "dataURL" = "text"): Observable<ProgressEvent> {
    return this.cdv.ready.flatMap(() => {
        return Observable.create(observer => {
            file.file(file => {
                let reader = new FileReader();
                reader.onerror = (evt: ErrorEvent) => {
                    this.zone.run(() => observer.error(evt)); //never triggered
                };
                reader.onload = (evt: ProgressEvent) => {
                    this.zone.run(() => observer.next(evt)); //never trigerred
                };
                switch (mode) {
                    case "text":
                        reader.readAsText(file);
                        break;
                    case "arrayBuffer":
                        reader.readAsArrayBuffer(file);
                        break;
                    case "binaryString":
                        reader.readAsBinaryString(file);
                        break;
                    case "dataURL":
                        reader.readAsDataURL(file);
                        break;
                }
            });
        });
    });
}

为什么会发生这种情况,我该如何处理才能触发我的回调?

【问题讨论】:

    标签: angular cordova filereader zone.js


    【解决方案1】:

    编辑

    如前所述,我在 zone.js 存储库上打开了一个问题,zone.js 所有者迅速修补了代码。只需在你的 polyfill 中导入 zone.js/zone-patch-cordova,你就可以避免使用我的肮脏 hack 的痛苦。

    原答案

    在调试此代码时,我意识到 FileReader 构造函数已被 cordova 和 zone.js 修补。根据我对 zone.js 修补的理解,它会将每个“onProperty”(onloadonloadendonerror)更改为 addEventListener(...) 对应部分。

    模块名称:

    on_property

    zone.js 的行为:

    target.onProp 将成为区域感知 target.addEventListener(prop)

    source

    但是cordova没有使用dispatchEvent(...) API来通知监听器操作已经结束。

    一种解决方案可能是从 zone.js 停用 onProperty 模块,但它可能会破坏 angular 的行为

    这就是我应对这种情况的方式:

    read(file: FileEntry, mode: "text" | "arrayBuffer" | "binaryString" | "dataURL" = "text"): Observable<ProgressEvent> {
        return this.cdv.ready.flatMap(() => {
            return Observable.create(observer => {
                file.file(file => {
                    let FileReader: new() => FileReader = ((window as any).FileReader as any).__zone_symbol__OriginalDelegate
                    let reader = new FileReader();
                    reader.onerror = (evt: ErrorEvent) => {
                        this.zone.run(() => observer.error(evt)); //never triggered
                    };
                    reader.onload = (evt: ProgressEvent) => {
                        this.zone.run(() => observer.next(evt)); //never trigerred
                    };
                    switch (mode) {
                        case "text":
                            reader.readAsText(file);
                            break;
                        case "arrayBuffer":
                            reader.readAsArrayBuffer(file);
                            break;
                        case "binaryString":
                            reader.readAsBinaryString(file);
                            break;
                        case "dataURL":
                            reader.readAsDataURL(file);
                            break;
                    }
                });
            });
        });
    }
    

    这里的秘密是 zone.js 将原始构造函数保留在 __zone_symbol__OriginalDelegate 属性中,因此调用它实际上会直接调用 Cordova 的 FileReader无需 zone.js 补丁

    这个解决方案是一个肮脏的黑客,我有openned an issue on zone's repository

    编辑:

    FileWriter 也有同样的问题(它在内部调用 FileReader)所以我写了这个小垫片:

    function noZonePatch(cb: () => void) {
        const orig = FileReader;
        const unpatched = ((window as any).FileReader as any).__zone_symbol__OriginalDelegate;
        (window as any).FileReader = unpatched;
        cb();
        (window as any).FileReader = orig;
    }
    

    然后包装我对读/写操作的调用:

    write(file: FileEntry, content: Blob) {
        return this.cdv.ready.flatMap(() => {
            return Observable.create((out: Observer<ProgressEvent>) => {
                file.createWriter((writer) => {
                    noZonePatch(() => {
                        writer.onwrite = (evt: ProgressEvent) => {
                            this.zone.run(() => {
                                out.next(evt);
                                out.complete();
                            });
                        };
                        writer.onerror = (evt) => {
                            this.zone.run(() => out.error(evt));
                        };
                        writer.write(content); // this is where FileReader is called internally
                    })
                }, err => out.error(err));
            });
        });
    }
    
    read(file: FileEntry, mode: ReadMode = "text"): Observable<ProgressEvent> {
        return this.cdv.ready.switchMap(() => Observable.create((observer: Observer<ProgressEvent>) => {
            file.file(file => {
                noZonePatch(() => {
                    let reader = new FileReader();
                    reader.onerror = (evt: ErrorEvent) => {
                        this.zone.run(() => observer.error(evt));
                    };
                    reader.onload = (evt: ProgressEvent) => {
                        this.zone.run(() => observer.next(evt));
                    };
                    switch (mode) {
                        case "text":
                            reader.readAsText(file);
                            break;
                        case "arrayBuffer":
                            reader.readAsArrayBuffer(file);
                            break;
                        case "binaryString":
                            reader.readAsBinaryString(file);
                            break;
                        case "dataURL":
                            reader.readAsDataURL(file);
                            break;
                    }
                });
            });
        }));
    }
    

    【讨论】:

    • 你是个天才。我花了 3 天的时间来调试这个问题。我看过至少 20 种不同的解决方案,但都失败了。我正在使用 Angular 5,当我使用“ng build --prod”构建我的应用程序时,我的“FileReader”例程失败,因为文件被传递给“onload”而不是事件 [event.target & event.目标.结果]。你的一行闪耀的光彩解决了这个问题。我不完全确定为什么,但该事件及其所有属性现在已正确传递到“onload”中,我怀疑这适用于所有 FileReader 的其他事件。非常感谢...
    • 请检查我在 zonejs 的 repo 上打开的问题,他们添加了一个补丁,所以升级区域并添加补丁可能会解决这个问题,而无需我的肮脏黑客。
    • 感谢您的提醒,但我正在使用 Angular,所以我不确定补丁是否是解决方案,因为我认为 Angular 已经加入了区域。我可能必须升级到 Angular 6,这有点吓人!
    【解决方案2】:

    聚会迟到了,但我不得不修复一个带有这个确切问题的 Ionic3/Angular4 项目,我发现 the answer from @n00dl3 是正确的但是当@ 987654322@ 实例是在全局服务中创建的。因为有时区域尚未修补 FileReader window 对象,因此找不到 __zone_symbol__OriginalDelegate

    所以我所做的总是得到正确的类是一个返回 FileReader 实例的小工厂函数:

    function HackFileReader(): FileReader {
      const preZoneFileReader = ((window as any).FileReader as any).__zone_symbol__OriginalDelegate;
      if (preZoneFileReader) {
        console.log('%cHackFileReader: preZoneFileReader found creating new instance', 'font-size:3em; color: red');
        return new preZoneFileReader();
      } else {
        console.log('%cHackFileReader: NO preZoneFileReader was found, returning regular File Reader', 'font-size:3em; color: red');
        return new FileReader();
      }
    }
    

    并使用它:

    const reader = HackFileReader();
    

    希望对大家有所帮助

    【讨论】:

    • 你不应该再使用那个黑客了,因为 ZoneJS 团队已经发布了一个补丁。我会在我的答案中添加这个细节
    • @n00dl3 当代码库很旧并且您不允许更新依赖项时,是的,您需要它:(
    【解决方案3】:

    如果您使用的是 ionic/cordova,则不需要 @disante 的 HackFileReader 解决方案(我实际使用过)

    您需要做两件事,首先是确保您拥有最新的 zone.js。

    npm install --save zone.js@latest
    

    其次,您需要确保您的index.html 添加cordova.js AFTER build/polyfills.js

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-24
      • 1970-01-01
      • 2012-01-03
      • 1970-01-01
      • 1970-01-01
      • 2020-01-04
      • 2013-11-20
      相关资源
      最近更新 更多