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

时间:2017-08-07 08:27:08

标签: angular cordova filereader zone.js

我有一个Angular 4.3 + Cordova应用程序,以前工作得非常好。但是现在,我在app启动时出现了一个空白屏幕,而且没有任何事情发生了。

经过一段时间的挖掘,我意识到它的来源:

我的主页受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;
                }
            });
        });
    });
}

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

3 个答案:

答案 0 :(得分:5)

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

  

模块名称:

     

on_property

     

使用zone.js的行为:

     

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

source

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

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

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

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;
                }
            });
        });
    }));
}

答案 1 :(得分:1)

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

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

npm install --save zone.js@latest

第二,您需要确保index.html添加了cordova.js 之后 build/polyfills.js

答案 2 :(得分:0)

聚会晚了一些,但是我不得不解决一个Ionic3 / Angular4项目,但确实遇到了这个问题,我发现the answer from @n00dl3点上,但是在全局服务中创建了一个modelState.Remove("Password") 实例。因为有时区域尚未修补FileReader 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();
  }
}

我希望它能对某人有所帮助