Angular 4.x + Cordova:FileReader无声地失败(白屏死机)

n00*_*dl3 3 filereader cordova zone.js angular

我有一个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;
                }
            });
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

为什么会发生这种情况,如何解决这个问题,以便我的回调被触发?

n00*_*dl3 8

在调试此代码时,我意识到zone.js/zone-patch-cordova构造函数已被cordova和zone.js修补.从我的理解有关zone.js补丁是改变每一个"onProperty"( ,FileReader,onload)onloadend它的onerror对应物.

模块名称:

on_property

zone.js的行为:

addEventListener(...) 将成为区域意识 target.onProp

资源

但是cordova没有使用target.addEventListener(prop)API来通知侦听器操作已经结束.

一种解决方案可能是dispatchEvent(...)从zone.js 停用模块,但它可能会破坏角度的行为.

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

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;
                }
            });
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

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

这个解决方案是一个肮脏的黑客,我在区域的存储库上打开了一个问题

编辑:

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

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;
                }
            });
        });
    }));
}
Run Code Online (Sandbox Code Playgroud)