본문 바로가기
비전공자를 위한 Flutter/Flutter 심화과정

Flutter How to optimize? 속도 10배 상승시킨 앱 최적화 하는 법 공유

by 밍잔 2022. 5. 8.

플러터로 앱을 개발하다보면 처음에는 모르지만 점점 앱이 느려지는 jank를 맞이하게 됩니다.(앱이 버벅이는 현상을 jank라고 합니다.) '최적화가 시급하다!!'라고 선언하고 복잡도를 줄이는 작업에만 몰두했었습니다. 하지만 jank를 유발하는 원인은 더 기본적인 계층에 있었죠. 앱 퍼포먼스를 10배 향상시켰던 시행착오를 공유합니다. 직접 해결했던 jank를 유발하는 원인은 아래와 같습니다.

 

1. 그라데이션 효과

2. 그림자 효과

3. Opacity 위젯 사용

4. Clip를 이용한 위젯 다듬기

5. const 미사용

6. 잘못된 위젯 사용

7. 애니메이션 또는 State 변경시 광범위한 리빌드

 

하나씩 살펴보죠.


원인1. 그라데이션 효과

 

 

 

그라데이션 효과는 시작점과 중간점, 끝점까지 지정한 컬러가 나오도록 계산을 해줘야 합니다. 화면이 움직이지 않고 가만히 있으면 괜찮겠지만, 그런 앱이 거의 없죠. 화면이 움직일 때마다 1초에 60번씩 계산하면서 정확한 위치에 그 색상을 그려줘야하니 부하가 걸릴만도 합니다.

 

그라데이션 효과 하나로 디자인 퀄리티가 확실히 좋아보이기는 합니다. 왜냐하면 이 세상에 단색은 없기 때문이죠. 어디에나 빛이 있기 때문에 우리가 보는 모든 사물은 위치에 따라 다른 양의 빛을 받게되고 지점마다 다른 색으로 보이게 됩니다. 그래서 그라데이션이 우리 눈에 더 자연스러워 보이죠. 디자이너들의 포트폴리오를 보시면 완전한 단색처럼 보여도 가장자리 부분마다 스포이드를 찍어보면 색상코드가 다른 것을 확인할 수 있습니다. 

 

많은 그라데이션 효과로 인해 jank현상이 발생하는 경우, 디자이너에게 이러한 이슈를 알리고 디자인 컨셉을 변경하거나, 그라데이션 요소를 최소화하는 방향으로 이야기할 필요가 있습니다. 

 


원인2. 그림자 효과 (컬러중첩)

 

 

 

플러터 프레임워크가 사용하는 skia엔진은 우리가 보는 화면을 중첩해서 그립니다. 쉽게 말해 흰색 위에 흰색을 코딩했다고 해서 흰색을 한 번만 그리는게 아니라는 겁니다. 

 

@override
Widget build(BuildContext context) {
  return Container(
    color: Colors.white
    child: Container(
      color: Colors.white
      child: Container(
        color: Colors.white
        child: ...
      ),
    ),
  );
}

 

위와 같이 흰색을 그리는 Container가 반복되면 반복되는 만큼 화면을 흰색으로 칠합니다! 어이없지만 컴퓨터는 시키는대로 합니다. 그래서 같은 영역에 같은 컬러를 중첩시키는 일도 최소화해야 합니다. 나중에 위젯 트리가 깊어지면 유지보수할 때도 귀찮아질 수 있습니다. 

 

그림자 효과 또한 같은 원리로 그려집니다. [그림자 아래에 있는 컬러 + 그림자 컬러 + 오퍼시티 계산 + 그림자 그라데이션 계산] 이 계산을 1초에 60번씩 해서 화면을 그린다고 생각하시면 됩니다. 그림자 효과가 많으면 많을수록 jank를 유발하겠죠. 카드 디자인이 높은 퀄리티를 보장해주긴 하지만 너무 많은 곳에 그림자 효과를 넣는 일은 지양해야 합니다. 

 


원인3. Opacity 위젯 사용

 

 

 

오퍼시티도 마찬가지로 [오퍼시티가 감싸고 있는 위젯 + 그 아래에 그려진 위젯]에 대해 중첩되는 컬러들을 계산해서 그려야합니다. 플러터 공식 문서에서도 많은 Opacity위젯의 사용을 지양하고 있죠. 만약 화면에 오퍼시티가 0인 위젯을 그려야 한다면 Offstage또는 Visiblity위젯을 고려해볼 수 있습니다. 두 위젯의 차이는 'State를 유지하는가' 입니다. Visibility위젯을 사용하면 child 위젯을 SizedBox위젯으로 교체하여 State를 완전히 dispose시키게 됩니다. 이 차이점을 인지하시고 잘 골라서 사용하세요. Opacity위젯은 최소화합시다!

 


원인4. Clip를 이용한 위젯 다듬기

 

 

Clip은 자식 위젯의 가장자리 부분을 동그랗게 만들고 싶을 때 사용합니다. 많은 서비스가 특히 프로필 사진을 동그랗게 만들죠. 이때 Clip위젯이 자식 위젯의 가장자리 부분을 잘라버리면 그 자리에 어떤 컬러가 그려져야 하는지 계산이 필요합니다. 앱 디자인 컨셉에 따라 가장자리가 동그랗게 되어있을 수도 있고, 버튼 디자인이 다각형인 경우도 있습니다. 이런 경우 차라리 그 모양으로 디자인된 이미지를 받아 사용하시는 게 좋습니다. 

 

ClipRRect 위젯 소개 동영상

원인5. const 미사용

 

구글의 플러터 코딩 규칙에서도 언급하는데요, 가능하면 const 지정자를 사용하라고 합니다. (https://dart-lang.github.io/linter/lints/prefer_const_declarations.html) final보다 const가 더 효과적입니다. final은 런타임에, const는 컴파일타임에 결정됩니다. 알기 쉽게 묘사하자면 final은 앱 실행중에, const는 앱을 처음 실행할 때 해당 변수를 참조한다는 뜻입니다. 

 

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

 

위의 예제 코드를 보면 MyHomePage의 final로 지정된 필드 title은 앱 실행중 매개변수에 따라 얼마든지 바뀔 수 있습니다. 따라서 final은 런타임중에 한 번 선언하고 나면 이후에 바뀌지 않는다는 뜻이고, const는 애초에 앱을 실행할 때부터 바뀌지 않는다는 뜻입니다. 어느쪽이 앱 실행중에 리소스를 더 차지할지 짐작이 가시죠?

 


원인6. 잘못된 위젯 사용

 

 

유튜브 Flutter 채널을 아시나요? 거기에 보면 '금주의 위젯' 하면서 플러터 프레임워크에 있는 정말 많은 위젯들이 소개됩니다. 위젯마다 사용되어야 하는 상황이 다른데요, 이런 위젯 소개 영상들을 하나씩 보시면서 사용법을 아셔야 합니다. 여러분은 리스트 위젯 중 Listview와 Listview.builder의 차이점을 아시나요? Listview 위젯은 build 메소드가 실행되면 모든 하위 위젯을 미리 빌드해놓습니다. 1,000개짜리 리스트를 한 번에 빌드하려고 하시면 안 되겠죠? 화면에 다 그려지지도 않을텐데 말이에요. Listview.builder 위젯은 스크롤을 내리면 화면에 그려져야 하는 하위 위젯을 그리기 직전에 빌드합니다. Listview위젯은 배열 요소가 적을 때, Listview.builder 위젯은 무한 스크롤이 필요할 때 사용해야하는 거죠. 이처럼 위젯마다 적절한 사용처를 아는 게 꽤 중요합니다!

 


원인7. 애니메이션 또는 State 변경시 광범위한 리빌드

 

예쁜 애니메이션들은 앱 퀄리티에 막대한 영향을 미치죠. 기획된 디자인에 따라 UI 곳곳에 애니메이션들을 적용하기 시작합니다. 그러다보면 애니메이션의 영향을 받는 위젯들이 많아지게 되는데요. 이때 위젯 트리를 제대로 설계해놓지 않으면 불필요한 위젯까지 다시 빌드하는 일이 벌어집니다.

 

애니메이션은 시작/끝 상태를 가져야하니 StatefulWidget이어야만 하는데요. 플러터 공식 문서에서 2가지를 언급합니다. 1) 가능하면 StatefulWidget을 트리 구조 가장 하위 계층에 위치하도록 하라고 합니다. 2) 그리고 불필요하게 다른 자식 위젯까지 빌드하지 말고, 화면에서 애니메이션이 필요한 위젯만 StatefulWidget 위젯으로 만들라고 하죠.

 


위의 7가지 요소만 수정해도 여러분의 앱 성능이 10배 이상 상승하는 것을 눈으로 확인하실 수 있습니다. 최적화 비포/애프터를 직접 시간을 로그로 찍어보며 비교해보세요. 리팩토링 성과로 어필하기도 좋습니다!

 

 

웃기지만, 다음 강의에서는 카드 디자인의 위젯에 예쁘게 그림자를 넣는 법에 대해 알아보겠습니다. 아래 링크를 눌러주세요!

 

 

https://mingzan.tistory.com/270

댓글