현재 회사에서 서비스 하고있는 Flutter Windows 앱에 자동 업데이트 기능이 있으면 좋겠다는 요청이 있었다. 사용자가 늘어남과 동시에 패치가 점점 잦아져 이 기능에 대한VOC가 늘어나게 되었고, 개발을 하게 되었다
기존 프로세스
기존 프로세스는 다운로드 홈페이지에서 직접 최신버전의 installer(.exe)를 다운 받고, installer를 실행 하여 설치를 진행하는 방식이었는데 exe파일이다보니 '안전하지 않은 다운로드'가 떠서 사용자들이 불편함을 겪는 문제가 있었다. 또한, 업데이트 까지의 과정이 길고, 사용자들의 동작이 필요한 부분이 많아 프로세스 자체가 간소화될 필요성이 있었다.

생각한 해결 방법들
1. auto_updater 등 패키지 사용
pub.dev에는 auto_update를 포함한 flutter windows 업데이트를 위한 오픈소스 패키지들이 등록되어 있다. 하지만 대부분의 패키지들의 최신 버전 릴리즈가 1년이 넘었고, github 레포를 봐도 관리가 잘 되고 있는 것 같지가 않았다. 자동 업데이트 같은 기능은 앱이 업데이트 될 때마다 필수적으로 사용되어야 하는 기능인데, 제대로 관리되고 있지 않은 패키지를 사용했다가는 유지보수에 큰 취약점이 될 것 같아 패키지를 사용하는 방법은 패스하였다.
2. Github API
개발 언어나 플랫폼에 구애 받지 않고, Github API를 사용한다면 자동 업데이트를 구현할 수 있을 것이다. 특히 Github action과 연동하여 사용한다면 빌드-배포-자동 업데이트를 한 번에 해결할 수 있기 때문에 꽤나 좋은 방법이라고 생각된다. 하지만 아쉽게도 회사의 폐쇄망에서도 사용이되는 앱이기 때문에 방화벽 이슈가 있었다. 사내의 모든 사용자들의 github에 대한 방화벽을 해제할수 없기 때문에 이 방법도 사용할 수 없었다.
3. update프로그램 직접 만들기
결국 직접 auto update프로그램을 만드는 것이 개발 기간도 단축되고, 유지보수 측면에서도 옳은 방법이라고 생각했다. 기존 installer에 update프로그램을 추가하여 서비스앱에서 최신버전 체크를 한 뒤, 최신버전이 아니라면 업데이트 프로그램을 실행시키는 방법으로 자동 업데이트 기능을 만들 수 있을 것이다.
우선, 업데이트 프로그램을 어떤 언어로 만들지 생각해 봤다. 서비스 앱과 똑같이 flutter로 만들거나, clickonce를 지원하는 c#, 간단하고 빠르게 프로그램을 만들 수 있는 python 중에 고민을 했다. 우선, flutter의 경우에는 같은 언어로 만들 수 있다는 장점이 있지만, gui로 실행된다는 단점이 있었고, c#은 python과 비교했을 때 생산성이 떨어진다는 생각을 했다. 그렇기 때문에 python으로 프로그램을 만들기로 하였다.

개발 과정
- 버전체크
우선 현재 앱이 업데이트가 필요한지 체크가 필요하다. 체크를 위해선 내 앱의 버전과 최신 배포된 앱의 버전을 알아야 한다. 최신 배포된 앱의 버전은 서버에 업로드된 파일의 버전을 가져오는 api를 개발하여 사용했다. 현재 앱의 버전을 체크하는 방법은 여러가지가 있는데
1. 패키지 사용
- package_info_plus 패키지를 사용하면 손쉽게 현재 앱의 버전을 알 수 있다.
https://pub.dev/packages/package_info_plus
package_info_plus | Flutter package
Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.
pub.dev
2. pubspec.yaml 파일 직접 참조
String yaml = await rootBundle.loadString('pubspec.yaml');
Map<dynamic, dynamic> yamlMap = loadYaml(yaml);
String version = yamlMap['version'];
- 위와 같이 pubspec.yaml 파일을 로드해 yaml을 직접 읽어 version을 직접 가져옴
나는 위 방법들로 현재 앱의 버전을 체크하여 최신 버전과 비교하였다.
- update 프로그램 실행
Flutter에서 외부 exe파일을 실행하기 위해 Process클래스(프로그램을 실행하는 클래스)의 run메써드를 사용하였다.
Process.run('cmd', [
'/c',
'start',
'',
'C:/SOME/PATH/auto_updater.exe',
"some args1",
"some args2",
]);
이 코드를 실행하면 CLI 환경에서 auto_updater프로그램이 실행되는 것을 확인할 수 있다.
이제 앱을 업데이트 시켜 줄 updater 프로그램을 만들어보자
exe확장자 형태의 updater 프로그램을 만들기 위해 python의 유명한 모듈인 pyinstaller를 사용하였다.
2024.09.14 - [Python] - [Python] PyInstaller로 exe파일 만들기
[Python] PyInstaller로 exe파일 만들기
PyInstaller는 파이썬으로 exe실행파일을 만들 수 있는 가장 간단하고 쉬운 방법 중 하나이다.PyInstaller를 사용해서 exe를 만드는 방법을 알아보자. 우선 pip로 PyInstaller를 설치한다 pip install pyinstalle
maybe-developer.tistory.com
프로그램에서 실행시킬 코드는 아래와 같다.
def download_servie_app(host):
print("servie_app installer downloading...")
request.urlretrieve(APP_DOWNLOAD_PATH, get_hub_download_path())
print("servie_app installer download complete")
def run_servie_app_installer():
print("servie_app installing...")
process = subprocess.Popen([get_hub_download_path(),"/verysilent","/norestart"], stdout=subprocess.PIPE, stderr= subprocess.PIPE)
stdout, stderr = process.communicate()
if process.returncode == 0:
print("servie_app installation is completed.")
time.sleep(2)
subprocess.Popen([APP_RUN_PATH], shell=True, creationflags=subprocess.DETACHED_PROCESS)
print("!!!!!")
else:
print("Mosis Hub installation failed")
print(f'Error: {stderr}')
os.system("pause")
def checking_servie_app():
for p in psutil.process_iter(['pid','name']):
if p.info['name'] == 'servie_app.exe':
print(">>>>> servie_app is running...")
p.terminate()
print("servie_app is terminated...")
else:
print("servie_app is not running")
if len(sys.argv) > 1:
try:
checking_hub()
run_servie_app_installer()
except Exception as ex:
print(ex)
print("Smart Updater Error: CAD개발 곽동현 프로에게 문의하세요.")
os.system("pause")
1. download_service_app
url로부터 파일을 다운로드 받는 함수이다. urllib.request에서 urlretrieve 메써드를 사용하여 installer파일을 다운로드 받았다.
*urlretrieve('다운로드 경로', '저장 경로')를 호출하면 해당 파일을 다운로드 받아 저장 경로에 파일 형태로 저장된다.
2. run_service_app_installer
앱 설치를 위한 installer를 실행한다.파이썬 에서는 다른 프로그램을 실행시키기 위해 subprocess를 주로 사용하는데,
process = subprocess.Popen([get_hub_download_path(),"/verysilent","/norestart"], stdout=subprocess.PIPE, stderr= subprocess.PIPE)
에서 subprocess.Popen('실행하고 싶은 파일 위치', 'param1', 'param2'...) 로 프로세스를 실행시킬 수 있다. 위 코드에서 "/verysilent", "norestart"는 installer를 만들 때 사용한 innosetup 에 대한 키워드이다.
또한 stdout=subprocess.PIPE, stderr= subprocess.PIPE 는 표준 출력에러를 처리할 수 있도록 설정해주는 값이다.
subprocess.Popen([APP_RUN_PATH], shell=True, creationflags=subprocess.DETACHED_PROCESS)는 업데이트 이후, 다시 앱을 재시작해주기 위해 작성된 코드이다. shell=True와 creationflags.DETACHED_PROCESS를 통해, '실행되는'프로세스를 '실행하는' 프로세스로부터 독립시켜, 실행하는 앱이 꺼지더라도 실행되는 앱이 꺼지지 않도록 한다.
3. checking_service_app
process_iter를 사용하면 현재 실행중인 프로세스의 목록을 받을 수 있다. 그 중에 현재 내가 업데이트할 앱이 포함되어 있는지 체크하여, 만약 앱이 실행되고 있는 중이라면 terminate()로 종료시킨다.
결과
flutter에서 업데이트 버튼을 클릭하면 python으로 만들어진 updater프로그램이 실행되어 자동으로 업데이트가 완료되는 것을 확인할 수 있었다. 프레임워크에서 직접적으로 제공되지 않는 기능이 있더라도, 방법을 찾아보면 다 해결책이 있다는 것을 다시금 생각하게 되는 계기가 되었다
'Flutter' 카테고리의 다른 글
[Flutter] 패키지 pub.dev에 배포하기 (0) | 2025.01.16 |
---|---|
[Flutter] 중첩 조건문 방지하기 Guard Clause (0) | 2024.12.06 |
[Flutter] Unable to load asset 오류 해결 (0) | 2024.08.03 |
[Flutter] JSON 다루기 (리스트, 중첩) (0) | 2024.07.27 |
[Flutter] Animation을 사용하여 ListView 선택 효과 만들기(AnimatedPositioned) (0) | 2024.07.21 |