<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>JH 개발 블로그</title>
    <link>https://ts2ree.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Apr 2026 17:21:05 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ji-hyun</managingEditor>
    <image>
      <title>JH 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/3880108/attach/0ebe2049bac94114ba5dc49886980c71</url>
      <link>https://ts2ree.tistory.com</link>
    </image>
    <item>
      <title>플러터에서 앱 상태복원하기</title>
      <link>https://ts2ree.tistory.com/407</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 백그라운드에 두었다가 다시 열었을 때, 진행 상황이 모두 사라지고 처음 상태로 돌아가 있는 모습을 한 번쯤 본 적 있을 것이다.&lt;br /&gt;만약 사용자가 폼을 작성 중이었거나, 잠깐 연락을 주고 받던 중이었는데 앱으로 돌아오자마자 모든 게 초기화돼버린다면 어떨까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;314&quot; data-start=&quot;283&quot; data-ke-size=&quot;size16&quot;&gt;유저 입장에서는 꽤나 불쾌하고 불편한경험일 것이다.&lt;/p&gt;
&lt;p data-end=&quot;314&quot; data-start=&quot;283&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;314&quot; data-start=&quot;283&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나 역시 어느 날, &lt;b&gt;앱이 백그라운드에서 돌아오면 초기화된다는 CS를 여러 차례 받은 적이 있다.&lt;/b&gt;&lt;br /&gt;사용자가 앱 밖에서 작업을 수행하고 다시 돌아올 때 &lt;b&gt;기존 세션 상태가 유지되어야 했지만&lt;/b&gt;, 앱이 완전히 초기화되면서 불편함을 호소하는 문의가 다수 이어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_5864.jpg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lVPQo/dJMcaacvRXW/c87x0iG6DOyoNFEOiKk4D0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lVPQo/dJMcaacvRXW/c87x0iG6DOyoNFEOiKk4D0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lVPQo/dJMcaacvRXW/c87x0iG6DOyoNFEOiKk4D0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlVPQo%2FdJMcaacvRXW%2Fc87x0iG6DOyoNFEOiKk4D0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;365&quot; height=&quot;120&quot; data-filename=&quot;IMG_5864.jpg&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음엔 이 문제를 마주쳤을 땐 내가 앱을 다시 초기화하는 로직을 넣은 것도 아니고 앱이 저절로 초기화가 되는거라 해결해야 하나 싶었는데 CS 를 몇 번 마주치다 보니 사용자 입장에서 불편함을 무시할 수 없었고 이걸 대응을 해주어야 하지 않을까 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 글에서는 &lt;b&gt;Flutter에서 앱 복원 시 세션이나 상태를 안정적으로 복원하기 위해&lt;/b&gt; 내가 연구하고 적용했던 과정을 소개한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;왜 초기화가 될까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 앱이 백그라운드에 있을 때, 운영체제는 메모리를 확보하기 위해 앱을 강제로 종료할 수 있다.&lt;br /&gt;이때 앱은 사용자가 직접 종료하지 않았더라도 &lt;b&gt;시스템에 의해 완전히 재시작되는 상황&lt;/b&gt;이 발생한다.&lt;/p&gt;
&lt;p data-end=&quot;367&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;367&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;이 현상은 쉽게 재현할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;367&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;iOS의 경우, 앱을 백그라운드로 보낸 뒤 여러 앱을 연속으로 실행해보면 된다. 메모리가 부족해지면 운영체제가 이전 앱을 종료시키고, 다시 돌아왔을 때 앱이 &lt;b&gt;초기화된 상태&lt;/b&gt;로 시작되는 걸 확인할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;367&quot; data-start=&quot;218&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;510&quot; data-start=&quot;369&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드에서도 비슷하다.&lt;br /&gt;메모리를 많이 점유하는 앱을 여러 개 실행해보면 마찬가지로 앱이 강제로 종료되고 다시 실행될 때 &lt;b&gt;상태가 초기화되는 현상&lt;/b&gt;을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. &lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;로컬 저장소를 사용하여 상태를 로컬에 저장하고 앱 재시작 시 로컬 저장소 상태를 확인하여 복원하기&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 방법은 로컬 저장소인 &lt;b&gt;Shared Preference&lt;/b&gt; 와 앱의 &lt;b&gt;라이프 사이클&lt;/b&gt;을 활용하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트해본 결과, 앱이 OS 에 의해 종료되고 재시작될 경우, flutter 에서의 didChangeAppLifeCycle 중 resumed 는 타지 않는걸 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761469187332&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);
    switch (state) {
      case AppLifecycleState.resumed:
      	// 해당 state 가 실행되지 않는다
        break;
      case AppLifecycleState.paused:
        break;
      default:
        break;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 paused 에서 shared_preference 를 사용하여 해당 데이터를 저장시켜놓고, 만약 앱이 OS 에 의해 종료되지 않는다면 resumed 가 실행될거기 때문에 해당 state 에서 데이터를 삭제시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 앱이 OS 에 의해 종료된다면 resumed 가 실행되지 않아서 데이터가 삭제되지 않고 남아 있는다. 이땐 앱 시작 시점에서 데이터를 체크하여 데이터가 남아있다면 해당 화면으로 딥링크 처리하여 유저가 미션을 이어서 진행할 수 있게 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 위의 방법에서 하나의 문제점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;만약 앱이 OS 에 의해 종료된게 아니라 유저가 앱을 직접적으로 종료시킨거라면?&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;resumed 가 실행되지 않는 것에 대한 처리를 했기 때문에 OS 에 의해 앱이 종료되었는지 유저가 앱을 종료시켰는지 이에 대한 판별이 부족한 것이 1번 방법의 문제점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. RestorableMixin 활용하여 복원 시점인지 명확하게 구분&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지피티에게 물어보니 이에 대한 해결책으로 ResotrableMixin 을 사용하라고 권장해주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://api.flutter.dev/flutter/widgets/RestorationMixin-mixin.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;RestorableMixin&lt;/a&gt; 은 플러터에서 제공해주는 mixin 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 사용법은 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-1. restorationScopeId 설정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Flutter 에 상태 복원을 원한다는 것을 알려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MaterialApp 에 restorationScropeId 를 추가해주자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761475535871&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MaterialApp(
  restorationScopeId: 'root',
  ...
)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #242424; text-align: start;&quot;&gt;참고로 restorationScopeId 는 루트 수준에 복원 버킷을 생성하는데, 이는 앱의 &quot;게임 저장&quot; 기능을 활성화하는 것과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2-2. 상태 저장이 필요한 위젯에 RestorationMixin 적용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 위젯을 복원시킬지 지정하는 기능이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 기능은 RestorationMixin 을 해당 위젯과 같이 사용해주어야 한다. 이때 RestorationMixin 은 StatefulWidget 의 State 객체애 대한 복원 데이터를 관리하기 때문에 StatefulWidget 에다가 사용을 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제는 Flutter 공식 문서에 소개된 &lt;b&gt;카운터 상태 복원 예제&lt;/b&gt;이다.&lt;br /&gt;&amp;lsquo;+&amp;rsquo; 버튼을 눌러 숫자를 증가시킨 뒤, 앱을 백그라운드로 보내 시스템에 의해 종료되더라도 다시 실행하면 이전의 &lt;b&gt;카운터 값이 자동으로 복원&lt;/b&gt;되는 모습을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761475600033&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});
  @override
  State&amp;lt;MyHomePage&amp;gt; createState() =&amp;gt; _MyHomePageState();
}

class _MyHomePageState extends State&amp;lt;MyHomePage&amp;gt; with RestorationMixin {
  final RestorableInt _counter = RestorableInt(0);

  @override
  String get restorationId =&amp;gt; 'home_page';

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_counter, 'counter');
  }

  @override
  void dispose() {
    _counter.dispose();
    super.dispose();
  }

  void _incrementCounter() {
    setState(() {
      _counter.value++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('State Restoration 예제')),
      body: Center(
        child: Text('카운터: ${_counter.value}'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        child: const Icon(Icons.add),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RestorationMixin 동작원리는 어떻게 될까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 체제는 포그라운드에서 실행 중인 다른 앱의 리소스를 확보하기 위해 백그라운드에 있는 앱을 종료할 수 있다.&lt;br /&gt;이때 앱은 종료되기 전에 &lt;b&gt;복원 데이터를 직렬화(serialize)&lt;/b&gt; 할 기회를 얻게 된다.&lt;/p&gt;
&lt;p data-end=&quot;230&quot; data-start=&quot;164&quot; data-ke-size=&quot;size16&quot;&gt;이후 사용자가 다시 앱으로 돌아오면, 앱은 새로 시작되면서 &lt;b&gt;직렬화된 복원 데이터가 복원 과정에서 다시 제공되는 원리이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestorationMixin 에 사용되는 중요한 속성 몇 가지를 아래에서 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;RestorableProperty&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 속성은 상태 복원 중에 State 객체가 복원하려는 T 값의 유형의 객체를 관리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;앞서 살펴본 예제에서는 카운터의 상태값이 바로 RestorableProperty 중 하나인 RestorableInt 객체에 해당한다고 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RestorableProperty 는 &lt;a href=&quot;https://api.flutter.dev/flutter/services/StandardMessageCodec-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;StandardMessageCodec&lt;/a&gt; 으로 직렬화할 수 있는 객체의 표현으로 반환해야 한다. 즉, 쉽게 말해서 null, bool, num, string, List, Map 등으로 변환할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;StandardMessageCodec이 지원하는 타입&lt;/b&gt;&lt;br /&gt;null, bool, int, double, String Uint8List, Int32List, Int64List, Float64List List (원소들도 위 지원 타입이어야 함) Map (키/값 모두 위 지원 타입이어야 함)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 만약 커스텀 클래스를 아래와 같이 만들었다면 Map 타입의 직렬화할 수 있는 객체의 표현으로 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아래 예시의 toMap() 을 활용해야 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761477253393&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class TargetMission {
  final int id;
  final String name;

  const TargetMission(this.id, this.name);

  Map&amp;lt;String, Object?&amp;gt; toMap() =&amp;gt; {'id': id, 'name': name};
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 커스텀 클래스를 직렬화해야 하는 이유는 Flutter 는 네이티브 간에 데이터를 주고 받는 플랫폼이기 때문에 호환성 및 안정성을 위해 직렬화 가능한 타입으로 저장해야 하는 것이 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;restoreState&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1761477697421&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(target_id, 'save_key');
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드는 State.initState 바로 뒤에 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 State 라이프 사이클을 간단하게 말하면 실행 순서가 &lt;b&gt;initState -&amp;gt; restoreState -&amp;gt; build&lt;/b&gt; 순이 되는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 이 메서드에선 &lt;b&gt;registerForRestoration&lt;/b&gt; 이 호출되는데 모든 RestorableProperty 를 등록시켜야 한다. 이 등록은 해당 키에 복원 데이터가 있으면 값을 복원시켜줄 수 있게 되고, 복원 데이터가 존재하지 않는다면 default value 값으로 초기화할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(예를 들어 RestorableInt 를 사용하는데 복원 데이터가 존재하지 않으면 0으로 초기화된다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, restoreState 에서 앱이 상태복원 상태이면 해당 복원 값을 갖고 오게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;문제 해결&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 경우, &lt;b&gt;앱이 복원 상태인지 여부를 구분하는 전역 상태 변수&lt;/b&gt;를 두어 이 문제를 해결했다.&lt;br /&gt;앱이 복원 상태라면 로컬 DB에 캐싱해둔 데이터를 활용하고, 복원 상태가 아니라면 새로 데이터를 조회하도록 분기했다.&lt;/p&gt;
&lt;p data-end=&quot;231&quot; data-start=&quot;170&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;231&quot; data-start=&quot;170&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;231&quot; data-start=&quot;170&quot; data-ke-size=&quot;size16&quot;&gt;이번 글에서는 우선, &lt;b&gt;앱이 복원 상태인지 여부를 판별하는 변수를 설정하는 예제&lt;/b&gt;만 소개하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762061765603&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  final RestorableBool _isRestored = RestorableBool(false);

  @override
  String? get restorationId =&amp;gt; Constants.homeLayoutWithNavRestorationKey;

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(_isRestored, 'is_restored');
    _isRestored.value = true;
  }
  
  @override
  void dispose() {
    _isRestored.dispose();
    super.dispose();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;751&quot; data-start=&quot;685&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;751&quot; data-start=&quot;685&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;코드 해석&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;751&quot; data-start=&quot;685&quot; data-ke-size=&quot;size16&quot;&gt;먼저 위 화면이 최초 생성될 때는 복원값이 존재하지 않으므로 _isRestored 초기값은 false 로 설정된다.&lt;/p&gt;
&lt;p data-end=&quot;751&quot; data-start=&quot;685&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;restoreState 안에서는 복원값 등록 밑에 _isRestored 값을 true 로 바꿔준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정하는 이유는 이제 화면이 한 번이라도 켜졌으므로, true 로 설정하면 다음부터 이 화면이 복원될 때는 _isRestored 가 true 로 복원되기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;결국 이 로직을 통해 화면이 &amp;lsquo;최초 진입인지&amp;rsquo; 혹은 &amp;lsquo;복원된 상태인지&amp;rsquo;를 명확히 구분할 수 있게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;테스트 및 확인&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;위의 코드 동작을 확인하려면 핫 리스타트 및 핫 리로드를 통해서 확인은 불가하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;상태 복원을 테스트하려면 아래와 같은 방법으로 테스트하도록 &lt;a style=&quot;color: #333333;&quot; href=&quot;https://api.flutter.dev/flutter/services/RestorationManager-class.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식문서&lt;/a&gt;에 기재되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #5d686f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #4a4a4a; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;안드로이드&lt;/span&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #5d686f; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;설정 &amp;gt; 개발자 옵션 클릭&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: decimal;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&quot;활동 유지 안 함&quot; 옵션을 활성화하기&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 style=&quot;color: #4a4a4a; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아이폰 OS&lt;/span&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #5d686f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;iOS의 경우 XCode 수준에서 더 많은 준비가 필요하다. iOS 부분은&amp;nbsp;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://api.flutter.dev/flutter/services/RestorationManager-class.html#state-restoration-on-ios&quot;&gt;iOS에서 상태 복원&lt;/a&gt;&amp;nbsp;가이드를 참조하기.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;참고로 iOS 에서 이를 테스트하며 개발하기에 만만치 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;먼저 안드로이드에서 개발 및 확인하고 iOS 에서는 이를 마무리 확인하는 방식을 권장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러터에서는 상태 복원과 관련된 자료가 많지 않아서, 개인적으로는 꽤 흥미롭게 공부했던 주제였다.&lt;br /&gt;RestorationMixin을 잘 활용하면 &lt;b&gt;앱이 복원된 상태에서도 이전 데이터를 자연스럽게 복원해 보여줄 수 있어&lt;/b&gt;, 결과적으로 &lt;b&gt;사용자 경험(UX)을 크게 향상시킬 수 있을 것 같다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 개발하는 사람이라면, 유저 UX 경험 향상을 위해&amp;nbsp;&lt;b&gt;한 번쯤은 상태 복원에 대해 고민해보는 것이 좋지 않을까&lt;/b&gt; 생각하며 글을 마무리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>flutter restoration</category>
      <category>flutter restorationMixin</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/407</guid>
      <comments>https://ts2ree.tistory.com/407#entry407comment</comments>
      <pubDate>Sun, 2 Nov 2025 15:29:08 +0900</pubDate>
    </item>
    <item>
      <title>[개념정리] Gradle Plugin</title>
      <link>https://ts2ree.tistory.com/406</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 개발을 하면서 Gradle을 단순히 빌드 도구로만 인식하고, 그동안은 설정을 그대로 복붙해 사용하는 데 그쳤었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 문법이 달라진 문서를 접하면서 단순히 따라 치는 방식에는 한계가 있음을 느꼈고, 이에 Gradle의 기본 개념과 문법을 제대로 이해할 필요성을 깨닫게 되어 이 글을 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot;&gt;Gradle&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: justify;&quot;&gt;Gradle은 Groovy 를 기반으로 한 오픈소스 빌드 도구이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Ant, Maven과 같은 기존의 빌드툴은 xml 형식을 이용하여 정적인 설정정보를 구성했다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Gradle은 &lt;b&gt;Groovy 라는 언어&lt;/b&gt;를 이용하여 코드로서 설정정보를 구성하기 때문에 구조적인 장점이 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래 코드 예시를 같이 보며 차이점을 한 번 알아보자!&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Ant 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759566584813&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.xml
&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!-- 프로젝트 빌드용 Ant 스크립트. build.properties의 내용을 기본 설정으로 사용한다.--&amp;gt;
&amp;lt;project name=&quot;common&quot; default=&quot;compile&quot;&amp;gt;
    &amp;lt;property name=&quot;src.dir&quot; value=&quot;src&quot; /&amp;gt;
	 
  	&amp;lt;target name=&quot;clean&quot;&amp;gt;
        &amp;lt;delete dir=&quot;build&quot;/&amp;gt;
    &amp;lt;/target&amp;gt;

    &amp;lt;target name=&quot;compile&quot;&amp;gt;
        &amp;lt;mkdir dir=&quot;build/classes&quot;/&amp;gt;
        &amp;lt;javac srcdir=&quot;src&quot; destdir=&quot;build/classes&quot;/&amp;gt;
    &amp;lt;/target&amp;gt;

    &amp;lt;target name=&quot;jar&quot;&amp;gt;
        &amp;lt;mkdir dir=&quot;build/jar&quot;/&amp;gt;
        &amp;lt;jar destfile=&quot;build/jar/HelloWorld.jar&quot; basedir=&quot;build/classes&quot;&amp;gt;
            &amp;lt;manifest&amp;gt;
                &amp;lt;attribute name=&quot;Main-Class&quot; value=&quot;oata.HelloWorld&quot;/&amp;gt;
            &amp;lt;/manifest&amp;gt;
        &amp;lt;/jar&amp;gt;
    &amp;lt;/target&amp;gt;

    &amp;lt;target name=&quot;run&quot;&amp;gt;
        &amp;lt;java jar=&quot;build/jar/HelloWorld.jar&quot; fork=&quot;true&quot;/&amp;gt;
    &amp;lt;/target&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Ant 는 프로젝트가 커지면 엄청 복잡해진다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;또한 규칙이 없기 때문에 개발자마다 스크립트를 짜는 스타일이 달라 이해하는데 많은 시간이 소요될 수 있다고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(= 절차적 빌드 도구의 성격을 가져서 사용자가 직접 &quot;어떤 순서로 무엇을 할지&quot; 일일이 적어야 한다)&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-d4819878-4bd1-47fa-89f9-67c38981374a&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;​&lt;/span&gt;&lt;/p&gt;
&lt;p id=&quot;SE-5519ff87-6101-4d2d-ba31-a9387a9f5697&quot; style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Ant 는 remote repository 도 사용할 수 없다. &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Maven 이나 Gradle 을 사용할 때는 Maven repository 에서 의존성을 찾아서 넣어주기만 하면 되는데, Ant 는 불가능하다. 직접 jar 를 받아서 넣어줘야 한다는 단점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Maven 예시&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1759566737801&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// POM.xml
&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
  xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
 
  &amp;lt;groupId&amp;gt;com.mycompany.app&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;my-app&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;
 
  &amp;lt;properties&amp;gt;
    &amp;lt;maven.compiler.source&amp;gt;1.7&amp;lt;/maven.compiler.source&amp;gt;
    &amp;lt;maven.compiler.target&amp;gt;1.7&amp;lt;/maven.compiler.target&amp;gt;
  &amp;lt;/properties&amp;gt;
 
  &amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;4.12&amp;lt;/version&amp;gt;
      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;
&amp;lt;/project&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&quot;SE-f4fd0c9b-4972-443e-9194-4e63500f2180&quot;&gt;
&lt;p id=&quot;SE-3b661633-25ae-4e45-b8d2-e532154d7742&quot; style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Maven 은 Java 용 프로젝트 관리도구로 Apache 의 Ant의 대안으로 만들어졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;외부 라이브러리를 관리하고, 표준화된 포맷을 제공한다. 하지만 여전히 POM.xml 라는 xml 로 관리되고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Maven 은 외부저장소에서 필요한 라이브러리와 플러그인들을 다운로드한 다음, 로컬시스템의 캐시에 모두 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Gradle 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759566873221&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'org.springframework.boot' version '2.3.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache Maven 과 Apache Ant 의 대안으로써 나온 프로젝트 빌드 관리 툴인데 완전한 오픈소스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Groovy 언어를 사용한 Domain-specific-language, DSL 을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle 스크립트는&amp;nbsp;&lt;b&gt;Groovy&lt;/b&gt;나 &lt;b&gt;Kotlin DSL &lt;/b&gt;로 작성할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1796&quot; data-start=&quot;1727&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1758&quot; data-start=&quot;1727&quot;&gt;build.gradle &amp;rarr; Groovy DSL&lt;/li&gt;
&lt;li data-end=&quot;1796&quot; data-start=&quot;1761&quot;&gt;build.gradle.kts &amp;rarr; Kotlin DSL&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;여기서 Domain-specific-language (도메인 특화 언어) 뜻은 말 그대로 특정 목인인 도메인에 맞게 설계된 언어를 뜻한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;자바, 파이썬, C 같은 언어는 어떤 프로그램이든 만들 수 있는 범용적인 언어라면 SQL, HTML, Regex 는 각각 DB, 웹문서, 문자열 패턴 매칭 도메인에 특화되어 있다는 뜻이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maven 저장소도 그대로 사용 가능하며, xml 형식이 아니라서 Maven 보다 더 간결한 문법을 가지고 있다는 특징이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐싱과 병렬 빌드는 역시 지원 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니 요즘은 빌드 속도나 대규모의 멀티 프로젝트 관리 측면에서 Gradle 이 Maven 에 비해 우세를 가지고 있기 때문에 Gradle 사용이 점차 늘어나고 있다고 한다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(하지만 점유율은 Maven 이 아직까지 크다고 함)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Build.gradle&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle 은 Gradle 을 쓰는 프로젝트라면 어디서든 쓰는 빌드 스크립트 파일 이름이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;317&quot; data-start=&quot;111&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;208&quot; data-start=&quot;111&quot;&gt;&lt;b&gt;Android&lt;/b&gt;: Android Studio 프로젝트는 기본이 Gradle이라 &lt;b&gt;루트&lt;/b&gt;와 &lt;b&gt;모듈(app 등)&lt;/b&gt; 각각에 build.gradle이 있다&lt;/li&gt;
&lt;li data-end=&quot;317&quot; data-start=&quot;209&quot;&gt;&lt;b&gt;Spring Boot&lt;/b&gt;: Spring Boot는 &lt;b&gt;Maven 또는 Gradle&lt;/b&gt; 중 고를 수 있는데, &lt;b&gt;Gradle을 선택하면&lt;/b&gt; 프로젝트에 build.gradle이 생긴다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 spring boot 의 빌드 도구를 Maven 으로 선택하게 되면 pom.xml 파일에 프로젝트에 필요한 각종 의존성들과 라이브러리, 각 라이브러리의 버전 명시 등의 설정 정보를 역시 xml 형식으로 길게 나열하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android 와 Spring Boot 의 Build.gradle 예시를 찾아보니, 비슷한 모양새였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;안드로이드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759561768391&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
  id 'com.android.application'
  id 'kotlin-android'
}
dependencies {
  implementation 'com.google.android.gms:play-services-ads:24.5.0'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프링 부트&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759561781824&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
  id 'org.springframework.boot' version '3.3.0'
  id 'io.spring.dependency-management' version '1.1.5'
  id 'java'
}
dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Gradle 은 &quot;코드를 자동으로 빌드, 테스트, 패키징해주는 빌드 자동화 도구&quot; 여서 안드로이드, 스프링 부트 모두 Gradle 을 써서 프로젝트를 만들고 실행, 배포한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 plugins 와 dependencies 의 차이점은 뭘까?&lt;/p&gt;
&lt;p data-end=&quot;265&quot; data-start=&quot;187&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Plugins 와 Dependencies&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;plugin&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Gradle 빌드 시스템의 동작을 확장하거나 새로운 기능을 추가하는 모듈이다.&lt;/li&gt;
&lt;li&gt;빌드 과정 자체를 바꾸거나 추가한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759575107262&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id &quot;com.android.application&quot;    // 안드로이드 앱 빌드 규칙 추가
    id &quot;kotlin-android&quot;             // 코틀린 코드 컴파일 지원
    id &quot;com.google.gms.google-services&quot; // google-services.json 적용
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징 요약&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;508&quot; data-start=&quot;464&quot;&gt;빌드 과정에 새로운 task(작업)를 추가하거나 빌드 파이프라인을 확장.&lt;/li&gt;
&lt;li data-end=&quot;569&quot; data-start=&quot;511&quot;&gt;예: com.android.application 플러그인이 있어야만 APK/AAB 빌드 가능.&lt;/li&gt;
&lt;li data-end=&quot;594&quot; data-start=&quot;572&quot;&gt;&lt;b&gt;빌드 타임에 영향&lt;/b&gt;을 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;dependencies&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱 실행 시 외부 라이브러리나 모듈이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1759575187671&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation &quot;androidx.core:core-ktx:1.12.0&quot;
    implementation &quot;com.google.firebase:firebase-analytics&quot;
    testImplementation &quot;junit:junit:4.13.2&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;특징 요약&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;979&quot; data-start=&quot;936&quot;&gt;실제 애플리케이션 코드에서 사용하는 API, SDK, 유틸 등을 포함.&lt;/li&gt;
&lt;li data-end=&quot;1018&quot; data-start=&quot;982&quot;&gt;APK/AAB 안에 라이브러리 코드가 함께 묶여 들어간다.&lt;/li&gt;
&lt;li data-end=&quot;1048&quot; data-start=&quot;1021&quot;&gt;&lt;b&gt;런타임/컴파일 타임에 영향&lt;/b&gt;을 준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;1377&quot; data-start=&quot;1361&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;Plugins 와 Dependencies &lt;/b&gt;의 관계&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1788&quot; data-start=&quot;1379&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1581&quot; data-start=&quot;1379&quot;&gt;어떤 기능은 &lt;b&gt;플러그인 + 라이브러리&lt;/b&gt;가 짝으로 필요할 때가 있다&lt;br /&gt;예: Crashlytics &amp;rarr; 플러그인(com.google.firebase.crashlytics)은 매핑파일 업로드/빌드 연계를 하고,&lt;br /&gt;라이브러리(com.google.firebase:firebase-crashlytics)는 앱에서 크래시를 실제로 수집&amp;middot;전송한다.&lt;/li&gt;
&lt;li data-end=&quot;1690&quot; data-start=&quot;1583&quot;&gt;&lt;b&gt;플러그인을 쓴다고 라이브러리가 자동으로 들어오지 않는다.&lt;/b&gt;&lt;br /&gt;반대로 라이브러리를 넣었다고 관련 플러그인이 자동 적용되는 것도 아니다. 용도에 따라 둘 다(혹은 하나만) 필요하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;plugin 버전 관리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서에서 아래와 같은 코드가 있고, &lt;span style=&quot;color: #006dd7;&quot;&gt;apply false&lt;/span&gt; 도 같이 쓰여 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;루트&lt;/b&gt; build.gradle.kts&lt;/p&gt;
&lt;pre id=&quot;code_1759563018374&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 최신 버전 작성 방식
plugins {
    id(&quot;com.google.gms.google-services&quot;) version &quot;4.4.3&quot; apply false
    // 다른 플러그인들도 여기서 버전만 고정
}


// 과거 버전 작성 방식
buildscript {
  dependencies {
    classpath 'com.google.gms:google-services:4.4.3'
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드의 의미는 해당 Gradle 플러그인을 &amp;ldquo;현재 모듈에는 적용하지 않고, 버전만 선언해 둔다&amp;rdquo;&lt;span&gt;&amp;nbsp;&lt;/span&gt;는 뜻이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통 루트 build.gradle[.kts](또는 settings.gradle의 pluginManagement)에서 이렇게 써서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;플러그인 버전을 한 곳에서만 관리&lt;/b&gt;하고, 실제 적용은 각 서브모듈에서 하게 만든다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;apply false: 현재(루트) 프로젝트엔&amp;nbsp;적용 안 함. 하위 모듈에서 필요할 때 id(&quot;...&quot;) 만 적어&amp;nbsp;적용할 수 있음.&lt;br /&gt;참고로 apply 를 생략하거나 apply true 면 그 파일의 모듈에 즉시 적용된다.&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;app 모듈&lt;/b&gt; build.gradle.kts&lt;/div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1759563035010&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id(&quot;com.android.application&quot;)
    id(&quot;com.google.gms.google-services&quot;) // 여기서는 버전 없이 적용만!
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서브 모듈인 App 에서 위와 같이 적용을 시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;버전은 루트에 한 번만 적으므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;버전 일관성&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;업데이트 관리&lt;/b&gt;가 쉬워지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글을 포스팅함으로써 Gradle을 단순히 빌드 도구로만 생각하고 복붙해서 쓰던 과거와 달리, 문법과 개념을 정리하면서 &amp;ldquo;왜 이렇게 동작하는지&amp;rdquo;를 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Ant, Maven, Gradle의 차이를 비교해보니 빌드 도구도 진화해왔다는 게 눈에 보였고, plugins vs dependencies, apply false 같은 개념을 알게 되니 앞으로는 버전 관리나 설정을 훨씬 더 체계적으로 할 수 있기를 기대해본다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/406</guid>
      <comments>https://ts2ree.tistory.com/406#entry406comment</comments>
      <pubDate>Sat, 4 Oct 2025 21:06:19 +0900</pubDate>
    </item>
    <item>
      <title>Flutter MaterialApp 에서 home, initialRoute 차이</title>
      <link>https://ts2ree.tistory.com/405</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Flutter 의 MaterialApp 에는 많은 속성이 존재한다.&lt;br /&gt;Route 관련해서도 정말 많은 속성이 존재하는데 나도 처음엔 헷갈린 채로 사용했던 것 같다.&lt;br /&gt;이번 글은 혹여나 나와 같이 헷갈리는 사람이 있지 않을까 하여 포스팅해본다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;MaterialApp 에서 home, initialRoute 의 차이는?&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 둘의 차이가 도대체 무엇인지 헷갈린다.&lt;br /&gt;둘 다 앱 초기 라우트를 호출하는 것 아닌가?&lt;br /&gt;그건 맞다. 하지만 공식 문서를 보면 initialRoute 를 정의한다면 home 은 정의하지 말아야 한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;191&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAeaow/btsQzgZ7zw1/rxWWWoiVdtsMM4rnSUI970/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAeaow/btsQzgZ7zw1/rxWWWoiVdtsMM4rnSUI970/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAeaow/btsQzgZ7zw1/rxWWWoiVdtsMM4rnSUI970/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAeaow%2FbtsQzgZ7zw1%2FrxWWWoiVdtsMM4rnSUI970%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1180&quot; height=&quot;191&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;191&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;왜 둘을 같이 사용할 수 없는가에 대해서 생각해보려면 둘의 역할을 각각 알아봐야 한다.&lt;br /&gt;이 둘의 역할은 다음과 같다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;home 의 역할&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;- 앱의 최초 진입 화면을 지정한다.&lt;br /&gt;- 만약 home 을 지정한다면 routes 에 / 를 설정해서는 안된다. 즉, / 의 역할이 된다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #9feec3;&quot;&gt;&lt;b&gt;initialRoute 의 역할&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;- 처음 표시할 라우트 이름을 지정할 수 있다.&lt;br /&gt;- &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #4a4a4a;&quot;&gt;경로 이름이 슬래시로 시작하면 &quot;딥 링크&quot;로 처리되며, 이 경로가 푸시되기 전에 이 경로로 연결되는 경로도 푸시된다. (아래에서 더 자세히 설명할 예정)&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;공식문서에서는 home 의 경우, initialRoute 가 지정되지 않았거나 intialRoute 를 표시할 수 없는 경우 home 이 대신 표시된다고 한다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아무튼 개발자가 예상하지 않는 동작이 일어날 수 있기 때문에 두 개를 동시에 지정하지 않도록 권장하는 것 같다.&lt;br /&gt;이걸로는 아직 동일한 기능을 하는 것처럼 보이는 두 속성이 왜 별도로 존재하는지 이해하기 어렵다.&amp;nbsp;아래에서 좀 더 내용을 자세히 다뤄보겠다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;initialRoute 의 세부 동작&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;보통이면 초기 라우트를 / 로 지정하는 것이 일반적일 수 있다.&lt;br /&gt;하지만 나 같은 경우, 이미 다른 화면이 / 경로로 지정되어 있어서, 초기 라우트를 / 로 설정하면 혼동이 생길 것 같았다.&lt;br /&gt;&lt;br /&gt;그래서 initialRoute 에 /initial 라는 Named Route 로 지정하고 해당 경로를 routes 나 onGenerateRoutes 에 처리하도록 구성했다.&lt;br /&gt;그리고 정말 예상치 못한 일이 일어났다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;예상 동작&lt;/b&gt;&lt;br /&gt;앱 시작 시 /initial 이 호출되어 지정한 초기 화면으로 이동이 된다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;실제 동작&lt;/b&gt;&lt;br /&gt;앱 시작 시 두개의 화면이 호출된다.&lt;br /&gt;&lt;br /&gt;이에 대해 공식 문서를 찾아보았고 아래처럼 initialRoute 에 대해 정의되어 있었다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;initialRoute property&lt;/b&gt;&lt;br /&gt;For example, if the route was /a/b/c, then the app would start with the four routes /, /a, /a/b, and /a/b/c loaded, in that order.&lt;br /&gt;Even if the route was just /a, the app would start with / and /a loaded.&lt;/blockquote&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;위에서 썼던 &lt;u&gt;&quot;&lt;/u&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #4a4a4a;&quot;&gt;&lt;u&gt;경로 이름이 슬래시로 시작하면 &quot;딥 링크&quot;로 처리되며, 이 경로가 푸시되기 전에 이 경로로 연결되는 경로도 푸시된다.&quot;&lt;/u&gt; 가 바로 이 의미이다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;즉 /initial 은 / -&amp;gt; /initial 이렇게 동작하고, 이걸 &quot;딥 링크&quot;로 처리된다고 말하는 것이었다.&lt;br /&gt;웬만해서 초기 화면 설정은 하나의 화면으로 설정하는 것이 대부분이긴 하지만, 예외의 경우 여러 스택의 화면이 필요한 경우도 있기 때문에 initialRoute 속성이 존재하는 것 아닐까 생각해본다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 결론은 &lt;b&gt;대부분의 경우&lt;/b&gt; 초기 화면을 정해야 한다면, 일반적으로는 home 속성으로 처리하는 것이 가장 깔끔하고 단순하다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다만 시작 지점을 초기 &lt;b&gt;네비게이션 스택을 미리 구성&lt;/b&gt;해야 하는 경우 등 예외의 케이스가 있어야 한다면 initialRoute를 사용하는 편이 적합하지 않을까&lt;br /&gt;&lt;s&gt;헷갈려!!&lt;/s&gt;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>flutter initialRoute</category>
      <category>flutter initialRoute home</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/405</guid>
      <comments>https://ts2ree.tistory.com/405#entry405comment</comments>
      <pubDate>Wed, 17 Sep 2025 01:07:32 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] iOS 웹뷰 흰 화면 뜨는 현상 2탄</title>
      <link>https://ts2ree.tistory.com/401</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;http://[Flutter] iOS 웹뷰 흰 화면 뜨는 현상 - https://ts2ree.tistory.com/m/393&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;지난번 썼던 글&lt;/span&gt;&lt;/a&gt;이 생각보다 찾는 사람이 많다는 것을 알게 되면서 좀 더 보완할 겸 2탄으로 돌아오게 되었다.&lt;br&gt;또한 이번 회사에서 하는 업무도 웹뷰가 상당 부분 구성되어 있는 앱을 만들게 되면서 다시 똑같은 이슈를 맞닿뜨리게 되었다.&lt;br&gt;&lt;br&gt;&lt;br&gt;1탄에서는 unity webgl 이라는 기술을 사용해서 flutter 앱 안에 웹뷰를 띄우는 방식이었는데 이번 회사에서는 앱 안에 react 로 만들어진 웹뷰를 띄우는 방식이었다.&lt;br&gt;&lt;br&gt;&lt;br&gt;이직한 첫 날, 회사 사람들이 내게 질문했었다.&lt;br&gt;다른 개발자들: 시간이 지나면 하얀색 화면이 뜨는데 혹시 ***님은 이유를 아시나요?&lt;br&gt;나: 혹시 iOS 에서 발생하는 걸까요?&lt;br&gt;다른 개발자들: 네 그랬던 것 같아요.&lt;br&gt;&lt;br&gt;&lt;br&gt;그때 내가 맞닿뜨렸던 이슈가 생각나면서 unity webgl 과 같은 무거운 웹뷰뿐만 아니라 거의 모든 웹뷰가 이 현상이 발생하는 것이라는 것을 깨닫게 되었다. (iOS 한정)&lt;br&gt;&lt;br&gt;&lt;br&gt;1탄에서 내가 생각했던 해결책은 아래와 같았다.&lt;br&gt;&lt;b&gt;1. webviewController 를 reload 하기&lt;/b&gt; (대부분이 1번에서 해결할 것으로 추정함. 하지만 나 같은 경우, webgl 이라는 무거운 기술을 사용했기에 백그라운드 환경에서 reload 가 수없이 반복되다가 제한되는 현상이 발생함 &amp;gt;&amp;gt; 2번으로 해결하였음)&lt;br&gt;&lt;b&gt;2. 앱이 paused 상태일 때 webviewController 를 null 로 해제해버리고 resumed 상태가 되면 webviewController 다시 할당&lt;/b&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;2번처럼 해결했기에 앱을 껐다가 키면 웹뷰가 무조건 로드되는 현상을 맞이하게 되었다.&lt;br&gt;그때 당시 이 문제를 100% 해결하지 않았었다고 생각했고 입사 첫 날, 다시 이 해결책에 대해 고민해본 것 같다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;토스가 하는 방식&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;해결하기 어려울 땐 비슷한 기술을 구현해낸 어플을 찾아보고 모방하는 편이다. 그 중 토스 어플을 대상으로 테스트해보기로 했다. &lt;br&gt;(토스는 웹뷰가 상당수 구성된 React-Native 앱이다.)&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;&amp;lt;테스트 결과&amp;gt;&lt;/b&gt;&lt;br&gt;토스 앱의 웹뷰 화면을 켜두고 내려두었다가 다시 켜본다 -&amp;gt; 그대로 남아있음&lt;br&gt;토스 앱의 웹뷰 화면을 켜두고 내려두었다가 다른 앱을 10개 이상 켜본다 -&amp;gt; 그 결과, 토스 앱을 다시 켜보았을 때 메인 화면으로 리다이렉트되는 현상을 보였다.&lt;br&gt;&lt;br&gt;&lt;br&gt;여기서 힌트를 얻었다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2탄의 해결책&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;토스 어플이 메인 화면으로 리다이렉트가 되는 이유가 정확히 이것&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(= 웹뷰 백화 현상)&lt;/span&gt; 때문인지는 확신할 수 없지만 내 생각에는 왠지 그렇지 않을까…? 추측해본다.&lt;br&gt;토스와 비슷하게 구현해내기 위해서 아래와 같이 생각해봤다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;1. 앱을 사용중일 때만 webviewController 를 reload 하기&lt;/b&gt;&lt;br&gt;앱을 사용중일 때만 &amp;lt;— 이 조건이 하나 붙었다. 즉, 앱이 paused 된 경우가 아닐 때에만 reload 시키자.&lt;br&gt;isPausedOnIOS 라는 변수를 맨 처음에 false 로 선언해둔다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;C++&quot; data-ke-language=&quot;C++&quot;&gt;&lt;code&gt;bool _isPausedOnIOS = false;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;이때 다음과 같이 웹뷰가 종료된 이벤트가 오게 되면 아래와 같이 isPausedOnIOS 가 false 인 경우만 reload 처리한다.&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;C++&quot; data-ke-language=&quot;C++&quot;&gt;&lt;code&gt;onWebResourceError: (error) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (error.errorType == WebResourceErrorType.webContentProcessTerminated) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (_isPausedOnIOS == false) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_controller.reload(); // 앱 실행중일 때만 reload 처리
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;b&gt;2. 백그라운드에서 종료되는 현상 해결하기&lt;/b&gt;&lt;br&gt;백그라운드에서는 웹뷰가 kill 되어도 내비둘 것이다. &lt;br&gt;웹뷰가 kill 되도록 내버려둔 이유는 iOS 정책(백그라운드에서 웹뷰를 종료해버리는 현상)을 그대로 따르는 것이 좋다고 생각했기 때문이다.&lt;br&gt;이제 다른 앱을 10개 이상 켜본다.&lt;br&gt;그러면 웹뷰가 kill 되었을 것이다. 앱을 paused 된 상태에서는 웹뷰를 reload 를 하지 않았기에 그대로 웹뷰의 currentUrl 이 null &lt;span style=&quot;color: #666666;&quot;&gt;(url 이 about:blank)&lt;/span&gt; 이 된다.&lt;br&gt;&lt;br&gt;&lt;br&gt;앱을 다시 사용하려고 키게 될 경우, currentUrl 이 null 이 되었는지 확인하고 null 이 맞다면 웹뷰를 초기화시킨다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(혹은 다른 화면으로 리다이렉트시켜줘도 됨)&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;C++&quot; data-ke-language=&quot;C++&quot;&gt;&lt;code&gt;if (state == AppLifecycleState.resumed) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_isPausedOnIOS = false;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (await _controller.currentUrl() == null) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_initializeWebView();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else if (state == AppLifecycleState.paused) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (Platform.isIOS) _isPausedOnIOS = true;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;2탄의 해결책으로 앱을 출시하였고 현재 별 문제가 없는 것 같다.&lt;br&gt;또한 1탄의 해결책으로 했던 문제점 &lt;span style=&quot;color: #666666;&quot;&gt;(앱을 킬 때마다 웹뷰 초기화)&lt;/span&gt; 을 &lt;b&gt;‘앱을 켰을 때의 웹뷰가 메모리 이슈로 종료되었을 때만 초기화시키기’&lt;/b&gt; 로 해결해서 앱을 킬 때마다 로드가 안되는 현상이 사용성 측면에서 장점이 보였다.&lt;br&gt;&lt;br&gt;&lt;br&gt;웹뷰 흰 화면 뜨는 현상에 대해 많은 분들이 검색하는 것 같은데 1탄에서 해결을 완전히 해결하지 못하였기에 조금 부끄러운 글이 되어버린 것 같다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(하지만 후회는 하지 않는다. 내가 생각했던 최선의 방법이었다.)&lt;/span&gt;&lt;br&gt;2탄으로 작성한 이 글도 flutter 개발자들에게 많은 도움이 되었으면 한다.&lt;br&gt;더 좋은 해결책 있다면 언제든 환영입니다! ^^&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt; &lt;/p&gt;</description>
      <category>  Flutter</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/401</guid>
      <comments>https://ts2ree.tistory.com/401#entry401comment</comments>
      <pubDate>Sat, 1 Feb 2025 19:32:16 +0900</pubDate>
    </item>
    <item>
      <title>2024 회고록</title>
      <link>https://ts2ree.tistory.com/400</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;순천에 추억을 남기다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해 초는 잠깐 이사님 회사의 소속이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이사님의 회사가 전남 순천이어서 3월 달에 2번 정도 전남 순천에 내려가게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_3449.JPG&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ylpsB/btsLAvgvffo/8CJBiRpPo2AP8ZljNsdJp1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ylpsB/btsLAvgvffo/8CJBiRpPo2AP8ZljNsdJp1/img.jpg&quot; data-alt=&quot;2024.3.9일자 순천&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ylpsB/btsLAvgvffo/8CJBiRpPo2AP8ZljNsdJp1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FylpsB%2FbtsLAvgvffo%2F8CJBiRpPo2AP8ZljNsdJp1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;511&quot; height=&quot;383&quot; data-filename=&quot;IMG_3449.JPG&quot; data-origin-width=&quot;4032&quot; data-origin-height=&quot;3024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2024.3.9일자 순천&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전남 순천에서 순천만습지만 가본 나는 의외로 순천이라는 도시가 내 취향이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 주변에 위치한 문화마을을 걸어다니며 둘러보았는데, 가게와 카페들이 대체로 아기자기하였고 자연도 있어 매력적이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것이 한적하였고 여유로움이 느껴졌다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(서울에서 느껴본 적 없었던 느낌)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이사님 회사 가서 이사님이랑 밥도 먹고 대화도 나누고 힐링의 시간을 가져서 좋은 추억이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;특이한 경력을 갖게 되다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;24년 7월에 이사님 회사에서 다시 대표님 회사로 이동하였다. 그렇게 나는 3개 회사의 경력을 가지게 되었다. 그때 당시 나는 회사 이동 경력에 대해 별 생각이 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 사정이 힘든 걸 알았기에 조금이라도 부담을 덜기 위해 선의의 마음으로 이동하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모님도 내가 즐겁게 다니는 걸 보고 회사 이동에 대해 딱히 별 말을 하진 않으셨다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 내가 부모였다면 당장 옮기라고 그랬을거고 속 터졌을 것이다. 이 점은 정말 부모님께 감사하다.. 언제나 믿어주셔서 감사하고 나도 그에 걸맞게 더 열심히 일하려 했고 항상 발전적으로 살려고 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중의 에피소드지만 이력서 리뷰해시는 분이나 채용하시는 분들이 이력서를 보고 대체 이게 무슨 일인건지 의아해하셨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;면접관: 회사가 왜 3개야..?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;교토 여행&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_4100 3.JPG&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IqgXr/btsLCHl27bT/zBwP4keUPNvdpy3gUndBN0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IqgXr/btsLCHl27bT/zBwP4keUPNvdpy3gUndBN0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IqgXr/btsLCHl27bT/zBwP4keUPNvdpy3gUndBN0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIqgXr%2FbtsLCHl27bT%2FzBwP4keUPNvdpy3gUndBN0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;287&quot; height=&quot;383&quot; data-filename=&quot;IMG_4100 3.JPG&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10월에 아버지랑 교토를 여행하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;교토 힐링 여행을 생각하고 갔다 왔는데 교토 스트레스 여행이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 사람이 정말 너무 많았는데 나는 사람 많은 곳을 싫어했다. 어딜 가도 사람이 많았고 버스를 타면 출퇴근을 하는 기분이 들었다. (그만큼 사람이 많았다는 뜻)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밥도 먹으려면 바로 먹을 수 없었고 웨이팅을 해야 했다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 많은 곳을 싫어하는 친구가 있다면 나는 교토를 추천하진 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;일을 열심히 하였고 환상의 콤비로 활동하다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 올해를 되돌아 봤을 때 뭘 했는지 기억이 특별히 나지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유가 올해를 되돌아보면 일을 대부분 했다는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 사수 언니와 단둘이 앱 개발을 맡았었는데 일하면서 사수 언니분께 항상 감사한 마음을 가졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사수 언니분께 감사했던 점은 항상 일하다가 나에게 '제가 뭐 도와드릴게 있을까요?' 라고 물으셨다. 그래서 나도 내 일을 빨리 끝내고 사수분의 일을 더 도우려고 노력했던 것 같다. 그렇게 해서 합이 정말 잘 맞았고 개발도 정말 빨리 끝내버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;글또, 나의 취미 활동 Only One&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글또를 8기부터 꾸준히 활동을 이어왔다. 어째 활동을 이어올수록 더 열심히 활동을 하는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 마지막 기수인 10기를 활동 중인데 소모임 활동이 이전보다 특히 다양하고 더 재밌어진 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 소모임장들은 4장님이라는 호칭을 붙여서 각자 소모임을 열심히 이끌어주시었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 스터디를 많지는 않지만 한 두번 정도 열어본 기억이 있는데 생각보다 모든 구성원들을 이 끄는건 쉽지 않은 일임을 알고 있다. 4장님들이 재밌게 이끌어주셔서 감사하고 그에 보답해 나도 열심히 참여하려고 노력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 아래 소모임 활동이 기억이 남았다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다진마늘
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오늘 해야 할일을 TODO 를 적고 출근, 퇴근을 하는 모임인데 4장님이 소모임 활성화를 위해 이런 방법도 시도해보고 저런 방법도 시도해보면서 더 나은 방법을 찾아가는 모습이 인상 깊었다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이력또
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이력서를 피드백해주시는 소모임인데 어떠한 돈도 받지 않고 얼굴도 모르는 생판 남의 이력서를 보고 피드백해주신다. 그 모습이 나에겐 참 멋져보였다. 나도 어느 정도 연차가 차면 신입 개발자들 이력서와 포트폴리오를 리뷰하는 등 &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;이런 선한 영향력을 행사하기로 다짐했다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;왜요또
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPOP 관련 소모임이다. 특이점은 4장님이 유튜브 라이브를 켜신다.(ㅋㅋㅋㅋ) JPOP에 관심도 없었지만 한 번 들어가봤는데 JPOP이 이렇게 좋은 곡들이 많았나..? 라는 생각이 들었다. 덕분에 즐겁게 JPOP 들으면서 공부를 했었다. 감사했다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대나무숲
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;익명으로 글을 올릴 수 있는 게시판이다. 최근 이직 문제로 이런저런 고민이 많아 익명으로 몇 번 올린 적 있다. 그때마다 친절한 선배 개발자분들이 의견을 많이 올려주셨고 도움이 정말 많이 되었다. 나도 누군가에게 그런 도움을 줄 수 있었으면 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 뿐만 아니라 성윤님, 은찬님, 글또 운영진분들도 자신의 시간을 내주어 커뮤니티를 활성화하려는 모습 정말 감사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 재밌는 활동도 많이 생기고 규모도 커져서 내가 더욱 활발하게 활동해진 것 아닌가.. 싶은 생각이 든다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(제일 오랫동안 활동한 취미 활동이었다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 글또 없어지면 나는 어디로 흥미를 붙여야 하나..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예상치 않은 퇴사와 이직 준비&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;사실 현재 회사에서 될 수 있는 한 오랫동안 있으려 했다. 하지만 회사 사정이 안좋아 이직을 해야 하는 순간이 생각보다 빨리 찾아왔다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;그동안 이직 준비를 해놓아야 하는건 알고 있었는데 솔직히 취업 준비가 무섭고 두려웠다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;그러나 언젠간 이직은 해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;나는 인턴을 해서 합격했기에 정말 감사하게도 취업 준비를 많이 해보지 못했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;그래서 이직 준비, 취업준비를 이제서야 본격적으로 하기로 맘을 먹었다. 이력서라는 것을 작성해보았고 포트폴리오도 처음 만들어 보았다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;면접 준비도 처음 해보기 시작했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;구글링해가며 예시 이력서를 참고해가며 이력서를 작성해보기 시작했다. 처음에 막막했다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;내가 한 일이 너무 많은데 여기서 몇 개만 뽑아서 작성해야 하는건가?&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;나는 앱을 개발했는데 앱에서의 성능 향상, 개선하였다라고 적으려면 이런건 무슨 근거로 작성을 해야하나? 여러 번 고민하고 어찌 되었든 간에 조금은 부족한 이력서를 완성시키게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;이직 - 면접 준비&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 면접을 어떻게 합격했는지도 모르겠다. 사실 면접으로 합격한게 아니라 인턴을 했었기에 운좋게 붙었다고 생각한다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 나는 면접 경험이 0에 도달한다고 생각했고 무서웠다.&lt;br /&gt;이제는 정말 외면하고 싶지 않았고 이번 기회에 면접 공포증을 무조건 극복하고 싶었다.&amp;nbsp;&lt;br /&gt;그래서 면접을 공부해보기로 했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가 인생 경험이 많던 친오빠가 했던 말이 굉장히 도움되었다.&lt;br /&gt;&quot;경력직 면접은 신입이랑 달라. 너가 했던 거 있지. 그걸 그대로 설명하면 돼&quot;&lt;br /&gt;거기서 내가 면접을 한참 잘못 알고 있었다는 것을 깨달았다. (유튜브에서 대기업 신입직 면접 준비영상을 보고 있었던 사람.. 나)&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;면접은 대화다&quot;&amp;nbsp;&lt;/b&gt;&amp;gt; 부끄럽지만 나는 이때 알았다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;예상 외로 임원 면접 준비가 나에게 힘들었다.&lt;br /&gt;임원 면접은 최종 면접이고 나는 어떤 사람인가에 대해 평가하게 되는 자리인데 생각보다 나 자신을 몰라서 어려웠다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 나보다 높으신 분들 앞에서 내가 술술 잘 말할 수 있을까? 그것도 두려움이 컸다. 그래서 쉬는 동안 인성 질문 몇 개를 추려놓고 미리 예상 답변을 생각해보았다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;처음엔 면접이라고 생각하지 않고 답변을 정리해나갔다. 나의 성격 장단점, 리더형인가 팔로워형인가, 내가 살아온 과정들..&lt;br /&gt;정리된 후 혼자 카페에 가서 조금씩 말해보기도 하고 마인드 세팅을 다져나가는 시간을 가졌다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;이직 제안과 면접&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;취업 준비 와중에 이사님께서 플러터 개발자를 구한다는 주변 지인이 있어 소개 시켜준다고 하셨다.&lt;br /&gt;심지어 내가 원하는 B2C 일종의 리워드 앱이었다.&lt;br /&gt;나는 이전 경력에서 리워드 앱을 만들어봤지만 너무 재밌게 일했고 좋았어서 동일하게 리워드 어플 개발을 하고 싶었다.&lt;br /&gt;원했던 업종, 자신있는 이전 경험치.. 모든 것이 다 들어맞았다.&lt;br /&gt;그래서 흔쾌히 수락했다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;미팅을 하러 갔고 면접도 하였다.&lt;br /&gt;마인드 세팅을 하고 갔지만 역시나 면접 경험치 0% 였던 나는 면접을 망해버린 것 같다는 생각이 들었다.&lt;br /&gt;면접 피드백으로 추상적으로 말하는 것 같다라고 하셨다.&lt;br /&gt;면접이 끝나고 집에서 왔을 때 나는 바로 면접 복기를 하였고 내가 부족했던 점을 아래처럼 차근차근 적었다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;추상적으로 얘기했다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나는 평소에 구체적으로 말한다고 생각했었다.&lt;br /&gt;&amp;gt; 해결방법: 평소의 나의 생각에서 꼬리 질문을 달아보자&lt;br /&gt;예를 들어) UI 가 예쁘다고 생각합니다.&lt;br /&gt;&amp;gt; 어떤 부분이? 왜 예쁜 것 같나요?&lt;br /&gt;&amp;gt; 단순히 시각적인 미뿐만 아니라 디자인 요소가 기능성을 잘 뒷받침하기 때문입니다. 예를 들어 색상 조합이 명확하고 조화로워서 정보의 우선순위를 직관적으로 이해할 수 있었고 폰트와 간격 또한 적절하게 배치되어 사용자가 콘텐츠를 읽는데 부담이 없었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답변 중에 잘못 말했거나 확신이 없다고 느껴질 경우&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&amp;gt; 빠르게 인정하고 수정하자 (해결책이 이게 맞나 모르겠음..)&lt;br /&gt;&amp;gt; 처음에는 특정 관점에서 답을 드렸는데, 질문을 더 깊이 생각해보니 다른 관점에서 더 적합한 답이 떠올랐습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접은 역시 여러 번 봐야 한다고 하는 말을 틀리지 않다고 생각이 들었는데&lt;br /&gt;왜냐하면 아무리 집에서 연습을 해도 결국 실전을 경험하지 않으면 무엇이 부족한지 모른다는 것이다. 나름 준비를 했었고 자신감 있게 갔는데 꼬리 질문은 예상보다 많았고 꽤 구체적으로 물으셔서 실전에 가서 당황했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;한편으론 인생에서 면접 피드백을 받아본 적이 없어서 피드백을 조금이나마 준 것을 감사했다.&lt;br /&gt;위와 같은 점들이 내가 부족한 점이라는 것을 인지했고 좀 더 구체화해보는 연습, 답변 중에 확신이 없다고 느껴지면 인정하고 수정하기가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;첫 번째 면접의 교훈&lt;/b&gt;이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;이직하다&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이직 준비를 해보는 김에 차근차근해서 이직 실력을 쌓아볼까 생각했다. 정말 면접을 프리패스할 정도로 연습하고 싶었다.&amp;nbsp;&lt;br /&gt;근데 주변 개발자들의 장기간 취업 준비썰, 탈락썰, 자존감 낮아있는 모습을 보게 될 때마다 내 맘이 편치 않았고 나 또한 일하지 않으면 우울감을 느끼는 사람이었기에 역시 기회가 왔을 때 빨리 일하는 것이 낫다고 판단하였다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 회사는.. 원했던 도메인, 다양한 분야의 개발자가 있음! (&amp;lt;- 제일 설레는 부분..)&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 개인적 주관으로 파악했을 때에는 트렌드 파악이 빠른 느낌과 개발에 관심 있는 느낌, 이 정도의 장점이 느껴졌다.&lt;br /&gt;위와 같이 판단하였고 또한 추천으로 온 제의이기 때문에 면접을 저렇게 봐놓고도 이직을 할 수 있게 되었다.&lt;br /&gt;그래서 정말 감사하다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이전 회사에서 못했던 개발자로서의 성장 갈증을 바로 이 회사가 챙겨주지 않을까 싶었고 포지션이 앱 리드 담당자(?)로 갈 것이기 때문에 모든 것이 새로운 도전이 될 것 같다. 내가 사수 역할이라니..&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정말 부족함 없이 열심히 일해야지.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;나는 어떤 개발자인가&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 글을 적으며 그동안의 회사 생활을 돌아봤다.&lt;br /&gt;개발이 뭔지 모르던 상태로 왔고 플러터를 실무 업무로 하면서 플러터도 앱 개발도 배워나가기 시작했다. 개발팀 전부 퇴사하고 나는 끈질기게 남아 업무를 하면서 3번의 이직도 했다. 그리고 한 가지 앱을 개발, 유지보수하면서 경험이 점점 쌓이다보니  모든 시스템을 이해할 수 있게 되고 큰 이슈들을 번번히 처리할 수 있었다.&lt;br /&gt;그리고 회사 문을 닫게 되었다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;회사가 규모가 작아 현재 나의 개발 실력이 어느 정도에 위치하는지는 모른다. 사실 개발자들이 흔히 말하는 성능 최적화, 테스트 코드 작성, 자동화 모두 해본 적이 없고 할 줄 모르니 개발적인 측면으로 보면 나는 못하는 쪽에 가까울 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만 다른 부분에서 열심히 노력하는 편이다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일을 맡으면 기한 내에 무조건 끝내려 하고 피드백도 바로 반영하려고 노력한다. 그리고 내 일이 아니어도 내가 책임져야 한다는 생각을 가지고 일했었다. 이슈를 맞닿뜨리면 보통 며칠 동안 생각하는 편이었다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그렇게 일을 해왔더니, 경영진으로부터 '여태까지 회사 생활 7, 8년 중 여러 개발자를 보았지만, 맡은 일을 가장 빠르게 성과로 연결하고 개발도 가장 잘하는 사람'이라는 칭찬과 인정을 받았다. 그리고 이렇게 좋은 기회도 온 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;새로운 마음가짐&lt;/h4&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 회사로 이동하고 사수 없이 혼자 경력직으로 가게 된다.&amp;nbsp;&lt;br /&gt;신입일 때 빨리 경력직이 되고 싶었는데 경력직이 되고 나니 심적으로 부담감이 크다.&lt;br /&gt;신입이 아니니까 안 봐주겠지?, 돈 값 못하면 바로 짤리게 될까? 얼마나 기대하고 있을까?&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;더불어 추천을 받고 오게 되었기 때문에 추천해주신 사람의 입장도 난처해지지 않게 하고 싶은 마음도 크다. 나중에 '지현님 추천을 잘 받은 것 같아요. 우리 회사에 오게 해주셔서 감사합니다' 이 말 한마디가 듣고 싶다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;여태까지 잘해왔기 때문에 그대로 하면 되겠다는 생각을 가지고 있지만 역시 부담감은 어쩔 수 없나보다.&lt;br /&gt;새로운 곳에서 정말 열심히 해보려고 노력하는 한 해가 되보겠다.&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #555555; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>⏳ 회고</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/400</guid>
      <comments>https://ts2ree.tistory.com/400#entry400comment</comments>
      <pubDate>Mon, 30 Dec 2024 18:34:02 +0900</pubDate>
    </item>
    <item>
      <title>Flutter Unity 통합하기 2</title>
      <link>https://ts2ree.tistory.com/398</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/396&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.11.30 - [  Flutter] - Flutter Unity 통합하기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter Unity 통합 과정에 대한 글을 보고 싶다면 위 글을 참고해주세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 만 대가 넘는 전화기기 지원 중단&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter Unity 통합한 앱을 개발 완성하였고 앱 스토어에 심사 받고 배포할 일만 남았었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 정말 개발 끝인줄 알았는데 또 다시 난관에 봉착했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Google Play Console 에 해당 앱을 제출하게 되면 바로 아래와 같은 경고가 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2374&quot; data-origin-height=&quot;1100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhw3pA/btsLn7MC6mK/dY3LVqYtnpaA61D6ligzbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhw3pA/btsLn7MC6mK/dY3LVqYtnpaA61D6ligzbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhw3pA/btsLn7MC6mK/dY3LVqYtnpaA61D6ligzbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbhw3pA%2FbtsLn7MC6mK%2FdY3LVqYtnpaA61D6ligzbk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2374&quot; height=&quot;1100&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2374&quot; data-origin-height=&quot;1100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 경고는 이 버전 앱을 출시하면 기기의 10,696대가 지원 중단될 것이고 설치수 986건에 영향을 미치게 될 것이니 검토해달란 경고이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 우리 앱이 AR 관련 기능을 추가했지만 AR 기능은 우리 앱의 극히 일부분일 뿐 이것이 우리 앱에 설치 영향을 끼치는 것은 부당(?)하다고 생각했다. 그리고 기존 앱 유저는 우리 앱을 잘 사용하고 있었는데 갑자기 업데이트 후 앱 사용을 못하게 되면 화가 날 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것을 해결할 방법이 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티 개발자의 말씀으로는 Unity&lt;span data-teams=&quot;true&quot;&gt; 에서 AR 을 이용하기 위한 최소 API level은 24로 android 7.0부터 가능하다고 말했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flutter_unity_widget 패키지의 공식 문서를 보아도 minSdkVersion 24 부터 지원된다고 써있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 원래 프로젝트의 minSdkVersion 설정은 이미 API 24 이었다..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;그렇다면 왜 갑자기 만 대가 넘는 전화 기기 중단 경고가 뜬 걸까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. minSdkVersion 24 이어도 AR 기능이 지원되지 않는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 &quot;기기가 지원되지 않는 이유&quot;를 유심히 살펴보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 열심히 검색해봤고 결국 원인을 찾아냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2374&quot; data-origin-height=&quot;1100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XIwR1/btsLmLKLSM4/btLYdHz5e1OqtGbEMuUL11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XIwR1/btsLmLKLSM4/btLYdHz5e1OqtGbEMuUL11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XIwR1/btsLmLKLSM4/btLYdHz5e1OqtGbEMuUL11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXIwR1%2FbtsLmLKLSM4%2FbtLYdHz5e1OqtGbEMuUL11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2374&quot; height=&quot;1100&quot; data-filename=&quot;image.png&quot; data-origin-width=&quot;2374&quot; data-origin-height=&quot;1100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 API 24 이상이어도 AR 기능을 지원할 수 없는 기기가 있다는 것이다. 지금 생각해보니 기기에 따라 지원 안되는 기기가 있는건 당연한건데 그때 당시에는 공식문서의 minSdkVersion 24 설정을 하란대로 설정하면 다 지원되는 줄 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 문서를 살펴보면 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;u&gt;minSdkVersion 을 24 로 설정해두어도 지원 안되는 기기 목록이 표시되어 있다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;u&gt;또한 Depth API 에도 아래와 같은 설명이 써있었다.&lt;/u&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #202124; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Depth API&lt;/b&gt;: 2024년 11월 기준으로 활성 기기의 약 &lt;b&gt;90%&lt;/b&gt; 가 Depth API를 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/ar/devices?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/ar/devices?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734525915293&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;ARCore 지원 기기 &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;ARCore를 지원하는 Android 및 iOS 기기의 목록을 가져오거나 CSV 파일을 다운로드합니다.&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/ar/devices?hl=ko&quot; data-og-url=&quot;https://developers.google.com/ar/devices?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bySJfu/hyXOnWQ1ms/K3gpwLi0SiA0hWgHIMhRp1/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://developers.google.com/ar/devices?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/ar/devices?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bySJfu/hyXOnWQ1ms/K3gpwLi0SiA0hWgHIMhRp1/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;ARCore 지원 기기 &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;ARCore를 지원하는 Android 및 iOS 기기의 목록을 가져오거나 CSV 파일을 다운로드합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 해결방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하지만 AR 기능은 우리 앱에서 필수적인 기능이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이미지에서 &quot;기기가 지원되지 않는 이유&quot;에 나열되어 있는 권한을 찾아보니 android &amp;gt; unityLibrary &amp;gt; src &amp;gt; main &amp;gt; AndroidManifest.xml 파일에 아래와 같이 써있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734550900930&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;meta-data android:name=&quot;com.google.ar.core&quot; android:value=&quot;required&quot; /&amp;gt;
&amp;lt;uses-feature android:name=&quot;android.hardware.camera.ar&quot; android:required=&quot;true&quot; /&amp;gt;
&amp;lt;uses-feature android:name=&quot;com.google.ar.core.depth&quot; android:required=&quot;true&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모두 required 가 true 로 되어 있었다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 &lt;u&gt;AR 기능을 선택적(Optional)으로 하면 되지 않을까&lt;/u&gt; 생각했다. 이에 대해 찾아보았고 아래 설명 문서를 찾을 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/ar/develop/java/enable-arcore?hl=ko#ar-optional&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/ar/develop/java/enable-arcore?hl=ko#ar-optional&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734526567553&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Android 앱에서 AR 사용 설정하기 &amp;nbsp;|&amp;nbsp; ARCore &amp;nbsp;|&amp;nbsp; Google for Developers&quot; data-og-description=&quot;신규 또는 기존 앱에서 AR 기능을 구성하고 사용하는 방법을 알아보세요.&quot; data-og-host=&quot;developers.google.com&quot; data-og-source-url=&quot;https://developers.google.com/ar/develop/java/enable-arcore?hl=ko#ar-optional&quot; data-og-url=&quot;https://developers.google.com/ar/develop/java/enable-arcore?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/oDbma/hyXOpAnxoe/jaZVIurRUwBWJgwL1NKEWk/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675&quot;&gt;&lt;a href=&quot;https://developers.google.com/ar/develop/java/enable-arcore?hl=ko#ar-optional&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developers.google.com/ar/develop/java/enable-arcore?hl=ko#ar-optional&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/oDbma/hyXOpAnxoe/jaZVIurRUwBWJgwL1NKEWk/img.png?width=1200&amp;amp;height=675&amp;amp;face=0_0_1200_675');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android 앱에서 AR 사용 설정하기 &amp;nbsp;|&amp;nbsp; ARCore &amp;nbsp;|&amp;nbsp; Google for Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;신규 또는 기존 앱에서 AR 기능을 구성하고 사용하는 방법을 알아보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developers.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문서를 요약하면 다음과 같은 방법으로 해결할 수 있다고 써있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) AR 선택으로 변경하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;AR 선택적 앱은 ARCore를 지원하지 않는 기기에 설치하고 실행할 수 있습니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) &lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: left;&quot;&gt;앱의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;build.gradle&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일에 최신 ARCore 라이브러리를 종속 항목으로 추가&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734526618590&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    &amp;hellip;
    implementation 'com.google.ar:core:1.33.0'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) 런타임 검사 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;AR 필수 앱과 AR 선택적 앱 모두 ArCoreApk.checkAvailability() 또는 ArCoreApk.checkAvailabilityAsync() 를 사용하여 현재 기기가 ARCore를 지원하는지 확인해야 합니다. ARCore를 지원하지 않는 기기에서는 앱이 AR 관련 기능을 사용 중지하고 연결된 UI 요소를 숨겨야 합니다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 2)에서 추가한 ARCore 종속성으로 앱 런타임 시에 지원하는지 안하는지 체크를 하면 된다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 유니티 빌드 설정 변경&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서 ARCore 를 선택적으로 적용하려면 &lt;u&gt;안드로이드에서만 ARCore 에 대한 Build Setting 을 변경해주어야 한다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 유니티에서는 ARCore 에 대한 Build Setting 이 &lt;b&gt;Required&lt;/b&gt;(필수)로 설정되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 따라해서 설정을 변경해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;안드로이드 빌드 이전, ARCore 관련 Build Setting 에서 모두&amp;nbsp;Optional&amp;nbsp;로 바꿔서 적용해서 빌드하기&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;이때 Depth 관련 설정도 무조건 Optional 로 설정해주어야 한다.&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;&amp;rarr; Depth 필수 설정 또한 기기 설치 수에 영향을 미치기 때문이다. 전화 기기 지원 중단 이유 중에&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&quot;com.google.ar.core.depth&quot;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;가 있었다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;iOS 빌드 이전, ARCore 관련 Build Setting 에서 모두&amp;nbsp;Required&amp;nbsp;로 바꿔서 적용해서 빌드하기&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CUlr4/btsLnFJUSRY/tSDj0siS473ON0Owv5Am80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CUlr4/btsLnFJUSRY/tSDj0siS473ON0Owv5Am80/img.png&quot; data-alt=&quot;사진 따라 1 &amp;amp;gt; 2 &amp;amp;gt; 3 &amp;amp;gt; 4 순&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CUlr4/btsLnFJUSRY/tSDj0siS473ON0Owv5Am80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCUlr4%2FbtsLnFJUSRY%2FtSDj0siS473ON0Owv5Am80%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;800&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;800&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사진 따라 1 &amp;gt; 2 &amp;gt; 3 &amp;gt; 4 순&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 설정하고 빌드 후 안드로이드 관련해서는 따로 아래와 같이 적용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. android &amp;gt; unityLibrary &amp;gt; build.gradle 에서 아래 주석 처리&lt;/p&gt;
&lt;pre id=&quot;code_1734530615516&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// implementation(name: 'arcore_client', ext:'aar')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. android &amp;gt; unityLibrary &amp;gt; src &amp;gt; main &amp;gt; AndroidManifest.xml 에서 다음과 같이 적용 확인&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(이렇게 적용 안되어 있으면 추가해서 적도록 한다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734530648499&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;meta-data android:name=&quot;com.google.ar.core&quot; android:value=&quot;optional&quot; /&amp;gt;
&amp;lt;uses-feature android:name=&quot;android.hardware.camera.ar&quot; android:required=&quot;false&quot; /&amp;gt;
&amp;lt;uses-feature android:name=&quot;com.google.ar.core.depth&quot; android:required=&quot;false&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. ARCore 를 지원하는 기기인지 체크 코드 작성&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번에서 안드로이드의 ARCore  기능을 필수적이 아니라 옵셔널로 바꾸었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 런타임 환경에서 ARCore 를 지원하는 기기인지 체크하고 지원하지 않는 기기이면 &quot;지원하지 않는 기기입니다&quot; 팝업을 띄울 계획이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARCore 를 지원하는 기기인지 검사하려면 안드로이드 네티이브 코드를 추가해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MainAcitivity.kt 파일에서 아래와 같이 작성해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) MethodChannel 연결&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메서드명은 isARCoreSupported 로 지정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 메서드의 반환값이 &lt;b&gt;true&lt;/b&gt; 를 반환하면 &lt;b&gt;지원하는 기기&lt;/b&gt;, &lt;b&gt;false&lt;/b&gt; 를 반환하면 &lt;b&gt;지원하지 않는 기기&lt;/b&gt;, &lt;b&gt;null&lt;/b&gt; 을 반환하면 &lt;b&gt;네트워크 등 알 수 없는 오류&lt;/b&gt;로 처리하였다. &lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(네트워크를 통해서 지원 기기 여부가 체크 되는건가 싶다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734527419383&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;nativeChannel.setMethodCallHandler { call, result -&amp;gt;
            when (call.method) {
                &quot;isARCoreSupported&quot; -&amp;gt; {
                    val isARCoreSupported = isARCoreSupported();
                    result.success(isARCoreSupported)
                }
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) isARCoreSupported 로직 작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.google.com/ar/develop/java/session-config?hl=ko#kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/ar/develop/java/session-config?hl=ko#kotlin&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARCore 지원 검사 코드는 위 코드를 참조하여 작성하였다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 사실 나는 지원하지 않는 안드로이드 기기를 갖고 있지 않아서 테스트를 하지 못했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 하지 못했기 때문에 불안감이 있어서 아래에서 &lt;b&gt;SUPPORTED_APK_TOO_OLD, SUPPORTED_NOT_INSTALLED&lt;/b&gt; 의 경우에는 공식문서대로의 ARCore 를 설치하지 않고 그냥 false 로 처리하였다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(아래 코드에서 주석 처리한 부분이다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734527599190&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private fun isARCoreSupported(): Boolean? {
        return when (ArCoreApk.getInstance().checkAvailability(this)) {
            ArCoreApk.Availability.SUPPORTED_INSTALLED -&amp;gt; true
            ArCoreApk.Availability.SUPPORTED_APK_TOO_OLD, ArCoreApk.Availability.SUPPORTED_NOT_INSTALLED -&amp;gt; {
//                try {
//                    // Request ARCore installation or update if needed.
//                    when (ArCoreApk.getInstance().requestInstall(this, true)) {
//                        ArCoreApk.InstallStatus.INSTALL_REQUESTED -&amp;gt; {
//                            Log.i(&quot;ARCore&quot;, &quot;ARCore installation requested.&quot;)
//                            false
//                        }
//                        ArCoreApk.InstallStatus.INSTALLED -&amp;gt; true
//                    }
//                } catch (e: UnavailableException) {
//                    Log.e(&quot;ARCore&quot;, &quot;ARCore not installed&quot;, e)
//                    false
//                }
                false
            }

            ArCoreApk.Availability.UNSUPPORTED_DEVICE_NOT_CAPABLE -&amp;gt;
                // This device is not supported for AR.
                false

            ArCoreApk.Availability.UNKNOWN_CHECKING -&amp;gt; {
                // ARCore is checking the availability with a remote query.
                // This function should be called again after waiting 200 ms to determine the query result.
                Log.i(&quot;ARCore&quot;, &quot;ARCore availability is being checked. Retrying in 200ms...&quot;)
                Handler(Looper.getMainLooper()).postDelayed({
                    isARCoreSupported() // 함수를 다시 호출하여 상태 확인
                }, 200) // 200ms 대기 후 재시도
            }
            ArCoreApk.Availability.UNKNOWN_ERROR, ArCoreApk.Availability.UNKNOWN_TIMED_OUT -&amp;gt; {
                // There was an error checking for AR availability. This may be due to the device being offline.
                // Handle the error appropriately.
                null
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UNKNOWN_CHECKING&lt;/b&gt; 의 경우, 공식문서에 쓰여 있듯이 200ms 후에 재시도를 해야 한다고 써있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UNKNOWN_ERROR, UNKOWN_TIMED_OUT&lt;/b&gt; 의 경우, 지원 기기 체크 도중 오류가 발생한 것으로, null 로 반환하여 Flutter 에서 &quot;잠시 후 다시 시도해주세요&quot; 라는 팝업을 띄워두도록 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) Depth API 지원 체크 로직 작성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 앱에서는 ARCore 기능을 사용할 뿐만 아니라 Depth API 도 사용하고 있었다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(만약 Depth API 를 사용하지 않는다면 이 과정은 넘어가도 된다)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: left;&quot;&gt;&lt;a href=&quot;https://developers.google.com/ar/develop/java/depth/developer-guide?hl=ko#kotlin&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/ar/develop/java/depth/developer-guide?hl=ko#kotlin&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Depth API 를 지원하는지 체크 코드는 위 문서를 참고하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isARCoreDepthSupported 메서드 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734531239104&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;nativeChannel.setMethodCallHandler { call, result -&amp;gt;
            when (call.method) {
                &quot;isARCoreDepthSupported&quot; -&amp;gt; {
                    val isARCoreDepthSupported = isARCoreDepthSupported();
                    result.success(isARCoreDepthSupported)
                }
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;isARCoreDepthSupported 로직&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734531277379&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private fun isARCoreDepthSupported(): Boolean {
        return try {
            val session = Session(this)
            val isDepthSupported = session.isDepthModeSupported(Config.DepthMode.AUTOMATIC)
            session.close()

            isDepthSupported
        } catch (e: UnavailableArcoreNotInstalledException) {
            false
        } catch (e: UnavailableDeviceNotCompatibleException) {
            false
        } catch (e: Exception) {
            false
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 보면 session 을 close 하는 모습을 볼 수 있는데 session 을 close 하지 않으면 메모리 문제가 발생할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;자세한 내용은 아래 공식 문서를 참고하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Session는 상당한 양의 네이티브 힙 메모리를 소유합니다. 명시적으로 실패한 경우 세션을 닫으면 앱의 네이티브 메모리가 부족해지고 비정상 종료될 수 있습니다. 날짜 AR 세션이 더 이상 필요하지 않다면 출시까지 &lt;a href=&quot;https://developers.google.com/ar/reference/java/com/google/ar/core/Session?hl=ko#close()&quot;&gt;close()&lt;/a&gt;&amp;nbsp;남음 리소스를 배포합니다 앱에 AR 지원 활동이 하나만 포함된 경우&amp;nbsp;close()를 호출합니다. 활동의&amp;nbsp;onDestroy()&amp;nbsp;메서드에서 가져올 수 있습니다.&lt;br /&gt;&lt;a href=&quot;https://developers.google.com/ar/develop/java/session-config?hl=ko#close_a_session&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developers.google.com/ar/develop/java/session-config?hl=ko#close_a_session&lt;/a&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. Flutter 처리 코드&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ARCore 에 대한 지원 여부 코드는 모두 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 앱에서는 이제 아래와 같이 안드로이드 경우에서만 체크하도록 작성해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나 유의해야 할 점은 &lt;span style=&quot;background-color: #ffffff; color: #202124; text-align: start;&quot;&gt;ARCore 지원 여부를 확인하기 전에 카메라 권한이 부여되었는지 확인해야 한다. 이에 대한 코드는 생략하겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1734531565664&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Future&amp;lt;bool&amp;gt; isARSupported() async {
    try {
      if (Platform.isIOS) {
        return true;
      }
		
      // 1. 카메라 권한 먼저 확인
      if (!await DeviceUtils().checkCameraPermission()) {
        await Get.dialog(
          GoblinConfirmPopup(text: '카메라 권한을 허용해주세요.'),
          barrierDismissible: false,
        );
        return false;
      }
		
      // 2. ARCore 지원 여부 체크 
      final isARCoreSupported = await MethodchannelService().isARCoreSupported();
      if (isARCoreSupported == null) {
        await Get.dialog(
          GoblinConfirmPopup(text: '잠시 후 다시 시도해주세요.'),
          barrierDismissible: false,
        );
        return false;
      }

      if (!isARCoreSupported) {
        await Get.dialog(
          GoblinConfirmPopup(text: '지원하지 않는 기기입니다.'),
          barrierDismissible: false,
        );
        return false;
      }
		
      // 3. Depth API 지원 여부 체크
      final isARCoreDepthSupported = await MethodchannelService().isARCoreDepthSupported();
      if (!isARCoreDepthSupported) {
        await Get.dialog(
          GoblinConfirmPopup(text: '지원하지 않는 기기입니다.'),
          barrierDismissible: false,
        );
        return false;
      }

      return true;
    } catch (e) {
      await Get.dialog(
        GoblinConfirmPopup(text: '일시적인 문제가 발생했습니다.'),
        barrierDismissible: false,
      );
      return false;
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드에서 true 로 반환되면 아래와 같이 AR 화면을 진입하게 되고 런타임 크래시는 나지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(테스트 기기가 없어서 확인 안해봤는데 그럴 것이라는 추측..)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_4521 2.PNG&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fa24F/btsLoKJ8aRh/QJTIQWt36gcp0tf2z5Ldpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fa24F/btsLoKJ8aRh/QJTIQWt36gcp0tf2z5Ldpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fa24F/btsLoKJ8aRh/QJTIQWt36gcp0tf2z5Ldpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFa24F%2FbtsLoKJ8aRh%2FQJTIQWt36gcp0tf2z5Ldpk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;216&quot; height=&quot;468&quot; data-filename=&quot;IMG_4521 2.PNG&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;느낀점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter Unity 통합 과정을 해보면서 정말 많은 오류를 맞닿뜨렸는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 이런 오류를 하나씩 해결해나가는 재미가 있는 것 같다. Flutter Unity 연동 사례가 많이 없는데 이번 글도 많은 도움이 되었으면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 앱 개발자들 홧팅!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>arcore 지원</category>
      <category>flutter unity 연동</category>
      <category>전화기기 지원 중단</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/398</guid>
      <comments>https://ts2ree.tistory.com/398#entry398comment</comments>
      <pubDate>Wed, 18 Dec 2024 23:45:37 +0900</pubDate>
    </item>
    <item>
      <title>Flutter Unity 통합하기</title>
      <link>https://ts2ree.tistory.com/396</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc9af; color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Flutter에서 AR 화면 띄우기: Unity 통합 과정&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Flutter 애플리케이션에 AR 화면을 구현하기 위해&amp;nbsp;Unity 통합을 구현하였다.&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이번 글은 Flutter 와 유니티를 통합하며 진행했던 과정을 다시끔 되짚어보고자 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;(&lt;/span&gt;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://pub.dev/packages/flutter_unity_widget&quot;&gt;flutter_unity_widget&lt;/a&gt;&lt;span style=&quot;color: #666666; text-align: start;&quot;&gt;&amp;nbsp;이라는 라이브러리를 사용)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;background-color: #ffc9af; color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Unity 통합을 위한 Android 프로젝트 설정하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity를 통합한 Android 프로젝트를 생성하려면 여러 설정 파일을 수정해야 한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;아래에서 설정 파일을 수정하는 과정을 알아보자.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;# 1. &lt;u&gt;멀티 모듈 설정&lt;/u&gt;을 위한 settings.gradle 구성&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732948536102&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;MyProject/
├── app/
├── unityLibrary/
└── settings.gradle&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;위와 같이 유니티 모듈(unityLibrary)을 포함해서 멀티 모듈 프로젝트로 구성해줘야 하는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;그러기 위해선 settings/gradle 파일을 다음과 같이 수정해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732947171151&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 메인 앱 모듈 포함
include ':app'

// Unity 모듈 포함
include ':unityLibrary'
project(':unityLibrary').projectDir = file('./unityLibrary')

// (선택적) XR 기능이 필요한 경우
include ':unityLibrary:xrmanifest.androidlib'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;settings.gradle 파일에 위처럼 각 프로젝트 디렉토리를 설정해준다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;project(&quot;:unityLibrary&quot;).projectDir = file(&quot;./unityLibrary&quot;)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이 부분은 유니티 모듈의 디렉토리 위치를 정하는 부분이다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;include &quot;:unityLibrary:xrmanifest.androidlib&quot;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이 부분은 Unity 의 XR(확장 현실) 관련 기능을 제공하는 xrmanifest.androidlib 라는 서브모듈을 포함한다. &lt;br /&gt;&lt;/span&gt;즉, xrmanifest.androidlib 는 Unity 에서 제공하는 증강 현실(AR)이나 가상 현실(VR)과 같은 기능을 구현하는 라이브러리이다. (나는 AR 기능이 필요해서 작성하였다.)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;위 설정에 따라 전체 디렉토리 구조는 아래와 같게 된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732950446742&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;project_root/
│
├── app/                          # 메인 앱 모듈
│   └── build.gradle              # 메인 앱의 Gradle 설정 파일
│
├── unityLibrary/                 # Unity에서 내보낸 라이브러리 모듈
│   ├── build.gradle              # Unity 모듈의 Gradle 설정 파일
│   └── xrmanifest.androidlib/    # XR 관련 서브 모듈
│       └── build.gradle          # XR 기능 관련 Gradle 설정 파일
│
└── settings.gradle                # 멀티 모듈 정의 파일&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;# 2. Android 프로젝트에 Unity 통합하기 (app/build.gradle)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity 를 Android 앱에 통합하려면 app/build.gradle 파일을 다음과 같이 수정해야 한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732950249274&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;android {
	ndkVersion &quot;21.3.6528147&quot;
	defaultConfig {
		minSdk = 24
	}
}
dependencies {
    implementation project(':unityLibrary')
    implementation project(':flutter_unity_widget')
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;implementation project(':unityLibrary')&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity에서 내보낸 모듈 (unityLibrary)을 프로젝트에 포함시킨다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이 모듈은 Unity에서 사용하는 모든 네이티브 라이브러리와 Unity 기능을 포함한다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #009a87; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;implementation project(':flutter_unity_widget')&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Flutter Unity Widget은 Flutter와 Unity 간의 통신을 지원하는 라이브러리이다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이 라이브러리를 포함시키면 Flutter 코드에서 Unity를 제어할 수 있는 기능이 제공된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;참고로 Unity에서 내보낸 unityLibrary는 Unity 프로젝트를 빌드할 때 생성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity와 Android 프로젝트가 동기화되도록 Unity 프로젝트를 수정한 후에는 반드시 &lt;b&gt;Unity 프로젝트를 다시 빌드&lt;/b&gt;하여 unityLibrary를 최신 상태로 유지해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;# 3. build.gradle 에서 AAR 파일 가져오기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start; font-family: 'Nanum Gothic';&quot;&gt;Unity 를 빌드하고 나면 자동으로 안드로이드에서 사용할 수 있도록 여러 네이티브 라이브러리를 .aar 형식으로 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start; font-family: 'Nanum Gothic';&quot;&gt;따라서 flutter_unity_widget 문서에서는 이 aar 파일들을 포함시키기 위해 flatDir 방식을 사용해야 한다고 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1733035579701&quot; class=&quot;nginx&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;allprojects {
    repositories {
        flatDir {
            dirs &quot;${project(':unityLibrary').projectDir}/libs&quot; // 'libs' 디렉토리를 로컬 라이브러리 경로로 등록
        }
        google()
        mavenCentral()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;위처럼&amp;nbsp; Gradle이 unityLibrary/libs 폴더 안에 있는 .aar 파일들을 인식하고 빌드 과정에서 포함하도록 작성해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;✋ flatDir 이란?&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;flatDir: 로컬 디렉토리에서 .aar 또는 .jar 파일을 읽어오도록 설정한다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity 에서 내보낸 unityLibrary 의 libs 폴더에는 Unity 엔진과 관련된 .aar 파일들이 포함되어 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;flatDir 을 통해 이 디렉터리를 Gradle 이 종속성 검색 경로로 추가한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;✋ &lt;/span&gt;&lt;/b&gt;flatDir 방식의 주요 특징&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;로컬 라이브러리 참조&lt;br /&gt;&lt;/b&gt;로컬 디렉토리에 저장된 .jar 또는 .aar 파일을 직접 종속성으로 추가한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;저장소 등록&lt;br /&gt;&lt;/b&gt;Gradle에서 repositories 블록에 flatDir을 사용해 로컬 경로를 명시적으로 등록해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;수동 관리 필요&lt;br /&gt;&lt;/b&gt;라이브러리를 직접 관리해야 하며, 의존성 버전 관리가 자동화되지 않는다.&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;유니티 빌드 후 아래와 같이 구성되어 있음을 확인한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-12-01 오후 3.55.12.png&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DsP3M/btsK3gwrv1y/tiAxDYsCue5glelIx19GMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DsP3M/btsK3gwrv1y/tiAxDYsCue5glelIx19GMK/img.png&quot; data-alt=&quot;libs 아래 aar 파일 확인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DsP3M/btsK3gwrv1y/tiAxDYsCue5glelIx19GMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDsP3M%2FbtsK3gwrv1y%2FtiAxDYsCue5glelIx19GMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;497&quot; height=&quot;200&quot; data-filename=&quot;스크린샷 2024-12-01 오후 3.55.12.png&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;libs 아래 aar 파일 확인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;# 4. FlutterActivity &amp;rarr; FlutterUnityAcitivty 로 변경&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;MainActivity.kt 파일에서 FlutterActivity 를 FlutterUnityActivity 로 변경해준다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1733051926107&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;+ import com.xraph.plugin.flutter_unity_widget.FlutterUnityActivity;

+ class MainActivity: FlutterUnityActivity() {
- class MainActivity: FlutterActivity() {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;안드로이드 세팅은 끝났다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이제 최종적으로 플러터를 빌드할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;정리&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 111px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 35px;&quot;&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;android/settings.gradle&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity 모듈(unityLibrary) 포함&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 35px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Gradle이 Unity 모듈을 프로젝트의 일부로 인식하게 만듦.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;android/app/build.gradle&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity 모듈을 앱 모듈의 의존성으로 추가&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;메인 앱에서 Unity의 기능을 사용하기 위해 의존성으로 추가.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 38px;&quot;&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;android/build.gradle&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity의 .aar 파일이 있는 디렉터리 추가&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 38px;&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Unity 엔진 관련 파일들을 Gradle이 인식하고 빌드 과정에 포함할 수 있도록 설정.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Nanum Gothic';&quot;&gt;duplicate class a.a.a found in modules jetified-arcore_client-runtime (:arcore_client:) and jetified-installreferrer-2.2-runtime (com.android.installreferrer:installreferrer:2.2)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;플러터를 최종적으로 빌드한 후 나 같은 경우 installreferrer 라는 라이브러리와 충돌이 일어났다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;찾아보니 두 패키지 모두 난독화를 거쳐서 a.a.a 라는 클래스가 생겼고 installreferrer 와 arcore_client 는 a.a.a 동일한 클래스로 인해 충돌이 일어나는 것 같다. &lt;s&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(추측임)&lt;/span&gt;&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;flutter_unity_widget 깃헙 이슈를 뒤져보았지만 해결 사례가 없었고 아래 문서에 따르면 arcore_client 는 1.26 에 아래 이슈를 해결했다는 기록이 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&amp;darr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;a href=&quot;https://github.com/google-ar/arcore-android-sdk/issues/1140&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/google-ar/arcore-android-sdk/issues/1140&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start; font-family: 'Nanum Gothic';&quot;&gt;android &amp;gt; unityLibrary &amp;gt; build.gradle 을 다음과 같이 수정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start; font-family: 'Nanum Gothic';&quot;&gt;기존 arcore_client 를 주석처리하고 아래 코드로 바꿔 처리했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1733036678488&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// implementation(name: 'arcore_client', ext:'aar')
implementation 'com.google.ar:core:1.26.0'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;빌드되고 아무 문제가 없었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;위 방법을 사용하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333; background-color: #ffc9af; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Unity 통합을 위한 iOS 프로젝트 설정하기&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;먼저 유니티에서 export iOS Release 를 하여 내보내기를 해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;유니티에서 빌드하고 나면 아래와 같이 산출물이 생기는데 그 중 &lt;b&gt;네모 친 두 개&lt;/b&gt;를 사용해 줄 것 이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.29.43.png&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sTfSi/btsK2wsZzyQ/WnNe3koOb9T6PYCK5LSfik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sTfSi/btsK2wsZzyQ/WnNe3koOb9T6PYCK5LSfik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sTfSi/btsK2wsZzyQ/WnNe3koOb9T6PYCK5LSfik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsTfSi%2FbtsK2wsZzyQ%2FWnNe3koOb9T6PYCK5LSfik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;273&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.29.43.png&quot; data-origin-width=&quot;1318&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이제 iOS 에서 Unity 통합을 진행해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;# 1. 하나의 Workspace 에 두 프로젝트 결합하기 &lt;br /&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;(= Unity XcodeProject 를 workspace 로 끌어오기)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;아래처럼 작업창에서 우클릭 후 &lt;b&gt;&quot;Add Files to Runner&quot;&lt;/b&gt; 을 선택한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.09.35.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TNzAx/btsK1VGSHdZ/bTmr4sk1lR3fmbEoNoG161/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TNzAx/btsK1VGSHdZ/bTmr4sk1lR3fmbEoNoG161/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TNzAx/btsK1VGSHdZ/bTmr4sk1lR3fmbEoNoG161/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTNzAx%2FbtsK1VGSHdZ%2FbTmr4sk1lR3fmbEoNoG161%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;249&quot; height=&quot;267&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.09.35.png&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;656&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;아래처럼 iOS -&amp;gt; UnityLibrary -&amp;gt; Unity-iPhone.xcodeproj 를 선택하고 오른쪽 하단 Add 를 클릭한다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.09.50.png&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;518&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuyvus/btsK2kTIrJ4/VskwTW6i4xqyqH10OXSkhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuyvus/btsK2kTIrJ4/VskwTW6i4xqyqH10OXSkhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuyvus/btsK2kTIrJ4/VskwTW6i4xqyqH10OXSkhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcuyvus%2FbtsK2kTIrJ4%2FVskwTW6i4xqyqH10OXSkhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;487&quot; height=&quot;173&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.09.50.png&quot; data-origin-width=&quot;1454&quot; data-origin-height=&quot;518&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.32.15.png&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbVpnK/btsK35npjDS/JHl9kueMoOZUKc8aOwczk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbVpnK/btsK35npjDS/JHl9kueMoOZUKc8aOwczk1/img.png&quot; data-alt=&quot;결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbVpnK/btsK35npjDS/JHl9kueMoOZUKc8aOwczk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbVpnK%2FbtsK35npjDS%2FJHl9kueMoOZUKc8aOwczk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;296&quot; height=&quot;126&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.32.15.png&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;위와 같이 Workspace 에 Unity Project 도 추가되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #333333; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;# 2. UnityFramework 추가하기&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Data 폴더에서 Target Membership 바꿔야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이는 아래와 이유로 문서에서 권장하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;By default Data folder is part of Unity-iPhone target, we change that to make everything encapsulated in one single framework file.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.09.57.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9zNjR/btsK3lqvWCb/zvCRjGbOls9xWXfCgssKW0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9zNjR/btsK3lqvWCb/zvCRjGbOls9xWXfCgssKW0/img.png&quot; data-alt=&quot;먼저 Data 폴더 클릭한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9zNjR/btsK3lqvWCb/zvCRjGbOls9xWXfCgssKW0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9zNjR%2FbtsK3lqvWCb%2FzvCRjGbOls9xWXfCgssKW0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;272&quot; height=&quot;113&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.09.57.png&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;먼저 Data 폴더 클릭한다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: start; font-family: 'Nanum Gothic';&quot;&gt;우측 창에서 Target Membership 에서 Unity-iPhone 에 체크되어 있던걸 해제하고 UnityFramework 에 체크한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 9.04.38.png&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHeA1U/btsK22ESab6/Bdlnu58Rgpaz140foWgkok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHeA1U/btsK22ESab6/Bdlnu58Rgpaz140foWgkok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHeA1U/btsK22ESab6/Bdlnu58Rgpaz140foWgkok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHeA1U%2FbtsK22ESab6%2FBdlnu58Rgpaz140foWgkok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;321&quot; height=&quot;86&quot; data-filename=&quot;스크린샷 2024-11-30 오후 9.04.38.png&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;Runner 를 클릭해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.10.17.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqZETL/btsK2niwKr5/JcMGfx7MwavUAY1TeTi3ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqZETL/btsK2niwKr5/JcMGfx7MwavUAY1TeTi3ZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqZETL/btsK2niwKr5/JcMGfx7MwavUAY1TeTi3ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqZETL%2FbtsK2niwKr5%2FJcMGfx7MwavUAY1TeTi3ZK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;305&quot; height=&quot;147&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.10.17.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left; font-family: 'Nanum Gothic';&quot;&gt;General &amp;rarr; Framworks, Libraries, and Embedded Content 섹션 안 [+] 클릭하고&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.10.22.png&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUvQsu/btsK153MHnE/uzqmLaw36qUKPr8HJOGAk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUvQsu/btsK153MHnE/uzqmLaw36qUKPr8HJOGAk1/img.png&quot; data-alt=&quot;+ 클릭&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUvQsu/btsK153MHnE/uzqmLaw36qUKPr8HJOGAk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUvQsu%2FbtsK153MHnE%2FuzqmLaw36qUKPr8HJOGAk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;837&quot; height=&quot;112&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.10.22.png&quot; data-origin-width=&quot;1314&quot; data-origin-height=&quot;176&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;+ 클릭&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left; font-family: 'Nanum Gothic';&quot;&gt;Workspace &amp;rarr; Unity-iPhone &amp;rarr; UnityFramework.framework 선택 후 우측 하단 [Add] 클릭한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.10.28.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;874&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9NqGf/btsK2acGLhn/m3ULL76AxndaNTpdz9rMw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9NqGf/btsK2acGLhn/m3ULL76AxndaNTpdz9rMw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9NqGf/btsK2acGLhn/m3ULL76AxndaNTpdz9rMw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9NqGf%2FbtsK2acGLhn%2Fm3ULL76AxndaNTpdz9rMw1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;321&quot; height=&quot;371&quot; data-filename=&quot;스크린샷 2024-11-30 오후 5.10.28.png&quot; data-origin-width=&quot;756&quot; data-origin-height=&quot;874&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이렇게 Unity Framework 도 성공적으로 추가하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic';&quot;&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;참고로 UnityFramework 는 동적 프레임워크이므로&lt;br /&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;General &amp;rarr; Framworks, Libraries, and Embedded Content 섹션 안 [+] 클릭을&lt;/span&gt;&lt;/b&gt;&lt;b&gt;,&amp;nbsp;&lt;span style=&quot;text-align: left;&quot;&gt;Link Binary With Libraries에서는 삭제해야 한다&lt;/span&gt;.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;이제 iOS 도 최종 세팅이 끝났다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;플러터를 빌드하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Nanum Gothic';&quot;&gt;value&amp;nbsp;of&amp;nbsp;type&amp;nbsp;unityappcontroller&amp;nbsp;has&amp;nbsp;no&amp;nbsp;member&amp;nbsp;unitymessagehandler&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;iOS 를 빌드하고 나면 위와 같은 오류를 마주친다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;다행히 위 오류는 겪은 사람들이 많아 해결 방법도 쉽게 찾을 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://github.com/juicycleff/flutter-unity-view-widget/issues/486&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/juicycleff/flutter-unity-view-widget/issues/486&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;ios &amp;rarr; UnityLibrary &amp;rarr; Classes -&amp;gt; UnityAppController.mm 이동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;(void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString ... 밑 코드 추가&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1733051600092&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    extern &quot;C&quot; void OnUnityMessage(const char* message)
    {
        if (GetAppController().unityMessageHandler) {
            GetAppController().unityMessageHandler(message);
        }
    }
    
    extern &quot;C&quot; void OnUnitySceneLoaded(const char* name, const int* buildIndex, const bool* isLoaded, const bool* IsValid)
    {
        if (GetAppController().unitySceneLoadedHandler) {
            GetAppController().unitySceneLoadedHandler(name, buildIndex, isLoaded, IsValid);
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;ios &amp;rarr; UnityLibrary &amp;rarr; Classes -&amp;gt; UnityAppController.h 이동&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;@property (nonatomic, copy) void (^quitHandler)(void); 밑 코드 추가&lt;/span&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1733051655026&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;   @property (nonatomic, copy)                    void(^unitySceneLoadedHandler)(const char* name, const int* buildIndex, const bool* isLoaded, const bool* IsValid);
    @property (nonatomic, copy)                    void(^unityMessageHandler)(const char* message);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;빌드는 성공적으로 되었다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;하지만 로딩 화면에서 멈추는 오류를 발견했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;이것도 해결 방법을 쉽게 찾을 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;관련 이슈 :&amp;nbsp;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://discussions.unity.com/t/unity-2021-3-6f1-xcode-14-ios-16-problem-unityframework-crash-before-main/894790&quot;&gt;https://discussions.unity.com/t/unity-2021-3-6f1-xcode-14-ios-16-problem-unityframework-crash-before-main/894790&lt;/a&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Xcode 열기&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Product &amp;gt; Scheme &amp;gt; Edit Scheme&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;창이 뜬 후 왼쪽 Run 클릭 후 Diagnostics 탭 확인&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;Runtime API Checking 에서 Thread Performance Checker 체크 해제&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;&lt;b&gt;Flutter Unity 통합 후 주의할 점&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;유니티 제약 사항&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7; font-family: 'Nanum Gothic';&quot;&gt;&lt;a style=&quot;color: #006dd7;&quot; href=&quot;https://docs.unity3d.com/Manual/UnityasaLibrary.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.unity3d.com/Manual/UnityasaLibrary.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;color: #ee2323; font-family: 'Nanum Gothic';&quot;&gt;You can&amp;rsquo;t load more than one instance of the Unity runtime, or integrate more than one Unity runtime.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;유니티를 연동하고 나서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;유니티 화면을 벗어나고 다른 유니티 화면으로 이동하면 기존 유니티 화면이 재생되는 오류가 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;위의 공식문서와 같이 여러 유니티 인스턴스를 사용할 수 없다는 제약사항 때문인 것 같다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;그래서 여러 유니티 인스턴스를 사용하려면 기존 유니티 화면을 unload 하는 것이 최선의 선택일 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;하지만 막상 테스트해보니 이것마저 시원치 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;안드로이드&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;unload 후 다시 재진입 시, 앱이 강제 종료됨&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;iOS&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;WIP 작업중이라고 써있음 (일부 작동은 함, 하지만 찝찝한 느낌..)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666; font-family: 'Nanum Gothic';&quot;&gt;결국 궁리하다가 플러터에서 restart 라는 메시지를 보내면 유니티에서 진행중이던 로직을 취소하는 것으로 처리해두었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666; text-align: start;&quot;&gt;이번 글은 Flutter와 Unity를 통합하는 이 여정을 통해 얻은 여러 경험과 해결 과정을 공유해보았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Nanum Gothic'; color: #666666; text-align: start;&quot;&gt; 비슷한 문제를 겪는 개발자들에게 도움이 되었길 바라며 이 글을 마친다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>flutter unity</category>
      <category>flutter unity widget</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/396</guid>
      <comments>https://ts2ree.tistory.com/396#entry396comment</comments>
      <pubDate>Sun, 1 Dec 2024 20:25:42 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드 인텐트</title>
      <link>https://ts2ree.tistory.com/395</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인텐트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 앱을 개발하면서 액티비티를 뛰우는 가정에서 인텐트라는 것을 만들고 그 인텐트의 파라미터로 액티비티 클래스를 전달하면 그 액티비티가 실행되는 것을 경험해봤을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730627888809&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val intent = Intent(this, TestActivity::class.java)
startActivity(intent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 인텐트 안에 웹 페이지 주소나 전화번호 등을 URI 객체로 만들어 넣으면, 웹 브라우저나 전화걸기 화면이 띄워지는 것도 모두 인텐트가 그 중심이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730627948480&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val marketIntent = Intent(
    Intent.ACTION_VIEW,
    Uri.parse(&quot;market://details?id=$packageName&quot;)
)
startActivity(marketIntent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 이러한 인텐트 객체를 한 번 알아보는 시간을 가질 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인텐트란?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 컴포넌트에서 다른 컴포넌트를 호출하려면 &lt;b&gt;&quot;다른 컴포넌트를 호출하고 싶다&quot;&lt;/b&gt; 라는 의사 표현을 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의사 표현을 하려면 공통된 규약에 맞춰 이야기를 해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;안드로이드에서 이러한 의사 표현의 수단으로 인텐트 객체를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서 인텐트라 하면 일반적으로 인텐트 객체를 뜻한다. &amp;rarr; Intent()&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐트 객체는 안드로이드 어플리케이션 내의 컴포넌트를 호출하기 위한 여러 정보들을 담고 있으며, 이 정보들에는 호출 대상 컴포넌트의 이름이 명시되어 있을 수도 있고, 혹은 호출 대상 컴포넌트의 특성만 나열되어 있을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 좀 더 자세히 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;명시적 인텐트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 대상 컴포넌트의 이름이 명시되어 있는 인텐트를 명시적 인텐트라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 이름을 &lt;span style=&quot;color: #ee2323;&quot;&gt;명시적으로 정의&lt;/span&gt;해야 하기에 보통 앱 내에 정의되어 있는 액티비티를 인텐트 객체에 담아 실행시킨다. &lt;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(아래 그림 참고)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-09 오전 10.35.50.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ogwNX/btsKCM3Ztjl/YJNlmEMzUZKIyNe6UcGnKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ogwNX/btsKCM3Ztjl/YJNlmEMzUZKIyNe6UcGnKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ogwNX/btsKCM3Ztjl/YJNlmEMzUZKIyNe6UcGnKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FogwNX%2FbtsKCM3Ztjl%2FYJNlmEMzUZKIyNe6UcGnKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;407&quot; height=&quot;285&quot; data-filename=&quot;스크린샷 2024-11-09 오전 10.35.50.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1730631909625&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val intent = Intent(this, Activity2::class.java)
startActivity(intent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 명시적 인텐트로 다른 앱을 실행할 수는 없는 걸까?&lt;br /&gt;-&amp;gt; 놀랍게도 가능하다고 한다. ComponentName을 정확히 명시해서 다른 앱의 컴포넌트를 실행시킬 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1730630892620&quot; class=&quot;kotlin&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;ComponentName componentName = new ComponentName(packageName,className);
    Intent intent = new Intent();
    intent.setComponent(componentName);
    intent.putExtra(&quot;key&quot;,&quot;1234&quot;);
    startActivity(intent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 위해선 다른 앱의 패키지 및 클래스 이름을 정확히 알고 있고 다른 앱의 컴포넌트가 exported=&quot;true&quot; 로 설정되어 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 명시적으로만 알 수 있고 모든 권한이 충족되면 다른 앱도 실행시키는 것이 가능할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;암시적 인텐트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암시적 인텐트는 명시적 인텐트와 달리, 호출할 컴포넌트를 정확히 아는 것이 아니라서 인텐트 객체 내에 호출 대상 컴포넌트를 찾을 수 있는 정보들을 담는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보들을 바탕으로 안드로이드 시스템에서는 적합한 앱을 실행해 주는 식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730632421095&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Create the text message with a string.
val sendIntent = Intent().apply {
    action = Intent.ACTION_SEND
    putExtra(Intent.EXTRA_TEXT, textMessage)
    type = &quot;text/plain&quot;
}

// Try to invoke the intent.
try {
    startActivity(sendIntent)
} catch (e: ActivityNotFoundException) {
    // Define what your app should do if no activity can handle the intent.
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 보면 &lt;b&gt;action, extra, type&lt;/b&gt; 이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이들이 바로 정보들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정보의 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;Action&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;Category&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;Data &amp;amp; Type&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;Extras&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;Flags&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 정보를 가진 호출 대상 컴포넌트를 호출하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인텐트 해석&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 당하는 컴포넌트는 위 정보를 해석하여 어떻게 실행되는걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 바로 &lt;b&gt;&quot;인텐트 해석 과정&quot;&lt;/b&gt;이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 호출 당하는 컴포넌트는 정보를 아래처럼 AndroidManifest.xml 의 intent-filter 에 정의해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731118346896&quot; class=&quot;xml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;activity android:name=&quot;ShareActivity&quot; android:exported=&quot;false&quot;&amp;gt;
    &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.SEND&quot;/&amp;gt;
        &amp;lt;category android:name=&quot;android.intent.category.DEFAULT&quot;/&amp;gt;
        &amp;lt;data android:mimeType=&quot;text/plain&quot;/&amp;gt;
    &amp;lt;/intent-filter&amp;gt;
&amp;lt;/activity&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐트 해석 과정은 다음과 같이 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 인텐트를 발송하면, 안드로이드 시스템은 우선 인텐트가 포함한 Action, Category, Data 등의 속성을 분석한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 다음, 시스템은 이 정보를 기준으로 AndroidManifest.xml 파일에 정의된 intent-filter 를 참조하여, 해당 인텐트를 처리할 수 있는 컴포넌트를 검색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 조건에 맞는 컴포넌트가 발견된다면, 안드로이드 시스템은 이 컴포넌트에 해당 인텐트를 전달하여 적절한 작업을 수행하게 되는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 쉽게 표현하자면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLdLY/btsKD2YPOWN/rPwET3lyXCKHai480NTti0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLdLY/btsKD2YPOWN/rPwET3lyXCKHai480NTti0/img.png&quot; data-alt=&quot;인텐트 해석 과정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLdLY/btsKD2YPOWN/rPwET3lyXCKHai480NTti0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLdLY%2FbtsKD2YPOWN%2FrPwET3lyXCKHai480NTti0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;257&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인텐트 해석 과정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;암시적 인텐트의 정보&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암시적 인텐트와 인텐트 해석 과정에 대해 알아봤으니 이번엔 암시적 인텐트를 이루는 정보들을 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. Action&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 동작을 수행할지 말해주는 문자열이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 호출 대상 컴포넌트가 처리해줬으면 하는 작업이 action 에 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액션에는 안드로이드 시스템에서 사용하는 액션 외에도 사용자가 직접 액션 이름을 지정한 후 그 액션을 사용할 수도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 안드로이드에서 사용하는 액션은 아래와 같은 것들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Intent.ACTION_VIEW&lt;/b&gt;: 특정 데이터를 표시 (예: 웹 페이지 열기)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Intent.ACTION_SEND&lt;/b&gt;: 데이터를 다른 애플리케이션에 전송 (예: 이메일 보내기)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Intent.ACTION_DIAL&lt;/b&gt;: 전화번호를 다이얼러에 표시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 인텐트 객체에 정의된 액션과 인텐트 필터에 정의된 액션이 일치하는지 여부를 검사하는데 이 검사를 통과하려면 액션이 서로 일치해야 한다. 하지만 인텐트 객체에 정의된 액션이 없다면 인텐트 필터에 정의한 어떤 액션이든 관계없이 통과할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-03 오후 8.33.50.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xkY95/btsKuFRfRmW/klZTQwkkYfoA9LZkl7W5X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xkY95/btsKuFRfRmW/klZTQwkkYfoA9LZkl7W5X0/img.png&quot; data-alt=&quot;액션 검사 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xkY95/btsKuFRfRmW/klZTQwkkYfoA9LZkl7W5X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxkY95%2FbtsKuFRfRmW%2FklZTQwkkYfoA9LZkl7W5X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;494&quot; height=&quot;131&quot; data-filename=&quot;스크린샷 2024-11-03 오후 8.33.50.png&quot; data-origin-width=&quot;1360&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;액션 검사 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. Category&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리는 컴포넌트가 어느 범주인지 등 추가 정보를 담는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;웹 브라우저(BROWSABLE), 계산기(APP_CALCULATOR) 등&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LAUNCHER : 런쳐가 실행하는 컴포넌트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 알아두어야 할 점은 startActivity()나 startActivityForResult() 메서드로 암시적 인텐트를 실행할 경우 코드에서 지정하지 않아도 CATEGORY_DEFAULT 가 선언된 것처럼 행동한다. 따라서 호출 당하는 컴포넌트의 인텐트 필터에서는 category 태그로 DEFAULT를 무조건 넣어야 한다. &lt;b&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(넣지 않으면 어떠한 암시적 인텐트도 받을 수 없게 된다)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731120501310&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 액션 검사에서는 인텐트 객체 내에 액션이 정의되어 있지 않는 경우 액션 검사를 통과할 수 있었던 것에 반해, 카테고리 검사는 인텐트 객체에 정의된 카테고리가 인텐트 필터에 정의된 카테고리들과 일치해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 인텐트 필터에서 &lt;span style=&quot;color: #009a87;&quot;&gt;CATEGORY_DEFAULT&lt;/span&gt; 와 &lt;span style=&quot;color: #009a87;&quot;&gt;com.androidhuman.TEST_CATEGORY&lt;/span&gt; 가 정의되어 있을 경우 인텐트 객체는 이 두 개 범위 내에서 한 개 이상은 가지고 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, &lt;span style=&quot;color: #009a87;&quot;&gt;CATEGORY_DEFAULT &lt;span style=&quot;color: #333333;&quot;&gt;와&lt;/span&gt;&lt;/span&gt;&amp;nbsp;&lt;span style=&quot;color: #009a87;&quot;&gt;com.androidhuman.NEW_CATEGORY&lt;/span&gt; 를 가지는 인텐트 객체의 경우, 인텐트 필터에서 &lt;span style=&quot;color: #009a87;&quot;&gt;NEW_CATEGORY&lt;/span&gt; 가 정의되어있지 않으므로 카테고리 검사를 통과할 수 없게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. Data &amp;amp; Type&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Data &amp;amp; Type 는 컴포넌트가 어떤 성격의 데이터를 처리하는지 보여준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐트 객체 내의 Data 항목 및 Type 을 검사하여 인텐트 필터에 정의된 값과 비교하여 일치 여부를 검사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 검사는 크게 &lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;데이터의 주소(URI)를 검사하는 부분&lt;/span&gt;&lt;/b&gt;과 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;데이터의 유형(type, MIME type)을 검사하는 부분&lt;/b&gt;&lt;/span&gt;으로 나누어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;URI(Uniform Resource Identifier)&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;URI 는 어떤 자원을 나타내는 유일한 주소이다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;URL(Uniform Resource Location) 또한 URI의 하위개념이다. 인터넷의 특정 페이지를 나타내는 주소가 URL인것과 마찬가지로, 안드로이드 시스템 내의 자원의 주소를 표현하는 하나의 방식이다.&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3-1. 데이터의 주소(URI) 검사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터의 주소를 세분화하여 검사할 수 있도록 되어 있다. URI 는 다음과 같은 구조로 되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;scheme://host:port/path&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;스킴 (scheme)&lt;/b&gt;: 리소스의 종류를 지정한다. 예를 들어 http, https, tel, geo, content 등이 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호스트 (host)&lt;/b&gt;: URI의 주체가 되는 주소. 예를 들어, &lt;a href=&quot;http://www.example.com&quot;&gt;http://www.example.com &lt;/a&gt;에서 www.example.com이 호스트이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경로 (path)&lt;/b&gt;: 더 구체적인 리소스를 가리키기 위한 경로 정보이다. 예를 들어, 파일의 경로를 지정하거나, 웹 URL에서 페이지의 위치를 나타낼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-11-09 오후 2.14.53.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zB5ov/btsKDUUfRKH/w439kjigKd4Ap4KVO8ci41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zB5ov/btsKDUUfRKH/w439kjigKd4Ap4KVO8ci41/img.png&quot; data-alt=&quot;데이터 주소 유형&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zB5ov/btsKDUUfRKH/w439kjigKd4Ap4KVO8ci41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzB5ov%2FbtsKDUUfRKH%2Fw439kjigKd4Ap4KVO8ci41%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;462&quot; height=&quot;160&quot; data-filename=&quot;스크린샷 2024-11-09 오후 2.14.53.png&quot; data-origin-width=&quot;900&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;데이터 주소 유형&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 아래 URI 는 쇼핑 앱의 해당 제품 페이지가 열리도록 설정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731131484313&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val intent = Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse(&quot;shoppingapp://product:443/details/67890&quot;)
}
startActivity(intent)


// 호출 당하는 컴포넌트의 인텐트 필터
&amp;lt;data android:scheme=&quot;shoppingapp&quot; 
      android:host=&quot;product&quot; 
      android:port=&quot;443&quot;
      android:pathPrefix=&quot;/details&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;다른 예시 - 사용자가 특정 도메인의 링크를 클릭할 때 앱이 자동으로 열리는 앱링크 같은 경우, 아래와 같이 인텐트 필터로 설정되어 앱이 특정 URL 을 수신할 수 있도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731131729820&quot; class=&quot;routeros&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;data android:scheme=&quot;http&quot; android:host=&quot;predict-dev.wwwww.io&quot; /&amp;gt;
&amp;lt;data android:scheme=&quot;https&quot; android:host=&quot;predict-dev.wwwww.io&quot;/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3-2. 타입 검사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Type 검사는 데이터의 종류를 지정해 인텐트 해석 과정에서 정확하게 대상 컴포넌트를 찾을 수 있도록 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제 MIME 타입을 설정할까?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 보통 웹페이지, 파일 이미지, 이메일 등과 같이 리소스의 구체적인 유형이 필요할 때 MIME 타입을 설정한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 특정 파일을 열거나 이미지를 보여줄 때 MIME 타입을 통해 시스템에 리소스 형식을 전달해 알맞은 앱이 실행되도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731130725318&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val intent = Intent(Intent.ACTION_VIEW).apply {
    data = Uri.parse(&quot;file://path/to/image.jpg&quot;)
    type = &quot;image/jpeg&quot;
}
startActivity(intent)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예는 URI 는 파일 경로를, 타입은 image/jpeg 로 지정해 안드로이드가 이미지 파일임을 인식하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;lt;data android:mimeType = &quot;video/mpeg&quot; android:scheme = &quot;http&quot;&amp;gt; &lt;br /&gt;&amp;lt;data android:mimeType = &quot;audio/*&quot; android:scheme = &quot;http&quot;&amp;gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인텐트 필터에서의 mineType 은 위와 같은 형식으로 정의된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식으로 첫 번째 data 필터를 해석하면 &lt;span style=&quot;color: #009a87;&quot;&gt;&quot;http 스키마를 가진 mpeg 형식의 비디오 데이터&quot; &lt;/span&gt;를 가진 인텐트를 허용하는 것임을 알 수 있고, 두 번째 필터는 &lt;span style=&quot;color: #009a87;&quot;&gt;&quot;http 스키마를 가진 모든 오디오 데이터&quot; &lt;/span&gt;를 가진 인텐트를 허용하는 것임을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. Extras&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;extra 는 인텐트 객체에 실제로 데이터를 첨부하여 보내는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 데이터들은 키-값 쌍을 이루어 인텐트 객체에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드는 안드로이드에서 &lt;b&gt;인텐트(Intent)를 통해 데이터를 전달하고, 이를 다른 컴포넌트에서 받아오는 방식&lt;/b&gt;을 보여준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730631337138&quot; class=&quot;reasonml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Intent i = new Intent();
i.putExtra(&quot;TEST&quot;, &quot;Test string&quot;); // String을 넣습니다.

// 인텐트를 받은 다른 컴포넌트가 Extras 데이터를 받아옵니다.
String str = getIntent().getExtras().getString(&quot;TEST&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/guide/components/intents-common?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.android.com/guide/components/intents-common?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예시에서 각종 인텐트의 예제가 잘 나타나 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 몇 가지 예를 직접 테스트해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;알람 설정 테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1730624849279&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.AlarmClock

class TestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)

        val intent = Intent(AlarmClock.ACTION_SET_ALARM).apply {
            putExtra(AlarmClock.EXTRA_MESSAGE, &quot;test message&quot;)
            putExtra(AlarmClock.EXTRA_HOUR, 0)
            putExtra(AlarmClock.EXTRA_MINUTES, 1)
        }


        if (intent.resolveActivity(packageManager) != null) {
            startActivity(intent)
        }


    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 암시적 인텐트를 작성하면 &quot;test message&quot; 라는 텍스트인 알람이 설정된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 위처럼 인텐트를 호출하기 위해서 user-permission 권한을 설정해주어야 오류가 나지 않는다. (권한을 설정해주지 않으면 앱이 runtime exception 오류를 내며 종료된다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730634695428&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;uses-permission android:name=&quot;com.android.alarm.permission.SET_ALARM&quot; /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 알람 앱은 아마 intent-filter 가 아래처럼 설정되어 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리 DEFAULT 는 아까 설명했던 것처럼 암시적 인텐트를 실행할 경우 코드에서 지정하지 않아도 CATEGORY_DEFAULT 가 선언된 것처럼 행동하기에 호출 당하는 컴포넌트에는 반드시 선언해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730624960524&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;activity ...&amp;gt;
    &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.SET_ALARM&quot; /&amp;gt;
        &amp;lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&amp;gt;
    &amp;lt;/intent-filter&amp;gt;
&amp;lt;/activity&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;미디어 (4).jpeg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS2Qla/btsKtL52Kla/oENIeBn0S7wfU9hbxKK4iK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS2Qla/btsKtL52Kla/oENIeBn0S7wfU9hbxKK4iK/img.jpg&quot; data-alt=&quot;테스트: 알람이 자동으로 설정된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS2Qla/btsKtL52Kla/oENIeBn0S7wfU9hbxKK4iK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS2Qla%2FbtsKtL52Kla%2FoENIeBn0S7wfU9hbxKK4iK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;203&quot; height=&quot;520&quot; data-filename=&quot;미디어 (4).jpeg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트: 알람이 자동으로 설정된 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;연락처 추가 테스트&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1730625625444&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.AlarmClock
import android.provider.ContactsContract
import android.provider.MediaStore

class TestActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test)

        val intent = Intent(Intent.ACTION_INSERT).apply {
            type = ContactsContract.Contacts.CONTENT_TYPE
            putExtra(ContactsContract.Intents.Insert.NAME, &quot;JH&quot;)
            putExtra(ContactsContract.Intents.Insert.EMAIL, &quot;ix219@naver.com&quot;)
        }
        if (intent.resolveActivity(packageManager) != null) {
            startActivity(intent)
        }



    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 Intent.ACTION_INSERT를 사용해 연락처 앱의 새 연락처 삽입 화면을 열고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값으로 이름과 이메일을 입력하도록 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 호출 대상 컴포넌트는 아마 아래처럼 intent-filter 가 설정되어 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1730625702058&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    &amp;lt;intent-filter&amp;gt;
        &amp;lt;action android:name=&quot;android.intent.action.INSERT&quot; /&amp;gt;
        &amp;lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&amp;gt;
        &amp;lt;data android:mimeType=&quot;vnd.android.cursor.dir/contact&quot; /&amp;gt;
    &amp;lt;/intent-filter&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;미디어 (5).jpeg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AAU7y/btsKtSjAeio/860hXjHcJtrm3p9A1HKIv0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AAU7y/btsKtSjAeio/860hXjHcJtrm3p9A1HKIv0/img.jpg&quot; data-alt=&quot;테스트: 연락처 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AAU7y/btsKtSjAeio/860hXjHcJtrm3p9A1HKIv0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAAU7y%2FbtsKtSjAeio%2F860hXjHcJtrm3p9A1HKIv0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;219&quot; height=&quot;561&quot; data-filename=&quot;미디어 (5).jpeg&quot; data-origin-width=&quot;904&quot; data-origin-height=&quot;2316&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;테스트: 연락처 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 우리는 안드로이드에서 인텐트가 무엇인지, 인텐트 객체의 구조, 그리고 인텐트 필터와 그 통과 조건에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드에서 &lt;b&gt;인텐트&lt;/b&gt;는 앱 컴포넌트 간의 상호작용을 위한 중요한 수단이라는 것을 알았으며,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트가 처리할 수 있는 Action, Category, Data 등의 조건을 설정하여 어떤 컴포넌트에 전달될지 결정할 수 있게 하여 앱은 다양한 상황과 사용자 요청을 유연하게 대응할 수 있게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 위와 같이 공부해보면서 안드로이드가 이렇게 앱과의 상호작용을 처리한다는 것이 흥미로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅도 안드로이드 글로 찾아뵙겠다. :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>안드로이드 암시적 인텐트</category>
      <category>안드로이드 인텐트</category>
      <category>안드로이드 인텐트 필터</category>
      <category>암시적 인텐트</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/395</guid>
      <comments>https://ts2ree.tistory.com/395#entry395comment</comments>
      <pubDate>Sat, 9 Nov 2024 15:14:31 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] iOS 웹뷰 흰 화면 뜨는 현상 1탄</title>
      <link>https://ts2ree.tistory.com/393</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전 웹뷰를 연동하는 업무를 했다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(근데 일반적이지 않는 웹뷰였다.)&lt;/span&gt;&lt;br&gt;앱에 3D 이미지를 붙이기 위해서 유니티 WebGL 이라는 기술을 사용해서 웹뷰를 띄워보기로 했다.&lt;br&gt;유니티와 플러터..? 정말 듣기만 해도 너무 멋진 기술 통합 같았는데.. 결과는 좋지 않았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;왜 그러한 사례가 없었는지를 생각해봤었어야 했다.&lt;br&gt;나는 웹뷰를 연동하면서 심각한 오류와 문제를 맞이하게 되었고 이를 기반으로 앱에 3D 이미지를 띄운다는 것은 정말 쉽지 않은 일이구나를 깨달았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;아마 유니티 WebGL 이어서 더 쉽지 않은 일인 것일 수도 있다.. 왜냐하면 WebGL의 JavaScript 환경은 싱글 스레드이기에..&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;유니티의 경우, 게임 엔진이 멀티스레딩을 지원하여 물리 계산이나 그래픽 렌더링 등을 별도의 스레드에서 처리할 수 있지만, WebGL 플랫폼에서는 이러한 병렬 처리가 불가능하다. 결국, 멀티스레딩을 사용할 수 없어 복잡한 계산이나 렌더링 작업이 단일 스레드에서 처리되어야 하기에 더 메모리 제한을 받을 수 있을 것 같다고 생각한다.&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;(아래 공식 문서 참고)&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2072&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm8CeQ/btsKdmqj7ui/YKmUKjGWtF9xYjqvdp5Kd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm8CeQ/btsKdmqj7ui/YKmUKjGWtF9xYjqvdp5Kd0/img.png&quot; data-alt=&quot;https://docs.unity3d.com/kr/2019.4/Manual/webgl-gettingstarted.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm8CeQ/btsKdmqj7ui/YKmUKjGWtF9xYjqvdp5Kd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm8CeQ%2FbtsKdmqj7ui%2FYKmUKjGWtF9xYjqvdp5Kd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2072&quot; height=&quot;390&quot; data-origin-width=&quot;2072&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://docs.unity3d.com/kr/2019.4/Manual/webgl-gettingstarted.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 웹뷰 연동&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;나는 메인 웹뷰 컨트롤러를 글로벌 상태관리로 관리해주었다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(Getx, Provider, Riverpod 와 같은 상태관리)&lt;/span&gt;&lt;br&gt;왜냐하면 앱 메인 페이지에 계속 떠 있어야 했고 웹뷰 로드 속도가 느리면 ui/ux 측면에서 굉장히 느려보이기 때문에 웹뷰 URL 로드 부분은 init() 메서드를 따로 빼서 미리 로딩 페이지에서 로드를 해주었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 글로벌 상태 관리 이용했지만 아래 코드에선 생략
// 아래 init() 메서드를 로딩 페이지에서 미리 로드

WebViewController? unityWebViewController;

Future&amp;lt;void&amp;gt; init() async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;unityWebViewController = WebViewController()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;..setJavaScriptMode(JavaScriptMode.unrestricted)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;..setBackgroundColor(Colors.transparent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;..setNavigationDelegate(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NavigationDelegate(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onProgress: (int progress) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('WebView is loading (progress : $progress%)');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPageStarted: (String url) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('Page started loading: $url');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPageFinished: (String url) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('Page finished loading: $url');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onNavigationRequest: (NavigationRequest request) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (request.url.startsWith('https://www.youtube.com/')) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('blocking navigation to ${request.url}');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return NavigationDecision.prevent;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('allowing navigation to ${request.url}');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return NavigationDecision.navigate;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onUrlChange: (UrlChange change) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('url change to ${change.url}');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; ),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)..loadRequest(Uri.parse(Constants.unityWebViewUrl));&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. iOS 에서 웹뷰 띄울 시 흰 화면 현상 발생&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rfmhd/btsKbxmTuDx/ucgvpjoY4bDLvQLpfgaEq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rfmhd/btsKbxmTuDx/ucgvpjoY4bDLvQLpfgaEq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rfmhd/btsKbxmTuDx/ucgvpjoY4bDLvQLpfgaEq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frfmhd%2FbtsKbxmTuDx%2FucgvpjoY4bDLvQLpfgaEq1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;269&quot; height=&quot;583&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;메인 웹뷰를 띄우고 시간이 지난 후 위와 같이 아이폰에서 계속 흰 화면이 종종 발견되었다.&lt;br&gt;처음에 핸드폰 메모리를 의심하였다.&amp;nbsp;&lt;br&gt;하지만 Unity WebGL 에 대해 공부해봤을 때 핸드폰 메모리가 아니라 &lt;u&gt;웹뷰 메모리에 문제가 있다&lt;/u&gt;는 사실을 알게 되었다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;&lt;b&gt;Unity 자체 개발:&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;PC, 콘솔 또는 모바일 플랫폼에서 유니티 게임을 개발할 때는 해당 플랫폼의 메모리 한도 내에서 작동한다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Unity WebGL:&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;webGL 애플리케이션은 브라우저 내에서 실행되므로 브라우저가 관리하는 메모리 한계 내에서 작동해야 한다. 일반적으로 메모리 한계는 몇백 MB 에서 2GB 정도로 제한된다. (브라우저와 운영체제에 따라 다를 수 있다는 점을 명심해두자)&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 웹뷰 메모리에 대한 이해&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;웹뷰 메모리에 대해 조사하다가 직접적인 관련은 없지만 아래 흥미로운 사례를 발견하게 되었다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;Ex. 컴퓨터의 메모리가 부족한 것이 아니다?&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #666666;&quot;&gt;어떤 블로그 인용&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #666666;&quot;&gt; “크롬을 사용하던 도중 저 놈의 out of memory 오류 코드가 뜨면서 브라우저가 다운되는 현상 때문에 딥빡이 친 적이 한 두번이 아니었다.&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;이해가 안가는 건 PC 성능을 실시간으로 띄워도 딱히 CPU 나 메모리가 부족하지 않고 심지어 넉넉한 상황에서도 크롬은 자꾸만 아웃오브 메모리를 외쳐대었다.”&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc67QB/btsKb4qR8UB/lq9TX8GOcauYxK1X5iUjNK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc67QB/btsKb4qR8UB/lq9TX8GOcauYxK1X5iUjNK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc67QB/btsKb4qR8UB/lq9TX8GOcauYxK1X5iUjNK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc67QB%2FbtsKb4qR8UB%2Flq9TX8GOcauYxK1X5iUjNK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;321&quot; data-origin-width=&quot;1110&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;해당 메시지는 크롬의 메모리가 부족하다는 것을 의미하는데, 이 오류 코드는 컴퓨터의 메모리(RAM)가 충분하더라도 발생한다.&lt;br&gt;&lt;br&gt;이는 사용자의 PC 문제보다는 주로 크롬이 원인이다. 이어지는 단계를 하나씩 적용해주는 것으로 크롬 Out of Memory 문제를 해결할 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;1. 크롬 업데이트&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;br&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;2. 작업 관리자 활용: 크롬은 장시간 사용할 수록 메모리 점유율이 상승하게 된다. 그래서 주기적으로 작업 끝내기를 적용하는 것이 좋다.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;br&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;3. 캐시 데이터 삭제&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;&lt;br&gt;&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;4. 확장 프로그램 비활성화&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;color: #555555;&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #555555;&quot;&gt;↓&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #555555;&quot;&gt;아마 위와 같은 사례와 마찬가지로 모바일에서의 WebGL 도 웹 브라우저 메모리 한도 내에서 작동하는 것과 마찬가지이지 않을까 싶다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;유니티 webGL 과 Three.js 로 만든 webGL 애플리케이션 모두 동일한 웹 브라우저의 메모리 한계 내에서 작동해야 한다.&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;4. &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;webViewWebContentProcessDidTerminate&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;iOS의 WKWebView는 웹뷰가 너무&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;많은 리소스를 사용할 경우 크래시가 일어나서 웹뷰 프로세스를 종료시켜버린다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이 때 웹뷰 프로세스가 종료되면서 빈 하얀색 화면만이 뜨게 되는 것이다.&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;iOS 에서 위 해결 방법에 대해 찾아보니 &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;webViewWebContentProcessDidTerminate&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 라는 이벤트를 받아서 처리할 수 있다고 한다. &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;패키지를 수정해야 하나 싶었지만 다행히도 &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;webview_flutter&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 패키지에서&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;webViewWebContentProcessDidTerminate&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 이벤트를 받아 처리할 수 있었다.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;unityWebViewController = WebViewController()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;..setJavaScriptMode(JavaScriptMode.unrestricted)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;..setBackgroundColor(Colors.transparent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;..setNavigationDelegate(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NavigationDelegate(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onProgress: (int progress) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('WebView is loading (progress : $progress%)');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPageStarted: (String url) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('Page started loading: $url');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onPageFinished: (String url) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('Page finished loading: $url');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onWebResourceError:(error) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (error.errorType == WebResourceErrorType.webContentProcessTerminated) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('unity webview error: WebResourceErrorType.webContentProcessTerminated');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onUrlChange: (UrlChange change) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;debugPrint('url change to ${change.url}');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;참고로 &lt;b&gt;webViewWebContentProcessDidTerminate &lt;/b&gt;현상이 일어나면 process: 0%, url change to null 출력도 같이 찍혔다.&lt;br&gt;url 이 null 이 된다는 것은 url 이 about:blank 상태라는 것이다. &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;(그래서 빈 흰 화면이 나타나게 되는 것)&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;이 현상은 실제로 테스트할 수 있는 방법은 웹뷰를 띄워놓고 앱을 hide 한 다음에 다른 앱들 10개 정도 키면 위 현상을 재현시킬 수 있다.&lt;br&gt;근데 시뮬레이터를 이용하면 더 간단히 위 현상을 재현시킬 수 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;↓&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;1. 아이폰 시뮬레이터를 킨다&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;2. 맥북에서 &quot;활성 상태 보기&quot;를 킨다&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;2. &quot;활성 상태 보기&quot; 창에서 &quot;com.apple.WebKit.WebContent&quot; 를 눌러서 종료시킨다&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;b&gt;3. webViewWebContentProcessDidTerminate 발생&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baMrKN/btsKdRi8Avq/iKubLVkSicefPDetguOnr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baMrKN/btsKdRi8Avq/iKubLVkSicefPDetguOnr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baMrKN/btsKdRi8Avq/iKubLVkSicefPDetguOnr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaMrKN%2FbtsKdRi8Avq%2FiKubLVkSicefPDetguOnr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;257&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 웹뷰 안 뜨는 현상 해결?&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;webViewWebContentProcessDidTerminate 이벤트 발생 시 아래와 같이 웹뷰 컨트롤러를 다시 reload 하면 다시 웹뷰가 정상적으로 보일 것이다.&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;onWebResourceError:(error) {
&amp;nbsp;&amp;nbsp;if (error.errorType == WebResourceErrorType.webContentProcessTerminated) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; debugPrint('unity webview error: WebResourceErrorType.webContentProcessTerminated');
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; unityWebViewController?.reload();
&amp;nbsp;&amp;nbsp;} 
 },&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이때까지만 해도 White Screen 현상을 완벽히 해결한 줄 알았다.&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;전보다 흰 화면 현상이 확실히 줄었긴 했지만 앱을 정말 오랫동안 백그라운드에 둔 뒤 다시 키면 역시 흰 화면을 재현시킬 수 있었다...&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아무래도 백그라운드에서는 webViewWebContentProcessDidTerminate 이벤트가 제대로 작동하지 않는 것 같아, 다음과 같이 처리했다.&lt;/span&gt;&lt;br&gt;webViewWebContentProcessDidTerminate 이벤트가 발생하면 URL이 null(즉, about:blank 상태)로 설정되기 때문에, 앱이 다시 resume 상태가 되면 아래와 같이 reload하도록 구현했다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;@override
&amp;nbsp;&amp;nbsp;void didChangeAppLifecycleState(AppLifecycleState state) async {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;switch (state) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case AppLifecycleState.resumed: 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	if (await unityWebViewController?.currentUrl() == null) {
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	&amp;nbsp;&amp;nbsp;unityWebViewController?.reload(); // 동작 안됨
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;	}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;....&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;하지만 위 조건식이 제대로 동작하지 않았다.. (어느 부분이 문제인건지 잘 모르겠다)&lt;/b&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;어떻게 웹뷰 메모리 문제를 해결할 수 있을까 곰곰히 생각해보았는데 이때&amp;nbsp;&lt;b&gt;&quot;앱을 사용하지 않을 때에도 웹뷰를 백그라운드에서 띄워줄 필요가 있을까?&quot;&lt;/b&gt; 라는 생각이 들었다.&lt;br&gt;문제는 앱을 오랫동안 백그라운드에 놔두다가 다시 앱을 Open 시킬 때 발생하는 것이었다.&lt;br&gt;앱을 사용 중일 때는 &lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;webViewWebContentProcessDidTerminate 이벤트가 잘 받아지고 웹뷰도 잘 reload 되어진다!&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;그래서 &lt;u&gt;백그라운드에서의 웹뷰 처리&lt;/u&gt;는 아래처럼 방법을 바꾸어 적용해 보았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;앱이 paused 되버리는 시점에 기존 웹뷰 컨트롤러를 null 로 할당해버린다.&lt;/b&gt;&lt;br&gt;그러면 이 웹뷰 컨트롤러는 이제 더 이상 웹뷰 이벤트를 갖지 않는 null 상태이다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;case AppLifecycleState.paused:
	unityWebViewController = null;&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;그리고 앱이 resumed 가 되면 다시 WebViewController 를 할당한다.&lt;/b&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;case AppLifecycleState.resumed:
	if (unityWebViewController == null) unityWebViewController.init();&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위와 같이 함으로써 백그라운드에서 앱을 오랫동안 두고 다시 켜도 웹뷰가 정상적으로 보이기 시작한다!&lt;br&gt;근데 ui/ux 측면에서 앱을 hide 시킨 후 킬 때마다 메인화면 웹뷰가 자꾸 로딩되는 불편한 측면은 있다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6. 결론&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;사실 유니티 WebGL 을 Flutter 웹뷰에 띄워보자는 생각 자체가 잘못된 접근이었다고 생각한다.&lt;br&gt;성능이 천차만별인 모바일 기기 웹뷰에 메모리 사용량이 큰 유니티 WebGL 기술을 사용하는 것은 맞지 않았다.&lt;br&gt;&amp;nbsp;&lt;br&gt;iOS 에서 시간이 지나면 물체가 검정색으로 변해버리는 문제는 여전히 해결하지 못했다. (아마 메모리 문제로 인해 렌더링이 지속되다가 결국 마지막 프레임을 그리지 못한 것 아닐까?)&lt;br&gt;&amp;nbsp;&lt;br&gt;앞으로 기술을 개발할 때 여러 기술을 혼합해 사용할 경우, 충분히 검토한 후 사용하는 것이 중요하다는 걸 깨달았다. 각 기술은 나름의 존재 이유가 있는 법이니까!&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>flutter ios webview white screen</category>
      <category>flutter unity webgl</category>
      <category>flutter webview white</category>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/393</guid>
      <comments>https://ts2ree.tistory.com/393#entry393comment</comments>
      <pubDate>Mon, 21 Oct 2024 23:58:12 +0900</pubDate>
    </item>
    <item>
      <title>글또 10기 다짐글</title>
      <link>https://ts2ree.tistory.com/391</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;글또 8기부터 시작하여 어느덧 10기까지 왔다.  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신기한 점은 8기를 처음 시작했을 때와 다른 마음가짐인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;글또 8기 (영유아기)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8기는 그저 다른 개발자들과 커피챗?! 신난다!! 하며 참여했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 마음가짐으로 참여해서 그런지 글을 대충 쓴 것도 있었고 지금 보니 글 퀄리티가 형편없다고 느낀다. &lt;b&gt;(첫 글짓기 걸음마 대실패)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 어릴 때 작성한 일기장을 훔쳐 보는 느낌이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;글또 9기 (청년기)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9기를 시작하고 나서는 주로 회사 이슈 업무에 대해서 포스팅을 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사 이슈 업무에 대해 포스팅한 이유는.. 사실 나는 회사 업무가 재밌고 &lt;span style=&quot;color: #9d9d9d;&quot;&gt;(매번 다른 프로젝트를 하니 사이드 프로젝트라고 생각하고 있다)&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연차가 쌓여서 그런지 회사 일이 점점 많아지면서 일과 삶은 혼연일체가 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 회사 이슈 업무 포스팅을 했던 9기에서 나는 몇 가지를 깨달을 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;글을 정리하지 않았다면 내가 좀 더 깊게 파고들 수 있었을까? 글을 써보니 개념을 순차적으로 정리하게 되고 이는 혼란스러운 내 머릿속을 같이 정리해주었다.&quot;&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무를 하면서 내 머릿속은 혼란스러운 교통 체계 그 자체였는데 글을 쓰면서 질서 정연한 교통 체계를 만든 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 나는 글쓰기에 대한 소중함을 느끼게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 9기 활동 당시 작성한 글들인데 한 두개 정도는 시간에 쫓겨 UI 관련 글을 쓰며 대충 작성한 것도 있었지만 그래도 대부분 열심히 작성은 했던 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/371&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.12.09 - [  Flutter] - Debounce, Throttle&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/373&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2023.12.29 - [⏳ 회고] - 광고 모듈화했던 경험&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/374&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.01.06 - [  Flutter] - Flutter NavigationController 에서 iOS 로 이동(push)해보기&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/375&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.02.04 - [⏳ 회고] - 2023 회고록&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/382&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.03.17 - [  분류 전체보기] - 나에게 아직은 어려운 앱 설치 광고&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/380&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.03.08 - [  Flutter] - [Flutter] Sliver TabBarView hide 이슈&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ts2ree.tistory.com/385&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2024.04.27 - [ &amp;zwj;  TIL] - 만보기와 안드로이드 배터리 최적화 관련 분석&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;글또 10기 (성년기?)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10기도 변함 없을 것 같지만 좀 더 &lt;b&gt;계획적&lt;/b&gt;으로 접근할 생각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9기 활동 당시에는 업무하면서 작성하기 좋을 것 같은 주제를 즉흥적으로 정하여 글의 주제를 정하고 마감 시간에 쫓겼던 적도 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 10기는 나만의 Action Item 을 정해 좀 더 계획적으로 글을 쓰고 유의미한 포스팅을 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;글또 10기 Action Item&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글또 10기에서 생각해둔 나만의 Action Item 은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;제출하기 2주 전부터 평일 저녁 퇴근 후 글감 주제에 대해 학습을 시작한다&lt;/li&gt;
&lt;li&gt;주말에는 자료 정리, 목차 구성, &lt;u&gt;글 초안을 조금이라도 작성한다&lt;/u&gt; &lt;span style=&quot;color: #ee2323;&quot;&gt;(중요) - 이때라도 하지 않으면 밀리게 된다 (경험)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;제출 주차의 주말에는 글 수정, 작성 및 마무리를 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 글또 10기 활동하시는 분이 아래와 같은 템플릿을 나눠주셨는데 아래 템플릿을 활용하면 미리 글감 주제에 대해 생각해보고 충분히 쓸 시간이 있을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 글감 주제를 다 생각해놓기는 어렵지만 2개씩 정도는 미리 생각해보고 자료 정리하려고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;예정 글감 주제 2개씩 생각하고 노션에 자료 정리하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-10-01 오전 12.03.11.png&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;876&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pgHHF/btsJSMqtRBb/uiyVhs6kgpDxcMmeye98Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pgHHF/btsJSMqtRBb/uiyVhs6kgpDxcMmeye98Zk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pgHHF/btsJSMqtRBb/uiyVhs6kgpDxcMmeye98Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpgHHF%2FbtsJSMqtRBb%2FuiyVhs6kgpDxcMmeye98Zk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;480&quot; data-filename=&quot;스크린샷 2024-10-01 오전 12.03.11.png&quot; data-origin-width=&quot;1158&quot; data-origin-height=&quot;876&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;글또 10기 주 관심사&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 나의 주 관심사는 회사 이슈 &amp;amp; 안드로이드 쪽인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글또 9기에는 iOS 를 주로 다루었는데 내가 너무 편향적으로 주제를 다룬 것은 아닐까 하는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 회사 앱 유저 중 안드로이드 유저가 대부분이고 부모님도 안드로이드 OS 를 쓰고 계시니 이번엔 안드로이드를 본격적으로 파보고 싶다는 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마무리 &amp;amp; 다짐&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글또 10기 마지막 활동도 잘 마무리하며 이전보단 좀 더 발전한 개발자의 모습으로 성장하려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 부탁드립니다  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>ji-hyun</author>
      <guid isPermaLink="true">https://ts2ree.tistory.com/391</guid>
      <comments>https://ts2ree.tistory.com/391#entry391comment</comments>
      <pubDate>Wed, 2 Oct 2024 15:44:24 +0900</pubDate>
    </item>
  </channel>
</rss>