[Flutter&Django] 너의 API서버가 자꾸 죽는 이유

리미·2020년 5월 8일
3

Flutter

목록 보기
6/6
post-thumbnail

서버가 자꾸 죽는다!

현재 작동방식은 이렇게 되어있다.

라이브하다보면 여러이유로 서버가 죽는걸 많이 발견할 수 있다.
현재 문제는 Flutter에서 계속 Django api를 호출하여 api만드는 로직에서 DB connection 이 너무 많이 되는 문제이다.

Django에선 뭐가 문제야?(추가예정)

DRF에서 serializers와 ORM을 사용하는데, 이 ORM을 사용하는 모든 스레드는 새로운 데이터베이스 연결을 만들어낸다고 한다.🤔 그리고 Django는 그 연결을 자동으로 관리해주지 않는다.
그말인 즉슨 ORM을 사용하고 나면 db connection을 끊어줘야한다는 말씀.🤭

Every thread that uses Django ORM creates a new database connection. And Django will not manage the connection automatically that created by your own thread. So you should manage it.

출처 : https://stackoverflow.com/a/17311359/13159805

serializers도 지금 쓰는 방식으로 사용하면 안됀다고 JK가 그러는데 이건 JK가 해결하고 알려주면 그때 쓰도록하겠다(협업의 올바른예)

Flutter에선 뭐가 문제야?

1. 특정 화면 전환시 flutter 에서 이렇게 에러를 알려준다.

This error happens if you call setState() on a State object for a widget 
that no longer appears in the widget tree 
(e.g., whose parent widget no longer includes the widget in its build). 
This error can occur when code calls setState() from a timer or an 
animation callback. The preferred solution is to cancel the timer or 
stop listening to the animation in the dispose() callback. 
Another solution is to check the “mounted” property of this object before 
calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called 
because another object is retaining a reference to this State object 
after it has been removed from the tree. 
To avoid memory leaks, consider breaking the reference to this object 
during dispose().

영알못이 대충 감으로 해석해보자면 대충 이렇다

setState()함수안에 timer나 animation작업을 했으면 그걸 중단시켜야해. setState에서 "mounted"를 체크해서 사용하도록하고 dispose()로 멈춰. 안그러면 너의 메모리가 안녕할거야

여기서 잠깐 mounted가 뭐지하고 찾아보니
현재 떠있는게 이 화면인지 아닌지 판별을 해주는 거라고 한다. 현재 화면이면 true 아니면 false 반환인듯

페이지가 넘어가도 setState는 계속 작동하기 때문에 그 페이지가 떠있을때만 작동해야 되는 코드들은 mounted로 묶어주는게 좋겟지이~?
안그러면 setState가 작동하는 페이지(A)에서 또 setState가 작동하는 페이지(B)로 가고 다시 A로 갈때 저런 에러를 만나게 되고 그 setState는 엉키게 되겠지~?

그래서 원하는 대로 해주도록한다.
일단 난 timer를 쓰는곳이 initState() 함수였다 그래서 timer 함수 안에 mounted를 체크하는 로직을 추가로 넣어주었다.

timer = Timer.periodic(Duration(seconds: 10), (Timer t) {
    if (mounted) { // 이부분!!!
        setState(() {
            _listenLocation();
            print(_location.toString());
            geolocationAPIData(_location.latitude, _location.longitude, workLog, place, device, position);
        });
    }
});

여기서 dispose()를 추가로 넣어주면되는데,
잠깐 dispose를 설명하기 전에 life cycle을 설명하도록 하겠다.
life cycle란 한 Activity(또는 view controller, 또는 Widget)가 보여지고 사라지는까지의 주기를 말한다.

Android - Activity

Android를 한번이라도 배워본사람은 Activity에 lifecycle을 들어본적이 있을것이다.
Android를 해봤는데 들어본적없다고? lifecycle은 다음과 같다

  • onCreate()
  • onStart()
  • onResume()
    ~ ~ ~ ~ ~ ~ ~
  • onPause()
  • onStop() -> 다시시작되면 onRestart(), 실행종료하면 onDestroy()
  • onDestroy()

이제 기억나지않는가?

iOS - UIViewController

이런게 iOS에도 있다 한번 살펴보자.
UIViewController에 lifecycle이다

  • viewDidLoad()
  • viewWillAppear()
  • viewDidApppear()
    ~ ~ ~ ~ ~ ~ ~
  • viewWillDisappear()
  • viewDidDisappear()
  • viewDidUnload()

Flutter - Widget lifecycle

Flutter의 Widget은 StatelessWidget과 StatefulWidget 두가지가 있는데, StatefulWidget이 Android와 iOS와 동일하니 살펴보도록하자

  • createState()
  • initState()
  • didChangeDependencies()
  • build()
  • (didUpdateWidget)
    ~ ~ ~ ~ ~ ~ ~
  • deactivate()
  • dispose()

대충 보다보면 이해가 될것이다.
Activity(또는 view controller, 또는 Widget)가 보여지고 사라지기까지 저런 메서드가 실행되고,
한 Widget이 사라질때(더이상사용하지않을때) dispose()를 사용하는구나!라고
이렇게 생각한다면 앞에서 말한 dispose()로 멈추라는 말이 이해가 되지? 느낌 RGRG~?

그렇다면 이제 문제의 코드에 넣어보도록한다.


void dispose(){
    timer?.cancel(); // timer가 null이면 null을 반환하고, 아니면 cancel() 호출
    timer = null;
    super.dispose();
}

2. 자꾸 이상하게 API 서버에서 0.3초마다 같은 api url이 여러개씩 호출되고있다?

나같은 경우에는 Bloc fetch을 하고자 setState() 안에다가 Bloc.fetch()를 넣었다.
알고보니 이렇게 하니까 계속 fetch를 호출해서 계속 api를 호출하는 것이 아닌가?


void setState(fn) {
    historyReportGetBloc.fetch();
    super.setState(fn);
}

혹시나 해서 mounted 체크를 추가해봤다


void setState(fn) {
    if (mounted) {
        historyReportGetBloc.fetch();
        super.setState(fn);
    }
 }

그래도 같은 오류라 setState안에 fetch부분은 빼버리고 일단보류 ㅡㅡ;


void setState(fn) {
    super.setState(fn);
 }

새로고침 할려는 부분은 수동으로 버튼을 눌러서 fetch하게 할지 아니면 local DB로 저장해서 보여줄지 고민중이다.

profile
Python이 하고싶은데 자꾸 Flutter 시켜서 빡쳐서 만든 블로그

0개의 댓글