Angular 4.x + Cordova:FileReader静默失败(白屏死机) [英] Angular 4.x + Cordova : FileReader fails silently (white screen of death)

查看:80
本文介绍了Angular 4.x + Cordova:FileReader静默失败(白屏死机)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个Angular 4.3 + Cordova应用程序,该应用程序以前运行得很好.但是现在,我在启动应用程序时出现了黑屏,什么也没发生.

I have an Angular 4.3 + Cordova application that used to work very well. But now, I get a blank screen on app start-up, and nothing happens any more.

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

After digging a while I realized where it comes from :

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

my home page is protected by a CanActivate guard that will check some file-system-persisted preferences and redirect the user to another page if this is the first run or if a required preference is missing, to fill-in the required properties.

因此,应用程序的启动取决于我的CanActivate保护,后者取决于PreferenceService,后者本身取决于我自己实现的FileSystemService.问题是,当我尝试读取存储用户首选项的文件时,没有触发任何回调,因此没有任何反应,甚至没有错误.

So the launch of the app depends on my CanActivate guard that depends on a PreferenceService that itself depends on a FileSystemService that I implemented myself. The problem is that when I try to read the file where user's preferences are stored, not a single callback is fired, nothing happen, not even an error.

这是我的FileSystemService的一部分,没有任何错误即可失败:

this is the part of my FileSystemService that fails without any error :

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

为什么甚至会发生这种情况,我该如何处理,以便触发我的回调?

Why does this even happen and how can I deal with that so my callbacks get triggered ?

推荐答案

编辑

正如很久以前所说,我在zone.js存储库上打开了一个问题,zone.js所有者迅速修补了代码.您只需将zone.js/zone-patch-cordova导入到polyfills中,就可以避免使用肮脏的hack带来的痛苦.

As stated long ago, I openned an issue on zone.js repository and zone.js owner quickly patched the code. You can avoid the pain of using my dirty hack just by importing zone.js/zone-patch-cordova inside your polyfills.

原始答案

在调试此代码时,我意识到FileReader构造函数已由cordova和zone.js修补.根据我对zone.js补丁的了解,它会将每个"onProperty"(onloadonloadendonerror)都更改为其addEventListener(...)对应部件.

While debugging this code I realized that the FileReader constructor was patched by both cordova and zone.js. From what I understood regarding zone.js patching is that it changes every "onProperty" (onload,onloadend,onerror) to its addEventListener(...) counterPart.

模块名称:

on_property

Module Name:

on_property

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

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

But cordova does not use the dispatchEvent(...) API to notify listeners operation has ended.

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

One solution might be to deactivate onProperty module from zone.js but it might break angular's behavior.

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

So this is how I coped with the situation :

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补丁.

The secret here is that zone.js keeps the original constructor in the __zone_symbol__OriginalDelegate property, so calling this will actually call Cordova's FileReader directly without zone.js patch.

此解决方案是一个肮脏的hack,我已经在区域存储库中打开了一个问题

This solution being a dirty hack,I have openned an issue on zone's repository

FileWriter也有相同的问题(它内部称为FileReader),所以我写了这个小垫片:

Had the same problem with FileWriter (it internally calls a FileReader) so I wrote this little shim :

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

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

then wrapped my calls to read/write operations :

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

这篇关于Angular 4.x + Cordova:FileReader静默失败(白屏死机)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆