티스토리 뷰

반응형

앱의 성능을 올려볼 방법이 없을까?

 

최근 작업을 하면서 성능을 올릴만한게 뭐 없을까? 라는 고민을 하며 회사에서 작업을 하던중 앱의 화면에 asset이 많이 들어가면 화면이 버벅이는거 같은 느낌을 받았습니다. 

이 느낌은 아이폰에서 특히 더 많이 느껴지는거같았고 어떻게 해결할 수 있는 방법이 없을지 찾아보던 중 이미지를 precache 할 수 있다는것을 알아냈습니다.

이미지 Precache는 Flutter에서 제공하는 기능 중 하나로, 화면에 표시되기 전에 이미지를 미리 로드하여 캐시에 저장하는 과정입니다. 이를 통해 사용자가 이미지를 요청할 때마다 실시간으로 다운로드하는 대신, 이미지가 사전에 로드되어 빠르게 표시될 수 있습니다.

 

 

그래서 어떻게 쓰는건데? 

 

기본적인 사용법은 아래와 같습니다.
이미지를 사용하기 전 precacheImage 메서드를 통해 ImageProvider의 구현체인 NetworkImage 혹은 AssetImage 객체를 생성해 ImageCache 내에 등록하는겁니다.

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // precacheImage(NetworkImage('https://example.com/image.jpg'), context); // or
    precacheImage(AssetImage('assets/image.jpg'), context);
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Image Precache Example'),
      ),
      body: Center(
        child: Image.network('https://example.com/image.jpg'),
      ),
    );
  }
}

 

이런식으로 새로운 개념을 가지고 놀고있었는데 문득 든 생각이 '이거 플러그인으로 뺴놓으면 편하겠는데?' 라는 생각이 들었습니다.

그때부터 바로 뚝딱뚝딱 작업에 돌입!

 

 

플러그인으로 만들어보자!

 

메인 페이지에서 캐시된 이미지와 캐시되지 않은 이미지를 구분해서 보기위해 각 페이지를 나누고 assets 폴더에 들어있는 모든 이미지를 캐시하게끔 작업했습니다.

 

여기서 들었던 생각이 '그러면 에셋의 모든 경로를 하드코딩으로 입력해줘야하나? 그럼 너무 비효율적인데?' 라고 생각하고 로컬 파일주소에 접근할 수 있는 방법을 찾다가 아래와 같은 방법을 발견했습니다.

 

final manifestJson = await DefaultAssetBundle.of(context).loadString('AssetManifest.json');
final Map<String, dynamic> manifestMap = json.decode(manifestJson);
final assetList = manifestMap.keys.toList();

 

AssetManifest.json 파일은 Flutter 앱에서 사용되는 에셋들의 정보를 포함하는 파일입니다. 이 파일은 앱이 빌드될 때 생성되며, 앱이 어떤 에셋을 포함하고 있는지에 대한 메타데이터를 담고 있습니다.

 

AssetManifest.json 파일을 까보면 이런식으로 생겼고 각 경로의 key 값을 모아 내 로컬의 에셋의 경로를 찾아올 수 있었습니다.

{
  "assets/test_cached.jpg": [
    "assets/test_cached.jpg"
  ],
  "packages/cupertino_icons/assets/CupertinoIcons.ttf": [
    "packages/cupertino_icons/assets/CupertinoIcons.ttf"
  ]
}

 

또한 이미지가 아닌 에셋들을 걸러내 주기 위해 아래와 같이 필터링을 실행해줍니다.

 List<String> extensionList = ['svg', 'png', 'jpg', 'jpeg'];
 imageList.removeWhere((e) => !extensionList.contains(e.split('.').last));

 

위에서 얻은 경로를 바탕으로 svg 파일과 기타 이미지 파일을 나눠서 precache를 진행하면 거의 완성입니다.

  /// 이미지 전달받아서 캐싱함
  void _imageCache(String path, BuildContext context) {
    bool isSvg = path.contains('.svg');
    if(isSvg){
      final loader = SvgAssetLoader(path);
      svg.cache.putIfAbsent(loader.cacheKey(null), () => loader.loadBytes(null));
    }else{
      precacheImage(AssetImage(path), context);
    }
  }

 

마지막으로 위 작업들을 진행하기 위해서는 BuildContext 를 전달 받아야하는 유저 입장에서의 불편함이 있는데 이를 해결하기위해

  void startImageCache(){
    final binding = WidgetsFlutterBinding.ensureInitialized();
    binding.addPostFrameCallback((_) async {
      BuildContext? context = binding.rootElement;
      if(context != null) _startImageCache(context);
    });
  }

 

이런식으로 플러그인 내부에서 위젯이 그려지고 context가 생기면 받아서 호출해주게끔 작업을 진행했습니다.

따라서 아래와 같이 main 함수 내에서도 별다른 제약없이 호출할 수 있게되었습니다!

void main() {
  GhAssetPreCache().startImageCache();
  runApp(const MyApp());
}

 

그래서 결과물은?

 

약 2mb 정도 되는 이미지를 카피해서 하나는 캐시를 진행하고 하나는 캐시를 시키지 않고 테스트 한 영상입니다.

'image cache' 버튼에서는 별다른 로딩없이 페이지가 열릴때부터 이미지가 잘 보이는 반면

'image non cache' 버튼에서는 흰바탕이었다가 화면이 어느정도 노출된 이후 이미지가 보이게 되는걸 볼 수 있습니다.

 

 

마무리

 

코드 라인이 많지도 않고 보고나면 어렵지도 않지만 앞으로 상당히 유용하게 쓰일것으로 보입니다.

위 플러그인은 아래 링크에 배포되어있고 추후에 좋은 아이디어가 떠오를때마다 업데이트를 할 예정입니다.

https://pub.dev/packages/gh_asset_pre_cache

반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함