Jekyll2023-01-30T02:56:54+00:00https://www.androidhuman.com/커니의 안드로이드 이야기#Android, #Kotlin, and #Tesla안드로이드 스튜디오 Electric Eel 로 업데이트 후 발생하는 JAVA_HOME 환경 변수 문제 해결하기2023-01-28T00:00:00+00:002023-01-28T00:00:00+00:00https://www.androidhuman.com/fix-jre-path-android-studiio-ee<p>지난 1월 23일 안드로이드 스튜디오 Electric Eel 버전이 정식 출시되었습니다.</p>
<p>습관처럼 업데이트를 한 후 플러터 프로젝트를 열고 개발을 진행하려 하는데, 다음과 같은 메시지가 표시되며 안드로이드 앱을 빌드할 수 없었습니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: JAVA_HOME is set to an invalid directory: /Applications/Android Studio.app/Contents/jre/Contents/Home
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation.
Exception: Gradle task assembleDebug failed with exit code 1
</code></pre></div></div>
<p>에러 메시지로 보니 환경변수에 설정된 경로가 더 이상 존재하지 않는 것 같군요. 안드로이드 스튜디오가 설치된 경로를 확인해 보니 아래와 같이 기존에 <strong>jre</strong> 폴더에 있던 자바 개발도구가 <strong>jbr</strong> 로 이동했더군요.</p>
<p>
<img src="/assets/posts/2023-01-28-fix-jre-path-android-studiio-ee/jbr_folder_contents.png" alt="jbr 폴더 구성" class="center-image" />
</p>
<blockquote>
<p>주: 조금 찾아보니 JBR이 <a href="https://github.com/JetBrains/JetBrainsRuntime">JetBrains Runtime</a>을 의미하더군요! OpenJDK를 기반으로 한 자바 런타임이라 합니다.</p>
</blockquote>
<p>JBR이 기존의 JRE가 하던 역할을 완전히 대체하므로, 다른건 건드릴 필요 없이 환경변수 내 경로만 바꿔주면 문제를 해결할 수 있습니다.</p>
<h2 id="macos">macOS</h2>
<ol>
<li>
<p>안드로이드 스튜디오를 종료합니다.</p>
</li>
<li>
<p>셸 환경설정 파일을 엽니다. (zsh 사용 기준)</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nv">$ </span>vi ~/.zshrc
</code></pre></div> </div>
<blockquote>
<p>팁: <code class="highlighter-rouge">open ~/.zshrc</code>를 입력하면 텍스트 편집기(GUI)로 내용을 수정할 수 있습니다.</p>
</blockquote>
</li>
<li>
<p><code class="highlighter-rouge">JAVA_HOME</code> 환경변수를 설정하는 부분을 찾습니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> export JAVA_HOME="/Applications/Android Studio.app/Contents/jre/Contents/Home"
</code></pre></div> </div>
</li>
<li>
<p>경로 내 <strong>jre</strong>를 <strong>jbr</strong>로 바꿔줍니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code> export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
</code></pre></div> </div>
</li>
<li>
<p>안드로이드 스튜디오를 다시 실행한 후, 안드로이드 앱을 문제없이 빌드할 수 있는지 확인합니다.</p>
</li>
</ol>
<h2 id="windows">Windows</h2>
<ol>
<li>
<p>안드로이드 스튜디오를 종료합니다.</p>
</li>
<li>
<p><strong>설정</strong> 앱을 실행한 후, <strong>시스템</strong> 항목을 선택합니다.</p>
</li>
<li>
<p><strong>정보</strong>를 선택한 후, <strong>고급 시스템 설정</strong>을 선택합니다.</p>
</li>
<li>
<p><strong>고급</strong> 탭에서 <strong>환경 변수</strong>를 선택합니다.</p>
</li>
<li>
<p><strong>JAVA_HOME</strong> 변수를 찾은 후, 편집을 눌러 경로 내 <strong>jre</strong>를 <strong>jbr</strong>로 변경합니다.</p>
</li>
<li>
<p>안드로이드 스튜디오를 다시 실행한 후, 안드로이드 앱을 문제없이 빌드할 수 있는지 확인합니다.</p>
</li>
</ol>커니지난 1월 23일 안드로이드 스튜디오 Electric Eel 버전이 정식 출시되었습니다.안드로이드 스튜디오에서 새 플러터 프로젝트 생성하기2021-09-18T00:00:00+00:002021-09-18T00:00:00+00:00https://www.androidhuman.com/android_studio_new_flutter_project<p>이번 포스트에서는 안드로이드 스튜디오에서 새 플러터 프로젝트를 생성하는 과정을 알아보겠습니다.</p>
<p>안드로이드 스튜디오를 처음 설치한 분들을 위해 플러터 플러그인 설치 과정이 포함되어 있으므로, 플러그인을 이미 설치하신 분은 <strong>새 프로젝트 생성하기</strong> 문단으로 넘어가 주세요!</p>
<h2 id="플러터-플러그인-설치">플러터 플러그인 설치</h2>
<p>안드로이드 스튜디오로 플러터 앱을 개발하려면 플러터 플러그인을 설치해야 합니다.</p>
<p>안드로이드 스튜디오의 Welcome 화면에 <strong>New Flutter Project</strong>가 표시되는지 여부로 플러그인 설치 여부를 확인할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/as_no_flutter_plugin.png" alt="플러터 플러그인이 설치되지 않은 상태" class="center-image" />
</p>
<p>플러터 플러그인을 설치하기 위해 왼쪽의 <strong>Plugins</strong>탭을 선택합니다. 검색 창에 <code class="highlighter-rouge">Flutter</code>를 입력하면 다음과 같이 검색 결과 최상단에 플러터 플러그인이 표시됩니다. <strong>Install</strong> 버튼을 눌러 플러그인을 설치합니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/install_flutter_plugin_1.png" alt="플러터 플러그인 검색 결과" class="center-image" />
</p>
<p>개인정보 관련 안내 다이얼로그가 표시됩니다. <strong>Accept</strong>를 눌러 설치를 게속 진행합니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/install_flutter_plugin_2.png" alt="개인정보 안내 다이얼로그" class="center-image" />
</p>
<p>플러터 플러그인에 필요한 플러그인인 Dart 플러그인도 함께 설치할지 물어봅니다. 플러터 앱은 Dart언어로 개발하기 때문에 Dart 플러그인도 함께 설치해주어야 합니다. <strong>Install</strong> 버튼을 눌러 설치합니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/install_flutter_plugin_3.png" alt="Dart 플러그인 설치 안내 다이얼로그" class="center-image" />
</p>
<p>플러그인 설치가 완료되면 안드로이드 스튜디오를 재시작 할지 묻습니다. 안드로이드 스튜디오를 재시작 하면 플러그인 설치가 모두 완료됩니다.</p>
<h2 id="새-프로젝트-생성하기">새 프로젝트 생성하기</h2>
<p>안드로이드 스튜디오의 Welcome 화면에서 <strong>New Flutter Project</strong>를 선택합니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/new_flutter_project_1.png" alt="" class="center-image" />
</p>
<p><strong>Flutter SDK Path</strong>에 플러터 SDK가 설치된 경로를 입력합니다. 아직 SDK를 설치하지 않았다면 <a href="https://flutter.dev/docs/get-started/install">개발자 문서</a>를 참고하여 플러터 SDK를 먼저 설치해주세요.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/new_flutter_project_2.png" alt="" class="center-image" />
</p>
<p>생성할 프로젝트 정보를 입력합니다.</p>
<ul>
<li>Project name: 프로젝트 이름입니다. 영어 소문자, 숫자 및 언더바(<code class="highlighter-rouge">_</code>)로만 구성할 수 있습니다.</li>
<li>Project location: 프로젝트를 생성할 경로입니다.</li>
<li>Description: 프로젝트에 대한 간단한 설명을 입력합니다. 패키지 형태로 배포할 것이 아니라면 그냥 그대로 두어도 무방합니다.</li>
<li>Project type: 프로젝트 유형을 선택합니다. 일반 사용자 대상 앱을 만드려면 Application을 선택합니다.</li>
<li>Organization: 개발사를 고유하게 실별할 수 있는 ID를 입력합니다. 일반적으로 개발사의 도메인을 역순으로 나열한 형태를 사용합니다. (예: <code class="highlighter-rouge">google.com</code>인 경우 <code class="highlighter-rouge">com.google</code> 사용)</li>
<li>Android/iOS language: 안드로이드 및 iOS에서 사용할 언어를 선택할 수 있습니다.</li>
<li>Platforms: 앱을 배포할 플랫폼을 선택합니다.</li>
</ul>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/new_flutter_project_3.png" alt="" class="center-image" />
</p>
<p><strong>Finish</strong> 버튼을 누르면 다음과 같이 새로 생성된 플러터 프로젝트의 모습을 확인할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/new_flutter_project_4.png" alt="" class="center-image" />
</p>
<h2 id="앱-id-application-id--bundle-id-변경하기">앱 ID (Application ID / Bundle ID) 변경하기</h2>
<p>앱 ID는 각각의 앱을 고유하게 식별할 수 있는 식별자로, 안드로이드에서는 <strong>Application ID</strong>, iOS에서는 <strong>Bundle ID</strong>로 불립니다.</p>
<p>플러터 프로젝트를 생성하면 프로젝트 이름에 기반하여 Application ID와 Bundle ID가 생성되는데, 안드로이드와 iOS가 약간 다르게 생성되므로 주의가 필요합니다.</p>
<p>Organization으로 <code class="highlighter-rouge">com.androidhuman</code>, 프로젝트 이름으로 <code class="highlighter-rouge">hello_world</code>를 기입했다면, Application ID와 Bundle ID는 다음과 같이 생성됩니다.</p>
<ul>
<li>Application ID: <code class="highlighter-rouge">com.androidhuman.hello_world</code> (프로젝트 이름 그대로 유지)</li>
<li>Bundle ID: <code class="highlighter-rouge">com.androidhuman.helloWorld</code> (언더바가 사라지면서 언더바 바로 뒷글자가 대문자로 치환됨)</li>
</ul>
<p>동일한 앱은 플랫폼과 무관하게 동일한 앱 ID를 부여하는게 일반적입니다. 따라서 플랫폼별로 각기 다른 앱 ID를 통일해줍니다.</p>
<h3 id="안드로이드">안드로이드</h3>
<p><code class="highlighter-rouge">android/app/build.gradle</code> 파일을 연 후, <code class="highlighter-rouge">defaultConfig</code> 내 <code class="highlighter-rouge">applicationId</code>를 수정합니다. 여기에서는 <code class="highlighter-rouge">com.androidhuman.hello_world</code>를 <code class="highlighter-rouge">com.androidhuman.helloworld</code>로 수정했습니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/flutter_app_id_1.png" alt="" class="center-image" />
</p>
<p>다음으로, <code class="highlighter-rouge">android/app/src/main/AndroidManifext.xml</code> 파일은 연 후 <code class="highlighter-rouge">package</code>를 수정합니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/flutter_app_id_2.png" alt="" class="center-image" />
</p>
<h3 id="ios">iOS</h3>
<p>프로젝트 이름을 오른쪽 클릭한 후, <strong>Flutter > Open iOS module in Xcode</strong> 메뉴를 선택하여 Xcode 프로젝트를 엽니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/flutter_app_id_3.png" alt="" class="center-image" />
</p>
<p>프로젝트 이름(<code class="highlighter-rouge">Runner</code>)을 선택한 후, General 탭을 선택합니다. <strong>Identity</strong> 섹션 내 <strong>Bundle Identifier</strong>를 수정합니다. 여기에서는 <code class="highlighter-rouge">com.androidhuman.helloWorld</code>를 <code class="highlighter-rouge">com.androidhuman.helloworld</code>로 수정했습니다.</p>
<p>
<img src="/assets/posts/2021-09-18-android_studio_new_flutter_project/flutter_app_id_4.png" alt="" class="center-image" />
</p>커니이번 포스트에서는 안드로이드 스튜디오에서 새 플러터 프로젝트를 생성하는 과정을 알아보겠습니다.플러터 프로젝트에서 안드로이드 에뮬레이터 실행시 Unable to locate adb 오류가 발생한다면?2021-08-03T00:00:00+00:002021-08-03T00:00:00+00:00https://www.androidhuman.com/android_studio_unable_to_locate_adb<p>플러터 프로젝트가 열린 상태에서 안드로이드 에뮬레이터를 실행할 때, 다음과 같이 <strong>Unable to locate adb</strong> 오류 메시지가 뜨는 경우가 있습니다.</p>
<p>
<img src="/assets/posts/2021-08-03-android_studio_unable_to_locate_adb/unable_to_locate_adb.png" alt="" class="center-image" />
</p>
<p>이 때, 오류 메시지와 함께 에뮬레이터가 아예 실행되지 않기도 하고, 잠시 후에 에뮬레이터가 정상적으로 실행되기도 합니다.</p>
<h2 id="에뮬레이터가-실행되지-않을-때">에뮬레이터가 실행되지 않을 때</h2>
<p>안드로이드 스튜디오를 연 후, <strong>SDK Manager</strong>를 실행한 다음 <strong>SDK Tools</strong> 탭을 선택합니다.</p>
<p>다음과 같이 안드로이드 SDK의 구성요소 중 하나인 <strong>Android SDK 플랫폼 도구 (Platform-Tools)</strong>가 설치되어 있는지 확인합니다.</p>
<p>
<img src="/assets/posts/2021-08-03-android_studio_unable_to_locate_adb/sdk_manager.png" alt="" class="center-image" />
</p>
<h2 id="오류-메시지가-표시되지만-에뮬레이터가-실행될-때">오류 메시지가 표시되지만 에뮬레이터가 실행될 때</h2>
<p>에뮬레이터가 아예 실행되지 않을 때와 동일하게, <strong>Android SDK 플랫폼 도구</strong>가 설치되어 있는지 먼저 확인합니다.</p>
<p>플랫폼 도구가 정상적으로 설치되어 있다면, 프로젝트 설정을 점검해야 합니다.</p>
<p>프로젝트 설정을 확인하기 위해 안드로이드 스튜디오 메뉴에서 <strong>File > Project Structure</strong>를 선택합니다. 아마 다음과 같이 Project SDK가 선택되어 있지 않을 것입니다. (다음 스크린샷처럼 No SDK로 표시됨)</p>
<p>
<img src="/assets/posts/2021-08-03-android_studio_unable_to_locate_adb/project_settings_no_sdk.png" alt="" class="center-image" />
</p>
<p>여기에서 임의의 안드로이드 SDK (예: Android API 30 Platform)을 선택합니다. 아무 버전이나 선택해도 무방합니다.</p>
<p>다음은 Android API 30 Platform을 선택한 예를 보여줍니다.</p>
<p>
<img src="/assets/posts/2021-08-03-android_studio_unable_to_locate_adb/project_settings_sdk.png" alt="" class="center-image" />
</p>
<p>선택이 끝났다면 OK 버튼을 눌러 프로젝트 설정을 저장하면 됩니다. 이제 AVD Manager로 돌아와 에뮬레이터를 실행해보면, 오류 메시지 없이 잘 실행되는 것을 확인할 수 있습니다.</p>커니플러터 프로젝트가 열린 상태에서 안드로이드 에뮬레이터를 실행할 때, 다음과 같이 Unable to locate adb 오류 메시지가 뜨는 경우가 있습니다.플러터 iOS 앱 실행시 iproxy 관련 경고가 뜨며 앱이 실행되지 않는다면?2021-07-14T00:00:00+00:002021-07-14T00:00:00+00:00https://www.androidhuman.com/flutter_ios_iproxy<p>플러터 개발 도구를 갓 설치한 분들이라면, 플러터 프로젝트를 iOS에서 실행할 때 스플래시 화면만 뜨고 멈춰있는 현상을 마주할 가능성이 있습니다.</p>
<p>이 때 콘솔 로그를 보면, 다음과 같이 VM Service에 연결하지 못하고 있다는 메시지를 확인할 수 있습니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[connection] nw_endpoint_handler_set_adaptive_read_handler [C2.1 142.250.196.106:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for read_timeout failed
[connection] nw_endpoint_handler_set_adaptive_write_handler [C2.1 142.250.196.106:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for write_timeout failed
Connecting to the VM Service is taking longer than expected...
Still attempting to connect to the VM Service...
If you do NOT see the Flutter application running, it might have crashed. The device logs (e.g. from adb or XCode) might have more details.
If you do see the Flutter application running on the device, try re-running with --host-vmservice-port to use a specific port known to be available.
[connection] nw_endpoint_handler_set_adaptive_read_handler [C3.1 142.250.196.106:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for read_timeout failed
[connection] nw_endpoint_handler_set_adaptive_write_handler [C3.1 142.250.196.106:443 ready channel-flow (satisfied (Path is satisfied), viable, interface: en0, ipv4, dns)] unregister notification for write_timeout failed
Exception attempting to connect to the VM Service: SocketException: OS Error: Connection refused, errno = 61, address = 127.0.0.1, port = 49319
This was attempt #50. Will retry in 0:00:01.600000.
</code></pre></div></div>
<p>이러한 현상은 플러터 개발 도구 중 iOS 앱을 실행할 때 사용하는 도구가 컴퓨터 보안 설정 때문에 차단되었을 때 발생하며, 문제가 발생하면 다음과 같이 <strong>iproxy 개발자를 확인할 수 없다</strong>는 메시지 (macOS cannot verify the developer of “iproxy”)가 표시됩니다.</p>
<p>
<img src="/assets/posts/2021-07-14-flutter_ios_iproxy/warning.png" alt="" class="center-image" />
</p>
<p>이를 해결하려면, 위 메시지가 표시된 후 <strong>시스템 환경설정 (System Preferences) > 보안 및 개인 정보 보호 (Security & Privacy)</strong>로 이동합니다.</p>
<p>화면 내 <strong>다음에서 다운로드한 앱 허용 (Allow apps downloaded from)</strong>을 보면 iproxy가 차단되었다는 메시지를 확인할 수 있을 것입니다. 여기에서 허용(Allow anyway) 버튼을 눌러 <strong>iproxy</strong>를 허용해 주면 됩니다.</p>
<p>
<img src="/assets/posts/2021-07-14-flutter_ios_iproxy/preferences.png" alt="" class="center-image" />
</p>
<p>이제 플러터 프로젝트로 돌아와 다시 iOS 앱을 실행하면, 스플래시 화면 후 정상적으로 앱이 실행되는 것을 확인할 수 있습니다.</p>커니플러터 개발 도구를 갓 설치한 분들이라면, 플러터 프로젝트를 iOS에서 실행할 때 스플래시 화면만 뜨고 멈춰있는 현상을 마주할 가능성이 있습니다.Apple Silicon 맥북에서 안드로이드 에뮬레이터 사용하기2021-07-04T00:00:00+00:002021-07-04T00:00:00+00:00https://www.androidhuman.com/android_emulator_apple_silicon<p>애플은 2006년부터 맥 기기에 Intel CPU를 사용해왔습니다. 그런데, 얼마 전 애플 실리콘 (Apple Silicon) 칩셋을 발표하면서 맥 기기에 인텔 CPU 대신 애플 실리콘 칩셋을 탑재하기 시작했습니다. (예: M1 맥북)</p>
<p>애플 실리콘 칩셋은 아이폰이나 아이패드와 같이 모바일 기기에서 사용하는 ARM 명렁어를 사용하므로, 기존 인텔 맥에서 사용하는 x86 기반 명령어와 호환되지 않습니다.</p>
<p>때문에, 기존에 인텔 맥에서 잘 실행되던 일부 앱이 애플 실리콘 맥에서는 제대로 실행되지 않을 가능성이 높습니다. Rosseta 2를 사용하면 x86용으로 제작된 앱도 사용할 수 있지만, 복잡한 앱일수록 정상 동작하지 않거나 매우 느리게 동작할 확률이 높습니다.</p>
<p>안드로이드 에뮬레이터는 일반적인 안드로이드 기기와 달리 하드웨어 가속을 위해 x86 기반 이미지를 사용하는데요, 이 때문에 애플 실리콘 맥에서는 제대로 사용할 수 없어 <a href="https://github.com/google/android-emulator-m1-preview">프리뷰 버전 에뮬레이터</a>를 별도로 설치해서 사용해야 했습니다.</p>
<p>다행이도, 2021년 3월 경부터 기존의 안드로이드 에뮬레이터에 애플 실리콘 지원 기능이 추가되어 애플 실리콘 맥에서도 에뮬레이터를 사용할 수 있게 되었습니다.</p>
<p>애플 실리콘 맥에서 안드로이드 에뮬레이터를 사용하는 과정은 다음과 같습니다.</p>
<p><strong>AVD Manager</strong>를 실행한 후, <strong>Create Virtual Device</strong> 버튼을 누릅니다.</p>
<p>
<img src="/assets/posts/2021-07-04-android_emulator_apple_silicon/avd_manager_1.png" alt="" class="center-image" />
</p>
<p>다음으로 사용할 기기 종류를 선택합니다. 원하는 기기를 아무거나 선택합니다.</p>
<p>
<img src="/assets/posts/2021-07-04-android_emulator_apple_silicon/avd_manager_2.png" alt="" class="center-image" />
</p>
<p>이제 시스템 이미지를 선택합니다. <strong>Ohter images</strong> 탭을 선택한 후, <strong>arm64-v8a</strong> ABI인 시스템 이미지를 선택합니다.</p>
<p>아직은 모든 버전이 아닌 프리뷰 버전 (스크린샷에서는 <strong>S</strong>)만 지원합니다. Recommendation 탭에서는 x86 이미지를 사용하는 걸 권장한다는 메시지가 나오는데요, 이는 x86 기반 CPU를 사용하는 기기에만 해당되므로 무시합니다.</p>
<p>
<img src="/assets/posts/2021-07-04-android_emulator_apple_silicon/avd_manager_3.png" alt="" class="center-image" />
</p>
<p>마지막으로 AVD 이름을 설정합니다. 마찬가지로 Recommendation 란에 나오는 메시지는 무시합니다.</p>
<p>
<img src="/assets/posts/2021-07-04-android_emulator_apple_silicon/avd_manager_4.png" alt="" class="center-image" />
</p>
<p>이제 AVD 생성이 완료되었습니다. 기존에 생성된 AVD와 달리 CPU/ABI가 <strong>arm64</strong>로 되어있는 것을 확인할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-07-04-android_emulator_apple_silicon/avd_manager_5.png" alt="" class="center-image" />
</p>
<p>AVD를 실행하면 에뮬레이터가 실행되고, 방금 생성한 AVD가 잘 실행되는 것을 확인할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-07-04-android_emulator_apple_silicon/emulator.png" alt="" class="center-image" />
</p>커니애플은 2006년부터 맥 기기에 Intel CPU를 사용해왔습니다. 그런데, 얼마 전 애플 실리콘 (Apple Silicon) 칩셋을 발표하면서 맥 기기에 인텔 CPU 대신 애플 실리콘 칩셋을 탑재하기 시작했습니다. (예: M1 맥북)안드로이드 스튜디오에서 Open Android module in Android Studio 메뉴가 사라졌어요!2021-06-03T00:00:00+00:002021-06-03T00:00:00+00:00https://www.androidhuman.com/flutter_android_missing_open_android_module<p>모바일 (안드로이드, iOS)를 타겟으로 하는 플러터 프로젝트는 안드로이드 프로젝트 (Gradle 프로젝트)와 iOS 프로젝트 (Xcode workspace)가 포함되어 있습니다.</p>
<p>안드로이드 스튜디오에 플러터 플러그인을 설치하면, 다음과 같이 안드로이드/iOS 프로젝트를 열 수 있는 컨텍스트 메뉴를 사용할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-06-03-flutter_android_missing_open_android_module/fixed.png" alt="" class="center-image" />
</p>
<p>그런데, 간혹 다음과 같이 안드로이드 프로젝트를 여는 메뉴인 <strong>Open Android module in Android Studio</strong> 메뉴가 나오지 않는 경우가 있습니다.</p>
<p>
<img src="/assets/posts/2021-06-03-flutter_android_missing_open_android_module/missing.png" alt="" class="center-image" />
</p>
<p>이는 안드로이드 프로젝트에 IntelliJ 프로젝트 파일 (<code class="highlighter-rouge">*.iml</code>)이 없어서 발생하는 문제입니다. 이를 해결하는 방법은 다음과 같습니다.</p>
<ol>
<li>
<p>텍스트 편집기 (Visual Stdio Code 등)을 연 후, 다음 내용을 붙여넣습니다.</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cp"><?xml version="1.0" encoding="UTF-8"?></span>
<span class="nt"><module</span> <span class="na">type=</span><span class="s">"JAVA_MODULE"</span> <span class="na">version=</span><span class="s">"4"</span><span class="nt">></span>
<span class="nt"><component</span> <span class="na">name=</span><span class="s">"FacetManager"</span><span class="nt">></span>
<span class="nt"><facet</span> <span class="na">type=</span><span class="s">"android"</span> <span class="na">name=</span><span class="s">"Android"</span><span class="nt">></span>
<span class="nt"><configuration></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"ALLOW_USER_CONFIGURATION"</span> <span class="na">value=</span><span class="s">"false"</span> <span class="nt">/></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"GEN_FOLDER_RELATIVE_PATH_APT"</span> <span class="na">value=</span><span class="s">"/gen"</span> <span class="nt">/></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"GEN_FOLDER_RELATIVE_PATH_AIDL"</span> <span class="na">value=</span><span class="s">"/gen"</span> <span class="nt">/></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"MANIFEST_FILE_RELATIVE_PATH"</span> <span class="na">value=</span><span class="s">"/app/src/main/AndroidManifest.xml"</span> <span class="nt">/></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"RES_FOLDER_RELATIVE_PATH"</span> <span class="na">value=</span><span class="s">"/app/src/main/res"</span> <span class="nt">/></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"ASSETS_FOLDER_RELATIVE_PATH"</span> <span class="na">value=</span><span class="s">"/app/src/main/assets"</span> <span class="nt">/></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"LIBS_FOLDER_RELATIVE_PATH"</span> <span class="na">value=</span><span class="s">"/app/src/main/libs"</span> <span class="nt">/></span>
<span class="nt"><option</span> <span class="na">name=</span><span class="s">"PROGUARD_LOGS_FOLDER_RELATIVE_PATH"</span> <span class="na">value=</span><span class="s">"/app/src/main/proguard_logs"</span> <span class="nt">/></span>
<span class="nt"></configuration></span>
<span class="nt"></facet></span>
<span class="nt"></component></span>
<span class="nt"><component</span> <span class="na">name=</span><span class="s">"NewModuleRootManager"</span> <span class="na">inherit-compiler-output=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><exclude-output</span> <span class="nt">/></span>
<span class="nt"><content</span> <span class="na">url=</span><span class="s">"file://$MODULE_DIR$"</span><span class="nt">></span>
<span class="nt"><sourceFolder</span> <span class="na">url=</span><span class="s">"file://$MODULE_DIR$/app/src/main/java"</span> <span class="na">isTestSource=</span><span class="s">"false"</span> <span class="nt">/></span>
<span class="nt"><sourceFolder</span> <span class="na">url=</span><span class="s">"file://$MODULE_DIR$/app/src/main/kotlin"</span> <span class="na">isTestSource=</span><span class="s">"false"</span> <span class="nt">/></span>
<span class="nt"><sourceFolder</span> <span class="na">url=</span><span class="s">"file://$MODULE_DIR$/gen"</span> <span class="na">isTestSource=</span><span class="s">"false"</span> <span class="na">generated=</span><span class="s">"true"</span> <span class="nt">/></span>
<span class="nt"></content></span>
<span class="nt"><orderEntry</span> <span class="na">type=</span><span class="s">"jdk"</span> <span class="na">jdkName=</span><span class="s">"Android API 29 Platform"</span> <span class="na">jdkType=</span><span class="s">"Android SDK"</span> <span class="nt">/></span>
<span class="nt"><orderEntry</span> <span class="na">type=</span><span class="s">"sourceFolder"</span> <span class="na">forTests=</span><span class="s">"false"</span> <span class="nt">/></span>
<span class="nt"><orderEntry</span> <span class="na">type=</span><span class="s">"library"</span> <span class="na">name=</span><span class="s">"Flutter for Android"</span> <span class="na">level=</span><span class="s">"project"</span> <span class="nt">/></span>
<span class="nt"><orderEntry</span> <span class="na">type=</span><span class="s">"library"</span> <span class="na">name=</span><span class="s">"KotlinJavaRuntime"</span> <span class="na">level=</span><span class="s">"project"</span> <span class="nt">/></span>
<span class="nt"></component></span>
<span class="nt"></module></span>
</code></pre></div> </div>
</li>
<li>
<p>플러터 프로젝트 내 안드로이드 프로젝트 폴더 (<code class="highlighter-rouge">android</code>)에 <code class="highlighter-rouge">{프로젝트 이름}_android.iml</code>라는 이름으로 파일을 저장합니다. (참고: <code class="highlighter-rouge">build.gradle</code> 및 <code class="highlighter-rouge">settings.gradle</code>이 있는 폴더입니다)</p>
<ul>
<li>예: 프로젝트 이름이 <code class="highlighter-rouge">my_app</code>인 경우 <code class="highlighter-rouge">my_app_android.iml</code>으로 파일을 저장하면 됩니다.</li>
</ul>
</li>
</ol>
<p>다시 안드로이드 스튜디오로 돌아와보면, 다음 스크린샷과 같이 안드로이드 프로젝트를 여는 메뉴가 다시 나오는 것을 확인할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-06-03-flutter_android_missing_open_android_module/fixed.png" alt="" class="center-image" />
</p>커니모바일 (안드로이드, iOS)를 타겟으로 하는 플러터 프로젝트는 안드로이드 프로젝트 (Gradle 프로젝트)와 iOS 프로젝트 (Xcode workspace)가 포함되어 있습니다.flutter doctor –android-licenses 문제 해결하기2021-06-02T00:00:00+00:002021-06-02T00:00:00+00:00https://www.androidhuman.com/flutter_android_license_noclassdeffound<p>안드로이드 SDK 사용 동의가 제대로 되지 않은 경우, <code class="highlighter-rouge">flutter doctor</code>를 실행하면 다음과 같이 에러 메시지가 표시됩니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.2.1, on macOS 11.4 20F71 darwin-x64, locale
en)
[!] Android toolchain - develop for Android devices (Android SDK version
30.0.3)
✗ Android license status unknown.
Run `flutter doctor --android-licenses` to accept the SDK licenses.
See https://flutter.dev/docs/get-started/install/macos#android-setup
for more details.
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.2)
[✓] VS Code (version 1.56.2)
[✓] Connected device (1 available)
</code></pre></div></div>
<p>이 문제는 안드로이드 스튜디오를 새로 설치했을 때 종종 발생합니다. 이 때, <code class="highlighter-rouge">flutter doctor --android-license</code> 명령을 실행하면 문제를 해결할 수 있습니다.</p>
<p>그런데, 간혹 이 과정에서 다음과 같이 <code class="highlighter-rouge">java.lang.NoClassDefFoundError</code> 오류가 발생하기도 합니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ ~ flutter doctor --android-licenses
Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)
at com.android.repository.api.SchemaModule.<init>(SchemaModule.java:75)
at com.android.sdklib.repository.AndroidSdkHandler.<clinit>(AndroidSdkHandler.java:81)
at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:73)
at com.android.sdklib.tool.sdkmanager.SdkManagerCli.main(SdkManagerCli.java:48)
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
... 5 more
</code></pre></div></div>
<p>이 문제는 안드로이드 SDK 구성요소 중 <strong>안드로이드 SDK 커맨드라인 도구 (Android SDK Command-line Tools)</strong>가 설치되어 있지 않을때 발생합니다.</p>
<p>문제를 해결하려면 안드로이드 스튜디오에서 <strong>SDK Manager</strong>를 실행한 후, <strong>SDK Tools</strong> 탭에서 <strong>Android SDK Command-line Tools</strong>를 설치하면 됩니다.</p>
<p>
<img src="/assets/posts/2021-06-02-flutter_android_license_noclassdeffound/sdk_manager.png" alt="" class="center-image" />
</p>
<p>커맨드라인 도구를 설치한 후, <code class="highlighter-rouge">flutter doctor --android-licenses</code> 명령을 다시 실행하면 다음과 같이 정상적으로 명령이 실행되는 것을 볼 수 있습니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ flutter doctor --android-licenses
Loading local repository...
5 of 7 SDK package licenses not accepted.
Review licenses that have not been accepted (y/N)?
...(생략)...
Accept? (y/N): y
All SDK package licenses accepted
</code></pre></div></div>
<p><code class="highlighter-rouge">flutter doctor</code>를 통해 모두 정상 상태로 돌아간 것을 확인할 수 있습니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.2.1, on macOS 11.4 20F71 darwin-x64, locale
en)
[✓] Android toolchain - develop for Android devices (Android SDK version
30.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio (version 4.2)
[✓] VS Code (version 1.56.2)
[✓] Connected device (1 available)
• No issues found!
</code></pre></div></div>커니안드로이드 SDK 사용 동의가 제대로 되지 않은 경우, flutter doctor를 실행하면 다음과 같이 에러 메시지가 표시됩니다.안드로이드 스튜디오에서 플러터 앱 실행시 JAVA_HOME PATH 문제가 발생한다면?2021-05-28T00:00:00+00:002021-05-28T00:00:00+00:00https://www.androidhuman.com/flutter_android_studio_not_installed<p>안드로이드 스튜디오는 Open JDK를 내장하고 있습니다. 따라서 별도의 자바 개발도구 (JDK; Java Development Kit)을 설치하지 않아도 됩니다.</p>
<p>그런데, 플러터 프로젝트에서 안드로이드 앱을 실행할 때 다음과 같은 오류가 발생하는 경우가 있습니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation.
</code></pre></div></div>
<p>이는 플러터 개발 도구에서 안드로이드 스튜디오의 경로를 제대로 잡지 못했을 때 발생합니다. <code class="highlighter-rouge">flutter doctor</code> 명령어를 실행해 보면 다음과 같이 안드로이드 스튜디오를 찾지 못했다는 메시지가 표시됩니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 2.0.6, on macOS 11.3 20E232 darwin-x64, locale
en-KR)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[!] Android Studio (not installed)
[✓] VS Code (version 1.56.2)
[✓] Connected device (1 available)
</code></pre></div></div>
<p>이 문제를 해결하려면, <code class="highlighter-rouge">flutter config --android-studio-dir</code> 명령어를 사용하여 안드로이드 스튜디오가 설치된 경로를 다시 설정해 주면 됩니다.</p>
<p>예를 들어 안드로이드 스튜디오가 <code class="highlighter-rouge">C:\Program Files\Android\Android Studio</code>에 설치되어 있다면, 다음 명령어를 실행하면 됩니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter config --android-studio-dir="C:\Program Files\Android\Android Studio"
</code></pre></div></div>커니안드로이드 스튜디오는 Open JDK를 내장하고 있습니다. 따라서 별도의 자바 개발도구 (JDK; Java Development Kit)을 설치하지 않아도 됩니다.iOS 14.5 - 좌충우돌 앱 추적 투명성(App Tracking Transparency) 적용기2021-04-28T10:00:00+00:002021-04-28T10:00:00+00:00https://www.androidhuman.com/ios_att_explainer<p>iOS 14.5부터 <a href="https://developer.apple.com/documentation/apptrackingtransparency">앱 추적 투명성 (App Tracking Transparency) 프레임워크</a>가 추가되면서, <a href="https://developer.apple.com/kr/app-store/user-privacy-and-data-use/">사용자에게 명시적인 허가를 받아야만</a> 기기의 기기의 광고 ID (IDFA; IDentifier for Advertising)에 접근할 수 있게 되었습니다.</p>
<p>광고 ID는 사용자에게 맞춤형 광고 (예: 최근에 조회한 상품의 연관 상품을 보여주는 등)를 제공할 때 필요한데요, 사용자가 클릭할 확률이 높기에 일반적인 광고보다 광고 단가가 높습니다. 따라서 광고 게시자(Publisher) 입장에서 더 높은 수익을 기대할 수 있습니다.</p>
<p>만약 광고 ID에 접근할 수 없다면, 더 이상 맞춤형 광고를 제공할 수 없습니다. 때문에 기존보다 낮은 단가의 광고를 받을 가능성이 높고, 광고 수익 하락으로 이어질 가능성이 매우 높습니다.</p>
<h1 id="추적-권한-요청하기">추적 권한 요청하기</h1>
<p>앱 투명성 프레임워크를 사용하면 사용자에게 <strong>추적(Tracking)</strong> 권한을 요청할 수 있는데요, 사용자가 권한을 허용해야만 앱에서 광고 ID에 접근할 수 있게 됩니다.</p>
<p>추적 권한은 <strong>단 한번만</strong> 요청할 수 있는데요, 앱을 <strong>완전히 삭제하고 재설치하지 않는 한</strong> 다시 권한을 요청할 수 없습니다. 따라서 적절한 시점에, 권한이 필요한 상세한 이유를 사용자에게 충분히 제공한 다음 권한을 요청해야 합니다.</p>
<p>앱 투명성 프레임워크에서는 권한을 요청할 때 사용자에게 보여줄 문구를 지정할 수 있습니다. <code class="highlighter-rouge">Info.plist</code>의 <code class="highlighter-rouge">NSUserTrackingUsageDescription</code> 키 (<a href="https://developer.apple.com/documentation/bundleresources/information_property_list/nsusertrackingusagedescription">Privacy - Tracking Usage Description</a>)에 추적 권한이 필요한 이유를 기재하면 되는데요, 다음 스크린샷에서 보는 것처럼 권한 요청 다이얼로그에 문구가 표시되는 것을 확인할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-04-28-ios_att_explainer/att_prompt.png" alt="" class="center-image" />
</p>
<p>권한 요청 다이얼로그를 보여주기 전에 권한이 필요한 이유를 자세히 설명하는 화면 (Explainer screen)을 활용할 수도 있습니다. 화면을 개발자가 원하는 대로 꾸밀 수 있으므로, 이를 적극적으로 활용하면 추적 허용 비율을 끌어올릴 수 있습니다. 다음은 커스텀 안내 화면을 구현한 사례입니다.</p>
<p>
<img src="/assets/posts/2021-04-28-ios_att_explainer/att_explainer.png" alt="" class="center-image" />
</p>
<h1 id="광고-네트워크별-skadnetworkidentifier-추가하기">광고 네트워크별 SKAdNetworkIdentifier 추가하기</h1>
<p>사용자가 추적을 허용하지 않는 경우, IDFA에 접근할 수 없기에 광고 네트워크에서 앱 설치와 같은 전환(conversion) 이벤트를 집계할 수 없습니다. 이를 보완하기 위해 애플에서는 광고 네트워크 별로 고유한 ID (<code class="highlighter-rouge">SKAdNetworkIdentifier</code>)를 부여하고, 이를 통해 각 네트워크가 전환 실적을 확인할 수 있도록 합니다.</p>
<p>네트워크에 따라 <code class="highlighter-rouge">SkAdNetworkIdentifier</code>를 추가하지 않는 앱에 광고를 서빙하지 않는 경우도 있으므로, 실적 하락을 방지하려면 반드시 추가해야 합니다.</p>
<p><code class="highlighter-rouge">SKAdNetworkIdentifier</code>는 <code class="highlighter-rouge">Info.plist</code> 내 <code class="highlighter-rouge">SKAdNetworkItems</code> 항목에 추가하면 됩니다. 광고 네트워크별로 추가해야 하는 항목이 다르므로, 사용하는 광고 네트워크의 개발자 문서를 확인하는게 가장 정확합니다.</p>
<p>다음은 2021년 4월 28일 기준으로 <a href="https://developers.google.com/admob/ios/ios14">구글 애드몹</a>을 사용할 때 추가해야 하는 <code class="highlighter-rouge">SKAdNetworkIdentifier</code> 목록입니다.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code><key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4fzdc2evr5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2fnua5tdw4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ydx93a7ass.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>5a6flpkh64.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>p78axxw29g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v72qych5uu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c6k4g5qg8m.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>s39g8k73mm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qy4746246.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3sh42y64q3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>f38h382jlk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>hs6bdukanm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>prcb7njmu6.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wzmmz9fp6w.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>yclnxrl5pm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4468km3ulz.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>t38b2kh725.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>7ug5zh24hu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9rd848q2bz.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n6fk4nfna4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbd757ywx3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9t245vhmpl.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2u9pt9hc89.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8s468mfl3y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>av6w8kgt66.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>klf5c3l5u5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ppxm28t8ap.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>424m5254lk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>uw77j35x4d.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>e5fvkxwrpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>zq492l623r.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qcr597p9d.skadnetwork</string>
</dict>
</array>
</code></pre></div></div>
<h1 id="사용자-안내-문구-작성하기">사용자 안내 문구 작성하기</h1>
<p>권한 요청 전 보여주는 안내 문구는 가급적 많은 사용자가 ‘추적 허용’을 선택하게 만들어야 합니다. 하지만, 너무 욕심을 부리면 앱스토어 심사 가이드라인을 위배하게 되므로 적절한 선(?!)을 지켜야 합니다. (작성자 본인 이야기…)</p>
<p>저도 욕심을 부리다가 두번 리젝을 먹었는데요, 어떤 표현을 사용할 수 있을지 고민하는 분들을 위해 제 사례를 공유합니다.</p>
<h2 id="리젝-사례-1">리젝 사례 1</h2>
<p>
<img src="/assets/posts/2021-04-28-ios_att_explainer/att_v1.jpg" alt="" class="center-image" />
</p>
<p><a href="https://developer.apple.com/kr/app-store/review/guidelines/#unacceptable">App Store 심사 지침 3.2.2 (vi)</a> 항을 위반하는 구현입니다. ‘다음 화면에서 ‘허용’ 을 선택’ 과 같이 직접적으로 사용자의 행동을 요구하는 문구를 사용할 수 없습니다.</p>
<p>더불어, ‘광고로 무료 앱을 후원하세요’ 문구도 ‘허용 버튼을 누르세요’ 문구와 함께 사용자의 특정 행동을 유도하는 문구라는 피드백을 받았습니다.</p>
<h2 id="리젝-사례-2">리젝 사례 2</h2>
<p>
<img src="/assets/posts/2021-04-28-ios_att_explainer/att_v2.jpg" alt="" class="center-image" />
</p>
<p>안내 문구를 수정한 빌드입니다. 안내 문구에는 문제가 없지만, ‘다음에 결정’ 옵션 (권한 요청 다이얼로그를 표시하지 않고, 다음에 다시 앱을 실행했을 때 다시 한번 안내 화면을 띄우는 기능)이 <a href="https://developer.apple.com/kr/app-store/review/guidelines/#data-collection-and-storage">App Store 심사 지침 5.1.1 (iv)</a> 항을 위반합니다.</p>
<h2 id="통과-사례">통과 사례</h2>
<p>
<img src="/assets/posts/2021-04-28-ios_att_explainer/att_final.jpg" alt="" class="center-image" />
</p>
<p>‘다음에 결정’ 옵션을 완전히 삭제한 구현입니다. 앱을 처음으로 실행했을 떄 안내 문구가 표시되고, ‘계속’ 버튼을 누르면 바로 권한 요청 다이얼로그를 띄워줍니다. 따라서, 이 시점 이후로는 <strong>더 이상 추적 권한을 요청할 수 없습니다</strong>.</p>
<h1 id="정리">정리</h1>
<ul>
<li>추적을 허용하지 않은 사용자의 전환 실적을 측정할 수 있도록, <code class="highlighter-rouge">Info.plist</code>에 광고 네트워크별 <code class="highlighter-rouge">SkAdNetworkIdentifier</code>를 반드시 추가합니다.</li>
<li>안내 문구에 직접적인 행동을 유도하는 문구(예: ‘허용’을 누르세요)를 사용하면 안됩니다.</li>
<li>안내 화면 (Explainer screen) 및 권한 요청 다이얼로그는 <strong>단 한 번만</strong> 표시할 수 있습니다.</li>
</ul>커니iOS 14.5부터 앱 추적 투명성 (App Tracking Transparency) 프레임워크가 추가되면서, 사용자에게 명시적인 허가를 받아야만 기기의 기기의 광고 ID (IDFA; IDentifier for Advertising)에 접근할 수 있게 되었습니다.Xcode에서 ‘Unsupported OS version’ 문제 해결하기2021-04-26T00:00:00+00:002021-04-26T00:00:00+00:00https://www.androidhuman.com/xcode_unsupported_os_version<p>Xcode에서 iOS 베타 시스템 소프트웨어가 설치되어 있는 기기에 앱을 실행하려면 반드시 <strong>베타 버전의</strong> Xcode를 사용해야 합니다.</p>
<p>만약 일반(정식) 버전 Xcode에서 해당 기기에 앱을 실행하려 하면 다음과 같이 <strong>Unsupported OS version</strong>이라는 메시지가 표시되면서 앱이 실행되지 않습니다.</p>
<p>
<img src="/assets/posts/2021-04-26-xcode_unsupported_os_version/unsupported_os_version.png" alt="" class="center-image" />
</p>
<p>이를 해결하는 정석적인 방법은 다음과 같습니다.</p>
<ol>
<li>
<p>기기에 설치된 베타 버전 시스템 소프트웨어를 정식 버전으로 되돌리기</p>
<p>가장 간편하면서도 무식한(?) 방법입니다. 기기에 저장된 데이터를 그대로 유지하려면 <a href="https://support.apple.com/ko-kr/HT203282">베타 프로파일을 기기에서 삭제</a>한 후, 정식 버전이 출시되기까지 기다려야 합니다.</p>
<p>가장 최신 정식 버전으로 기기를 복원할 수도 있지만, 기기에 저장된 데이터가 유실되므로 데이터 보존이 필요하지 않은 경우에만 이 방법을 사용할 수 있습니다.</p>
</li>
<li>
<p>Xcode 베타 버전 설치하기</p>
<p>정식 버전의 Xcode와 별개로 Xcode 베타 버전을 설치할 수 있습니다. (애플 개발자 센터에서 다운로드 가능)</p>
<p>다만, Xcode 용량이 만만치 않기에 (약 20~30GB 필요) 두 버전의 Xcode를 모두 유지할 수 있는 공간을 충분히 확보해야 합니다. (저의 경우 256GB 스토리지 중에서 2~30GB정도 여유공간을 두고 쓰는지라 공간 확보가 너무 어렵더군요…)</p>
</li>
</ol>
<p>위에 소개된 방법을 사용하기 곤란한 경우, Xcode 내 <code class="highlighter-rouge">DeviceSupport</code> 폴더에 사용할 기기의 iOS 버전에 맞는 파일을 추가해 주면 Xcode 버전과 상관없이 기기를 사용할 수 있습니다.</p>
<p><a href="https://github.com/iGhibli/iOS-DeviceSupport/tree/master/DeviceSupport">이 링크</a>에 들어가면 iOS 버전별 DeviceSupport 파일이 올라와 있는데, 필요한 버전을 다운로드 하면 됩니다.</p>
<p>제가 가진 기기에는 14.5 버전이 설치되어 있었기에, <a href="https://github.com/iGhibli/iOS-DeviceSupport/blob/master/DeviceSupport/14.5(FromXcode_12.5_Release_Candidate_xip).zip">이에 맞는 파일</a>을 다운로드했습니다.</p>
<p>파일을 다운로드한 후 압축을 풉니다. 압축 파일 내부에는 OS 버전 이름으로 된 폴더(예: 14.5) 안에 <code class="highlighter-rouge">DeveloperDiskImage.dmg</code> 파일과 <code class="highlighter-rouge">DeveloperDiskImage.dmg.signature</code> 파일이 들어있습니다.</p>
<p>다음, Xcode 앱이 있는 경로 (예: <code class="highlighter-rouge">Applications</code> 내)를 파인더로 연 후, Xcode를 오른쪽 클릭하여 <strong>패키지 콘텐츠 보기 (Show Package Contents)</strong> 를 선택합니다.</p>
<p>그 다음, <strong>Contents > Developer > Platforms > iPhoneOS.platform > DeviceSupport</strong> 폴더로 이동한 후, 앞에서 압축 해제한 폴더 (예: 14.5)를 이 폴더에 복사합니다.</p>
<p>
<img src="/assets/posts/2021-04-26-xcode_unsupported_os_version/finder.png" alt="" class="center-image" />
</p>
<p>Xcode를 다시 실행한 후, 문제가 있었던 기기를 연결하면 아래와 같이 정상적으로 기기에 앱을 실행할 수 있게 된 것을 확인할 수 있습니다.</p>
<p>
<img src="/assets/posts/2021-04-26-xcode_unsupported_os_version/fixed.png" alt="" class="center-image" />
</p>커니Xcode에서 iOS 베타 시스템 소프트웨어가 설치되어 있는 기기에 앱을 실행하려면 반드시 베타 버전의 Xcode를 사용해야 합니다.