Возможная утечка памяти в приложении NativeScript, если пользователь повторно открывает свое приложение несколько раз - PullRequest
0 голосов
/ 16 апреля 2020

Я не уверен, где ошибка, может быть, я неправильно использую rx js. ngDestroy не работает, чтобы отписаться от наблюдаемых в NativeScript, если вы хотите закрыть и вернуться к своему приложению. Я пытался работать с takeUntil, но с теми же результатами. Если пользователь закрывает / открывает приложение много раз, это может вызвать утечку памяти (если я правильно понимаю мобильную среду). Любые идеи? Этот код ниже это только демо. Мне нужно использовать пользователей $ во многих местах моего приложения.

Протестировано с эмулятором Android sdk и на реальном устройстве.

AppComponent

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription, Observable } from 'rxjs';
import { AppService } from './app.service';

import { AuthenticationService } from './authentication.service';
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnDestroy, OnInit {
  public user$: Observable<any>;

  private subscriptions: Subscription[] = [];

  constructor(private appService: AppService, private authenticationService: AuthenticationService) {}

  public ngOnInit(): void {
    this.user$ = this.authenticationService.user$;

    this.subscriptions.push(
      this.authenticationService.user$.subscribe((user: any) => {
        console.log('user', !!user);
      })
    );
  }

  public ngOnDestroy(): void {
    if (this.subscriptions) {
      this.subscriptions.forEach((subscription: Subscription) => subscription.unsubscribe());
    }
  }

  async signIn() {
    await this.appService.signIn();
  }

  async signOut() {
    await this.appService.signOut();
  }
}

AuthenticationService

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
import { AppService } from './app.service';

@Injectable({
  providedIn: 'root',
})
export class AuthenticationService {
  public user$: Observable<any>;

  constructor(private appService: AppService) {
    this.user$ = this.appService.authState().pipe(shareReplay(1)); // I'm using this.users$ in many places in my app, so I need to use sharereplay
  }
}

AppService

import { Injectable, NgZone } from '@angular/core';
import { addAuthStateListener, login, LoginType, logout, User } from 'nativescript-plugin-firebase';
import { BehaviorSubject, Observable } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators';

const user$ = new BehaviorSubject<User>(null);

@Injectable({
  providedIn: 'root',
})
export class AppService {
  constructor(private ngZone: NgZone) {
    addAuthStateListener({
      onAuthStateChanged: ({ user }) => {
        this.ngZone.run(() => {
          user$.next(user);
        });
      },
    });
  }

  public authState(): Observable<User> {
    return user$.asObservable().pipe(distinctUntilChanged());
  }

  async signIn() {
    return await login({ type: LoginType.PASSWORD, passwordOptions: { email: 'xxx', password: 'xxx' } }).catch(
      (error: string) => {
        throw {
          message: error,
        };
      }
    );
  }

  signOut() {
    logout();
  }
}

1 Ответ

1 голос
/ 16 апреля 2020

ngOnDestroy вызывается всякий раз, когда компонент уничтожается (после обычного рабочего процесса Angular). Если вы переместились на вперед в своем приложении, предыдущие представления по-прежнему будут существовать и вряд ли будут уничтожены.

Если вы видите несколько ngOnInit без каких-либо ngOnDestroy, то вы создание нескольких компонентов с помощью навигации, не связанной с вашими подписками. Не следует ожидать, что один и тот же экземпляр вашего компонента будет повторно использован после вызова ngOnDestroy, поэтому при наличии массива от push до Subscription[] будет только один объект.

Если вы завершаете работу приложение (т. е. принудительно завершить смахивание), весь контекст JavaScript выбрасывается, а память очищается. Вы не рискуете просочиться за пределы контекста вашего приложения.

Между прочим, вы усложняете отслеживание подписки (и не только тем способом, который я описал выше только для того, чтобы когда-либо его нажимали). Subscription - это объект, к которому одновременно могут быть присоединены другие Subscription объекты для завершения.

const subscription: Subscription = new Subscription();
subscription.add(interval(100).subscribe((n: number) => console.log(`first sub`));
subscription.add(interval(200).subscribe((n: number) => console.log(`second sub`));
subscription.add(interval(300).subscribe((n: number) => console.log(`third sub`));

timer(5000).subscribe(() => subscription.unsubscribe()); // terminates all added subscriptions

Будьте внимательны, чтобы добавить вызов подписки непосредственно в .add, а не с замыканием. Досадно, что это точно такой же вызов функции, когда вы хотите добавить блок завершения в подписку, передав вместо этого блок:

subscription.add(() => console.log(`everybody's done.`));
...