Naver Tech Concert Day-2

개요

  • 행사명 : Naver Tech Concert

  • URL : http://techcon.naver.com/

  • 일정 : DAY 1- 2018년 11월 1일(목) 10:00 - 17:30

    DAY 2- 2018년 11월 2일(금) 10:00 - 17:30

  • 장소 : 정자동 네이버 그린팩토리 본사 2층 CONNECT HALL

  • 일정 :

    • 11월 1일(목) D AY 1
      09:30 ~ 09:55   참가 등록
      10:00 ~ 10:10   인사말
      10:10 ~ 11:10   Android DataBinding (기초에서 고급까지) 
      11:10 ~ 12:10   MVVM with Grab architecture 
      12:10 ~ 13:30   점심시간 (별도 제공은 없습니다.) 
      13:30 ~ 14:30   디자인 1도 모르는 개발자, UX디자인 시작하기
      14:30 ~ 15:30   Material Design의 철학과 적용 사례
      15:30 ~ 16:30   Android Kotlin을 통한 개발 전략 
      16:30 ~ 17:30   Obfuscation 101: 난독화, 프로가드, R8, 트랜스포머 API
      17:30   마무리
  • 11월 2일(금) DAY 2
       09:30 ~ 09:55  참가 등록
       10:00 ~ 11:00  변화의 시대: 안드로이드 앱 어떻게 개발할 것인가?
       11:00 ~ 12:00  Efficient and Testable MVVM pattern(with using AAC, Rx, Koin)
       12:00 ~ 13:30  점심시간 (별도 제공은 없습니다.)
       13:30 ~ 14:30  내가 사랑한 개발자들
       14:30 ~ 15:30  안드로이드 웹뷰의 모든것 
       15:30 ~ 16:30  안드로이드에서 코루틴은 어떻게 적용할 수 있을까?
       16:30 ~ 17:30  자동화, 계륵에 살 붙이기
       17:30  마무리 

현장


전리품


01. 변화의 시대 : 안드로이드 앱 어떻게 개발할 것인가?

  • 발표자 : 신동길 (NAVER / 네이버앱개발)

  • 동영상 : https://tv.naver.com/v/4635525/list/272653

  • 슬라이드 : https://www.slideshare.net/NaverEngineering/21-121507374

  • 세션설명 : 변화의 시대 : 안드로이드 앱 어떻게 개발할 것인가? 안드로이드는 끊임없는 OS 버전 뿐만아니라 개발 언어, 구조, GUI등 많은 부분에서 다양항 변화가 시도되고 있습니다. 많은 방법론과 라이브러리가 제공되다보니 어떤 전략과 기준으로 개발해야하는지 혼돈스러울 때가 많습니다. 네이버 앱의 개편에 적용한 기술 사례와 방법론을 통해서 효율적인 앱 개발애 대해서 얘기하고자 합니다.

네이버 앱을 개편하면서 고민한 것들에 대한 이야기들.

  • 무엇이 변했는가?
    • Hardware, Platform, 프레임워크, 자바, FP vs OP
  • 앱의 구조.
    • Activity, Intent, Ui Navigation, Event Dispatching, 멀티프로세스
  • Design Architecture 적용
  • Multi Package
  • 다양한 Framework의 활용

02. Efficient and Testable MVVM pattern

Koin으로 DI를 하고 AAC, Rx를 조합한 MVVM 패턴 소개

  • Android 코드 아키텍쳐
    • why/what/how MVVM
  • Koin
  • Android MVVM, Koin, Rx 적용 예
  • Spek + LiveData를 활용한 Test
  • 기타 Tips

03. 내가 사랑했던 개발자들 : 더 나은 협업을 위한 디자이너, 기획자 이해하기

디자이너, 기획자의 일과 생각들 그리고 협업에 대해서 소개.

  • 디자이너, 기획자의 역할
    • UI, 인터렉션, BX, Graphic, UX 디자이너/기획자
  • 디자이너, 기획자의 생각
    • px에 민감한 이유, UX적 해결방법
  • 협업 및 커뮤니케이션에 대한 고민
  • 디자인에 필요한 것들

04. 안드로이드 웹뷰의 모든것

웹뷰의 역사 및 동작 원리에 대한 설명.

  • 안드로이드 웹뷰 소개 및 역사 그리고 파편화
    • 크롬과 크로미움
  • 크로미움은 어떻게 동작하는지
  • 안드로이드 크로미움은 어떻게 동작하는지
    • 웨일, 크롬과의 차이 등

05. 안드로이드에서 코루틴은 어떻게 적용할 수 있을까? : 코루틴 적용 및 ReactiveX(RxJava/RxKotlin)와 비교한다면?

코루틴 적용 및 ReactiveX(RxJava)와 비교

  • Kotlin coroutines 소개
    • Kotlin coroutines vs RxJava
  • Kotlin coroutines 기능들
  • blocking, non-blocking, CoroutineScope, GlobalScope, suspend, Job
  • Android에서의 활용
    • 클릭 이벤트 처리시 Kotlin coroutines vs RxJava

06. 자동화, 계륵에 살 붙이기 : Evolution of Android Automation Test

자동화 테스트 툴 제작 내용 및 후기.

  • UI 자동화 테스트 제작 히스토리
  • 자동화 라이브러리 소개
    • Appium 사용하여 제작한 라이브러리 사용 예제 등
  • Event Checker 소개
    • 자동화 테스트시 단말 이벤트에 대한 로그 수집용
  • Resource Monitoring 소개
    • 테스트시 리소스 상태 모니터링용


'IT > 행사' 카테고리의 다른 글

[행사] Droid Knights 2024  (0) 2024.06.26
[행사] 2019 NHN Forward  (3) 2019.11.27
[행사] Naver Tech Concert Day-1 요약  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 06  (1) 2019.01.14
[행사] Naver Tech Concert Day-2 05  (0) 2019.01.14

Naver Tech Concert Day-1

개요

  • 행사명 : Naver Tech Concert

  • URL : http://techcon.naver.com/

  • 일정 : DAY 1- 2018년 11월 1일(목) 10:00 - 17:30

    DAY 2- 2018년 11월 2일(금) 10:00 - 17:30

  • 장소 : 정자동 네이버 그린팩토리 본사 2층 CONNECT HALL

  • 일정 :

    • 11월 1일(목) D AY 1
      09:30 ~ 09:55   참가 등록
      10:00 ~ 10:10   인사말
      10:10 ~ 11:10   Android DataBinding (기초에서 고급까지) 
      11:10 ~ 12:10   MVVM with Grab architecture 
      12:10 ~ 13:30   점심시간 (별도 제공은 없습니다.) 
      13:30 ~ 14:30   디자인 1도 모르는 개발자, UX디자인 시작하기
      14:30 ~ 15:30   Material Design의 철학과 적용 사례
      15:30 ~ 16:30   Android Kotlin을 통한 개발 전략 
      16:30 ~ 17:30   Obfuscation 101: 난독화, 프로가드, R8, 트랜스포머 API
      17:30   마무리
  • 11월 2일(금) DAY 2
       09:30 ~ 09:55  참가 등록
       10:00 ~ 11:00  변화의 시대: 안드로이드 앱 어떻게 개발할 것인가?
       11:00 ~ 12:00  Efficient and Testable MVVM pattern(with using AAC, Rx, Koin)
       12:00 ~ 13:30  점심시간 (별도 제공은 없습니다.)
       13:30 ~ 14:30  내가 사랑한 개발자들
       14:30 ~ 15:30  안드로이드 웹뷰의 모든것 
       15:30 ~ 16:30  안드로이드에서 코루틴은 어떻게 적용할 수 있을까?
       16:30 ~ 17:30  자동화, 계륵에 살 붙이기
       17:30  마무리 

현장


전리품


01. 인사말


02. Android DataBinding (기초에서 고급까지)

PRISM Live Studio 앱에서 데이터바인딩을 전면적으로 사용 후 데이터바인딩의 사용방법에 대한 회고

  • 안드로이드 앱에서 DataBinding 셋팅

  • xml에서의 Setter

  • BindingAdapter

  • Observable

  • BindingMethod

  • Set Method

  • Two-way Binding

  • include와 ViewStub에서의 데이터 바인딩

  • Q & A


03. MVVM with Grab Architecture

  • 발표자 : 정승욱 (Grab / 안드로이드 개발)
  • 동영상 : https://tv.naver.com/v/4637223
  • 슬라이드 : https://www.slideshare.net/NaverEngineering/12mvvm-grab-architecture-mvvm
  • 세션설명 : 구글이 Android Architecture Component 의 ViewModel 을 발표하면서 다양한 시각의 MVVM 구현이 제시되고 있습니다. 여전히 많은 사람들이 혼동하는 MVVM 구현에 대해 올바른 안드로이드 MVVM 구현을 공유하고자 합니다. 이를 위해 안드로이드에서 어떠한 기본 작업이 선행되어야 하는지, 다양한 문제 상황에 대한 해결책에 대해 알아보고 MVVM 이 Grab 에서 어떻게 활용되고 있는지 알아보겠습니다.

안드로이드 MVVM의 올바른 구현과 그랩에서 사용하고 있는 아키텍처 소개.

  • 국내 안드로이드 MVVM의 이해의 일반적인 오해 및 올바른 이해
  • 안드로이드 MVVM에서 DataBinding 적용시 문제점들 및 그랩의 해결방안
  • 그랩의 협업을 위한 아키텍처 (MVVM + 단일 액티비티 with Node)
  • Q & A

04. 디자인 1도 모르는 개발자, UX디자인 시작하기

  • 발표자 : 최유리 (N Tech Service / NTS UX디자인실 설계&콘텐츠운영디자인)
  • 동영상 : https://tv.naver.com/v/4646101/list/272653
  • 슬라이드 : https://www.slideshare.net/NaverEngineering/13-1-ux-ux-121402323
  • 세션설명 : 통계에 따르면 2018년 1분기에는 앱스토어에 약 220만개, 구글플레이에 약 380만개의 앱이 등록되어 있다. 이 치열한 경쟁속에서 사용자에게 오래도록 사랑받는 앱이 되려면 UX디자인은 선택이 아닌 필수다. UX디자인은 디자이너만의 영역이 아닌 개발팀 모두가 함께 만들어내야하는 가치이다. UX디자인에 관심을 갖고 참여한 개발자가 서비스 성공에 어떤 역할을 해 내는지 알아보자.

개발자의 UX 디자인 참여의 필요성과 실제사례 후기

  • UX 디자인이란?
  • UX 디자인에 참여함으로 얻을 수 있는 것들
  • UX 디자인시 고려해야 하는 것들

05. Material Design의 철학과 적용 사례

구글 머티리얼 디자인의 과거와 현재

  • 머티리얼 디자인의 히스토리
  • 새로운 머티리얼 디자인
  • 머티리얼 파운데이션
  • 머티리얼 디자인 가이드 라인
  • 안드로이드에서 머티리얼 디자인 컴포넌트 사용하기

06. Android Kotlin을 통한 개발 전략

  • 발표자 : 신동길 (네이버 / 네이버앱개발)
  • 동영상 : https://tv.naver.com/v/4655590/list/272653
  • 슬라이드 : https://www.slideshare.net/NaverEngineering/15android-kotlin
  • 세션설명 : 코틀린을 안드로이드에 적용하기 위해서는 안드로이드에 맞는 모듈의 개발, 거버넌스, 코딩 패턴, 디자인 패턴 등의 전략적 고려 사항이 필요합니다. 네이버앱에 코틀린을 적용한 경험을 토대로, 기존 프로젝트(또는 신규 프로젝트)에 코틀린을 적용할 때 반드시 고려해야 할 사항과 적용하면서 나온 사례를 통해서 최적화된 적용 방법을 공유합니다.

네이버의 코틀린 개발 전략 사례

  • 네이버의 코틀린 적용 경험담
  • 자바와 코틀린의 차이. 코틀린에서도 DSL 사용시 차이.
  • 코틀린의 기술들
  • 자바를 코틀린의 변환시 참고사항
  • 코드 효율화를 위한 툴킷 정의와 활용
  • 팀 공동 개발을 위한 거버넌스
  • Q & A

07. Obfuscation 101: 난독화, 프로가드, R8, 트랜스포머 API

  • 발표자 : 김용욱 (카카오뱅크)
  • 동영상 : https://tv.naver.com/v/4655623/list/272653
  • 슬라이드 : https://www.slideshare.net/NaverEngineering/16obfuscation-101-r8-api
  • 세션설명 : 안드로이드의 앱들은 현재 프로 가드를 통해서 보호되고 있고 향후로는 구글이 작성한 R8으로 대체할 예정에 있다. 난독화 도구들을 써봤지만 막연히 쓰는 경우가 많은데 난독화 도구가 어떤 일을 하고 있고 기본적인 메커니즘이 어떻게 구현되어있는지 프로가드, R8은 무엇인지, 그리고 안드로이드 빌드 과정에 어떻게 통합되는지를 살펴보는 시간을 갖는다.

안드로이드 코드/컴파일러 특징 및 빌드 과정

  • 안드로이드 코드의 특징
  • 자바와 달빅 바이트 코드 소개
  • Transform API 소개
  • 동적 컴파일러 동작 소개
  • 난독화 및 앱 보호에 대한 이론
  • ProGuard와 R8의 역할


'IT > 행사' 카테고리의 다른 글

[행사] 2019 NHN Forward  (3) 2019.11.27
[행사] Naver Tech Concert Day-2 요약  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 06  (1) 2019.01.14
[행사] Naver Tech Concert Day-2 05  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 04  (0) 2019.01.14

Naver Tech Concert Day-2

06. 자동화, 계륵에 살 붙이기 : Evolution of Android Automation Test

  1. UI Automation Test
  2. Automation Library
  3. Android Event Checker
  4. Resource Monitoring

UI Automation Test

사용자의 테스트 동작 중 화면상의 마우스 동작, 키보드 입력 등을 자동화 수행이 가능한 script 형태로 변환하여 이후 동일한 형태로 Replay 함으로써 Regression Test 를 지원하는 테스트 자동화 방식

  • 단순하고 반복적인 테스트
  • 유지보수 기간 길어질수록 Test Case도 많아짐
  • Side-effect 확인을 위한 회귀 테스트 (작은 수정에도 전체 확인이 가능)

N Tech Service에서의 히스토리

  • Calabash, ADB, Sikuri, Appium, BDD 방식을 활용한 자동화 사례
  • Uiautomator Stub 활용한 멀티 디바이스 제어 자동화 사례
  • WebKuli(NTS 자체 제작) 적용사례

자동화 결과는 좋지 않았음.

이슈를 실제 검출하는 경우도 적고 프로젝트 유지가 오래된 케이스도 저었음.

  • 기대에 미치치 못하는 Test Coverage
  • Tool 이해도 부족
  • 자동화 구현 및 유지보수 공수 상승 (리소스 절감을 위해 도입했다 더 리소스가 투입된 경우도 있음)
  • 정보 공유 부족

Automation Library

표준화 및 규칙화를 위해 라이브러리 제작.

제작 당시 안드로이드 프레임워크 비교 후 Appium을 선택함.

public class TestClass {
    public AppiumDriver<WebElement> driver;
    
    @BeforeClass
    public void setup() throws Exceptiom {
        // 플랫폼, 단말기 정보,테스트 타켓 패키지 정보
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setCapability(MobileCapabilityType.PLATFORM_NAME, "Android");
        capabilities.setCapability(MobileCapabilityType.UDID, "UDID");
        capabilities.setCapability(MobileCapabilityType.PLATFORM_VERSION, "platform-version");
        capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UIAutomator2");
        capabilities.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "package");
        capabilities.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, "launchable-activity");
        
        // 안드로이드 드라이버 형성
        driver = new AppiumDriver<WebElement>(new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
        driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS);
    }
    
    @Test
    public void scenario_01() {
        // 동작 제어
        driver.findElement(By.id("tv_home_weekly_schedule_title")).click();
    }
    
    @Test
    public void scenario_02() {
        // 동작 제어
        driver.findElement(By.xpaht("//*[@text='요일별 연재']")).isDisplayed();
    }
    
    @AfterClass
    public void quit() {
        driver.quit();
    }
}

라이브러리 제작 전 고려사항

  • 자동화 테스트 환경 Setting (환경 일원화)
  • 자동화 구현 패턴 표준화 (스크립트 작성에 대한 표준화)
  • Appium Client API의 사용성 (가독성 높이고 타이밍 이슈에 대한 부분 커스텀마이징 추가)
  • 객관적 지표 산출

구성

  • Appium Server Builder
  • Appium Client Utilities
  • Report
public class TestClass extends Formatter {
    public AndroidUtil android;
    
    @BeforeClass
    public void setup() {
        android = (AndroidUtil) new Automation()
            .android()
            .mobileApp()
            .apkFile("apkPath")
            .packageName("Package_Name")
            .activityName("Activity_Name")
            .logLevel("loglevel")
            .start());            
    }
    
    @Test
    public void scenario_01() {
        android.click(By.id("tv_home_weekly_schedule_title"));
    }
    
    @Test
    public void scenario_02() {
        android.isElelmentPresent(By.xpath("//*[@text='요일별 연재']"));
    }
    
    @AfterClass
    public void quit() {
        android.quit();
    }
}

위처럼 커스텀하여 사용하며 운영도 실제 진행함.

UI 자동화 테스트 자체적으로 발생하는 이슈들 생김

  • UI Assertion 대한 신뢰성
    • 음악 스트리밍 앱에서 재생 중인지?
    • Audio Focus는 가져왔는지?
  • 자동화 시나리오 진행 중 안정성 문제
    • 자동화 테스트 중 Interrupt (SMS, Call 등)
    • 알 수 없는 간헐적 Fail Result

Android Event Checker

Event Checker

  • Log 출력 방식
  • Monitoring Start/Stop
  • Event Log

구조

  • Service
  • Command
  • Broadcast Receiver
  • Sensor : 기울기 센서를 이용하여 orientation 전환 확인
  • Audio Focus, Audio Manager : Audio Focus 선점 및 Audio 재생 확인
  • Notification Listener : Application Notification 정보 수집

약 30개 Event, 80개 Log

위와 같이 UI만 확인하던 UI 테스트에 기능적인 부분도 추가함.

Resource Monitoring

  • 동일 스텝
  • 동일 시간
  • 반복적

rMon Mobile Version

  • Battery 소모량
  • CPU 사용률
  • Memory 사용량
  • 발생 Traffic

rMon을 이용하여 앱을 켜둔 상태에서 테스트하며 성능 리포트를 남기게 함.

(Android 5.1.1 이후 사용 불가. rMon이 CPU나 Memory를 앱의 pid에 접근해야해서 불가됨.)

rMon PC Version

Android 5.1.1 이후 대응을 위해 PC 버전으로 adb 기반으로 제작.

  • ADB를 활용한 Resource 측정 방식
  • Android Application의 소모 CPU, Memory, Traffic 측정
  • Android 단말기 내부에 rMon 명령어 추가
  • ADB > rMon 명령어 호출하여 리소스 데이터 수집

Android Automation Test

  • GitLab으로 Automation Test Project 관리
  • CI 서버를 통해 Daily Build 수행 (서버 이슈, 컨텐츠 이슈, 로그인/댓글 같은 부분 배포로 인한 이슈 확인 용. 출근 후 확인)

Maver Repository -> Coding -> Code Respository -> CI 서버 -> Target Device -> Result Report

nMobile

https://solution.navercorp.com/nmobile/

모바일 기기 원격 제어 솔루션

  • 화면 제어, 디버깅 등
  • Real Time에 가까운 응답속도
  • 실제 단말과 동일한 사용감 제공

nMobile 자동화 연동시 장점

  • 가상 단말 아닌 리얼 디바이스 대상 수행 가능
  • 다양한 단말 보유 (버전 별, 제조사 별, 국가 별)
  • ADB 연결 가능 / 다양한 API 지원
  • 물리적인 연결 없이 접근 가능 / 사용 용이성

PC Browser & iOS

nMobile Android 단말과 실제 iOS 단말, PC 크롬에서 채팅 네이버 카페 채팅방 테스트 가능

  • Step1 nMobile Android 단말기 실행 및 연결
  • Step2 nMobile Android 단말에서 Cafe 앱 실행
  • Step3 실제 iOS 단말에서 Cafe 앱 실행
  • Step4 PC에서 Chrome 실행 및 Cafe 홈페이지 진입
  • Step5 PC웹 및 Android/iOS 앱에서 테스트 대상 카페 진입
  • Step6 PC웹 및 Android/iOS 앱에서 테스트 대상 카페 채팅방 진입
  • Step7 각 플랫폼에서 채팅 입력 후 다른 플랫폼에서 전송된 채팅 확인 및 답장
  • Step8 연결 종료

Result

  • Library를 이용한 안정적인 자동화 프로젝트 생성
  • 외부 인터럽트 및 기능 Assertion에 대한 단점 보완
  • 자동화 특성 반영 Resource Monitoring

Q & A

Q : 네이버는 자동화 테스트와 QA 인력들의 수동 테스트 둘다 진행하는지?

A : 둘 다 진행함. 자동화 테스트를 한다고 비용이 절감되지 않고 증대되는 듯. 사람과 자동화와 서로 교차 테스트 됨으로 해결되는 부분들이 잇음.

Q : 라이브러리 제작 시간은?

A : 2016년도 제작하면서 각자 QA 담당하면서 시간 쪼개서 만든 것임. 제작에 전적으로 시간 투자하면 빨리 가능할 듯.

Q : Appium이 iOS도 지원하지만 앱 내부 구조가 다를텐데 따로 스크립트를 작성하는지?

A : 동일한 기능에도 따로 작성함. iOS는 아직은 자동화 테스트 구현에 어려움이 있음.

Q : 자동화된 UI 테스트가 라이브 서비스의 패포 파이프라인에 포함이 되어 테스트 통과가 되어야 배포가 되는것인지?

A : 그렇지 않음. 개발자들이 유닛 테스트들을 별도로 따로 작성 후 확인하고, 자동화 테스트는 QA에서만 사용.

Q : nMobile은 리얼 디바이스처럼 와이파이 환경일때나 모바일 네트워크 환경일 때, 또는 유심이 들어있는 상황에서만 할 수 있는 상황도 연출이 가능한지?

A : 그런 상황들도 테스트 가능함.

Q : CI/CD는 어떻게 하는지?

A : CI 서버는 따로 두고 젠킨스로 있음.

Q : 자동화 테스트 툴을 오픈할 계획이 있는지?

A : 아직 오픈되진 않았음.

Q : 테스트 시나리오 작성시 사용자 행위로 서버에 변경되어 저장될 수 있는 케이스는 어떻게 처리하는지?

A : 서버까지는 확인이 불가하며, 노출되는 텍스트 등으로 확인 정도까지만.

Q : QA에 개발 인력도 소속되어 별도의 자동화를 위한 개발이 이루어지는 것인지?

A : 따로 개발인력이 있지 않음. 몇몇의 QA 인력들이 모여 만듬.

Q : 실제 기능 구현 개발자는 자동화 테스트 코드를 작성하지 않는지?

A : 직접 작성하는 개발자도 있지만 많이는 없는 듯. 시간이 많이 들기 때문에.

Q : 리소스가 부족한 소규모 사업장, 1인 개발자 들이 UI 자동화 테스트에 접근하려 할 때 좋은 방법은?

A : Appium을 직접 커스텀하여 만들었지만 이를 커스텀화한 서비스들이 이미 있음. 그걸 이용하면 좋을 듯.

Q : Jira 같은 서비스를 이용해서 각각의 이슈를 만드는지? 별도의 QA 시트를 만들어서 빨리 전달하는지?

A : 지라를 이용하여 이슈 공유함. 깃헙을 사용하기도 함. 개발자와 협의해서 사용함. 별도의 QA 시트도 작성하고 테스트케이스도 작성하여 진척도를 메일로 공유하는 등 같이 사용함.

Q : 국가 변경 등 보통 개발자 옵션 페이지 등으로 접근하는 경우도 범용적으로 사용 가능한 노하우가 있는지?

A : 개발서버에서 테스트하기 때문에 특정 언어를 바꾸거나, nMobile에서 제공하는 다른나라 단말 등을 사용함.

Naver Tech Concert Day-2

05. 안드로이드에서 코루틴은 어떻게 적용할 수 있을까? : 코루틴 적용 및 ReactiveX(RxJava/RxKotlin)와 비교한다면?

  • 발표자 : 권태환 (요기요 / 안드로이드 개발)

  • 동영상 : https://tv.naver.com/v/4635469/list/272653

  • 슬라이드 : https://www.slideshare.net/NaverEngineering/25-121499000

  • 세션설명 : 1.3에 정식으로 포함될 코루틴! 안드로이드에서 코루틴의 적용은 어떻게 할 수 있으며, ReactiveX(RxJava/RxKotlin)과 비교 한다면 좋은점과 부족한 점, 그리고 실무 프로젝트에 적용한 코루틴을 소개해본다.

  • 목차 :

    1. 코루틴 소개(서브루틴과 코루틴)
    
    2. 코루틴을 통해 할 수 있는것?
    
    3. 코틀린 코루틴에서 제공하는 주요 기능들
    
    4. 실제 업무에 적용한 코루틴 소개
    
    5. 코루틴 써보니? RxJava와 비교
    

코루틴 적용 및 ReactiveX(RxJava)와 비교

Kotlin coroutines

coroutines이란?

Subroutine의 사전적 의미 : 평소 사용하는 함수, 메서드 등.

subroutine의 return이 불러지기 전까진 다음 라인을 실행하지 않음.

private fun MutableList<Int>.sum(): Int = this.sumBy{ it }  // sum이 끝나야 return

@Test
fun test() {
    val sum = (0..10).toMutableList().sum()
    println(sum)	// sum이 끝나야 println
}

Coroutines : 1958년 어셈블리에서 언급되었고, 1963년 논문을 통해 설명됨.

코틀린 뿐만 아니라 파이썬, C#에서도 지원함.

  • Entry point 여러 개 허용하는 subroutine (쉽게 쓰레드라 생각하면 됨.)
  • 언제든 일시 정지하고 다시 실행 가능
  • event loops, iterators, 무한 리스트, 파이프 같은 것을 구현하는데 적합

private suspend fun Int.countDown(currentIndex: Int) {
    for(index in this downTo 1) { //countdown from 10 to 1
        tv_message.test = "Now index $currentIndex Countdown $index"	// update text
        delay(200)
    }
    Log.i("TEMP", "Now index $currentIndex Done!")
}

var currentIndex = 0
fab.onClick {
    CoroutineScope(Dispatchers.Main).launch {
        10.countDown(++currentIndex)
    }
}

suspend라는 키워드를 넣어야 코루틴으로 동작되고 delay(기존 Thread.sleep과는 다름)를 사용함.

코루틴에서는 CPU와 관계를 최소한으로 줄여 루틴마다 스위치가 일어날 때 CPU 영향을 적게 받음.

여러 쓰레드에서 하던 부분을 단일 쓰레드에서 처리할 수 있게 해줌.

Dispatchers.Main 지정으로 여러번 click해도 메인 쓰레드에서 동작함.

Kotlin coroutines vs RxJava

Android onClick - RxJava vs coroutines

kotlin coroutines

var currentIndex = 0
fab.onClick {
    CoroutineScope(Dispatchers.Default).launch {	// 새로운 scope를 생성하고 default로 launch (일반 Work thread)
        val job = launch(Dispatchers.Main) {		// launch를 Main thread로 변경
            10.countDown(++currentIndex)
        }
        job.join()							// join()으로 UI thread 종료하기 전까지 대기
    }
}
private suspend fun Int.countDown(currentIndex: Int) {		// 상위 scope thread에 따름(여기선 UI)
    for(index in this downTo 1) { //countdown from 10 to 1
        tv_message.test = "Now index $currentIndex Countdown $index"	// update text
        delay(200)
    }
    Log.i("TEMP", "Now index $currentIndex Done!")
}

RxJava

private val clickEventSubject = PublishSubject.create<View>()	// onClick 처리를 위한 Subject 생성
private var currentIndex = 0
fab.setOnClickListener {
    clickEventSubject.onNext(it)		// 클릭이 이루어지면 onNext를 호출하며 View를 넘겨줌.
}
clickEventSubject
.throttleFirst(500, TimeUnit.MILLISECONDS)	// 첫 번째만 처리하기 위한 throttleFirst (500ms 이후에 동작하게 함.)
.observeOn(Schedulers.io())					// 스케쥴러를 가지고 io 쓰레드에서 동작하게 함.
.map {
    currentIndex++							// index 증가.
}
.switchMap {									// switch에 2개의 zip을 묶음.
    Observable.zip(Observable.range(0, 10),		// 1 부터 10 출력을 위한 range
                  Observable.interval(200, TimeUnit.MILLISECONDS),	// 200ms interval
                   BiFunction<Int, Long, Int> { range, _ ->
                       10 - range
                   })
}
.observeOn(AndroidSchedulers.mainThread())		// UI thread로 변경하고, 메시지 노출
.subscribe({
    tv_message.text = "Now index $currentIndex Countdown $index"
}, {})

Subject, ObserveOn 등등 10 몇가지를 알아야 이해를 할 수 있음.

Rx는 observeOn 등 몇가지 지정을 하고 subject를 통해야 실행을 함.

Coroutines은 CoroutineScope를 지정하고 launch를 하면 실행이 됨.

suspend는 RxJava 처럼 subscribe가 올 때 처리하고 싶을때 선언하고 사용하면 되고, CoroutineScope 안에서만 호출이 가능함.

RxJava

  • 장점
    • Observable과 Streams
    • 기존 Thread보다 간단한 코드로 처리 (어디선가 callback이 올거라 보고 처리해야 함.)
    • stream을 통해 데이터 처리 용이
    • Thread간 교체가 간단
    • RxJava를 활용한 수 많은 라이브러리 활용 가능 (Retrofit 등등)
      • 예제도 많고 문서도 잘 되어 있음.
  • 단점
    • 용어를 모르면 코드 활용 이유를 알 수 없음
    • 처음 학습 비용이 높음

Kotlin coroutines

  • 장점
    • 함수 형태라 읽기 쉬움
    • light-weight threads
    • 모든 routine 동작을 개발자가 처리 가능
    • 처음 학습 비용이 낮음
  • 단점
    • 아직은 필요한 라이브러리를 구현해서 사용해야 함. (RxView와 같은 라이브러리 개발 필요)
      • 직접 만들 수 있고, 문서도 잘 되어 있음.

Kotlin도 Coroutines 처럼 처음은 쉽지만 그 다음부터 어려워짐.

Kotlin coroutines의 장단점

  • 장점
    • 함수 형태라 읽기 쉬움
    • light-weight threads
    • 모든 routine 동작을 개발자가 처리 가능
  • 단점
    • 아직은 필요한 라이브러리를 구현해서 사용해야 함. (RxView와 같은 라이브러리 개발 필요)

Kotlin coroutines 이 제공하는 것들

Kotlin 1.3과 함께 coroutines 1.0 정식 릴리즈됨.

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'

주의할 점 : 코루틴 0.25버전에서부터 deprecated 되었던 부분들이 1.0에서 다 삭제됨. 릴리즈 문서 참고.

  • Kotlin 다양한 platform 제공

    • Server-side

    • Desktop

    • Mobile Application

  • 다양한 언어에서 제공하던 주요 라이브러리 제공

    • C#, ECMAScript : async/await
    • Go : channels, select
    • C#, Python : generators/yield

Kotlin coroutines guide

Coroutines : https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html

Android Coroutines - codelab : https://codelabs.developers.google.com/codelabs/kotlin-coroutines/index.html

blocking과 no-blocking

@Test
fun test() {
    GlobalScope.launch {
        delay(300L)			// non-blocking
        println("World!")
    }
    pirntln("Hello,")
    Thread.sleep(500L)		// block main thread. UI 쓰레드에서 동작이라 화면이 멈춤
}
  • Thread.sleep() : Thread 처리 전까지 Main Thread가 멈춤. UI 쓰레드에선 최대한 안써야 함.
  • delay : 별도의 coroutine에서 동작하여 멈추지 않고 넘어감.

그래서 runBlocking을 제공함. (아래 두가지 코드는 동일하게 동작함.)

@Test
fun test() {
    CoroutineScope(Dispatchers.Unconfined).launch {
        delay(300L)
        println("World!")
    }
    pirntln("Hello,")
    runBlocking {
        delay(500L)
    }
}
@Test
fun test() = runBlocking {
    CoroutineScope(Dispatchers.Unconfined).launch {
        delay(300L)
        println("World!")
    }
    pirntln("Hello,")    
    delay(500L)
}

runBlocking

  • runBlocking 은 함수의 처리가 끝날때까지 대기
  • delay()를 걸어두면 delay 시간만큼 대기하고 return
    • Android에서 runBlocking을 UI에서 잘못 사용하면 멈추는 현상 발생
  • delay를 이용해 non-blocking을 할 수 있음
  • runBlocking, CoroutineScope, GlobalScope 안에서 동작해야 함

CoroutineScope, GlobalScope

CoroutineScope

  • 가장 기본적인 Scope
  • Thread 형태를 지정(Main, Default, IO 등을 지정)
    • CoroutineScope(Main, Default, IO, ...)
  • launch, async 등을 통해 scope를 실행
  • 중요한 점
    • Activity/Fragment LifeCycle에 따라야 함
      • onDestroy() : cancel하도록 코드 추가
    • CoroutineScope(/* thread type */).launch { } 로 실행
    • launch { }의 return job의 동작을 지정 가능
      • join() : scope 동작이 끝날때까지 대기하며, suspend에서 호출 가능
      • cancel() : 동작을 종료하도록 호출
      • start() : scope가 아직 시작하지 않을 경우 start, scope의 상태를 확인

CoroutineScope의 interface 정의

@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if (context[Job] != null) context else context + Job())


public interface CoroutineScope {
    @Deprecated(level = DeprecationLevel.HIDDEN, message = "Deprecated in favor of top-level extension property")
    public val isActive: Boolean
    	get() = coroutineContext[Job]?.isActive ?: true
    
    /** Context of this scope. */
    public val coroutineContext: CoroutineContext
}

CoroutineScope 활용 - Delay 대신 job.join()

@Test
fun test() = runBlocking {
    val job = CoroutineScope(Dispatchers.Unconfined).launch {	// 새로운 scope를 생성하고 default로 launch. (Work Thread로 동작함)
        //launch에서 CoroutinScope에서 지정한 default Thread로 사용. (별도 지정 없으면 부모를 따라감)
        delay(300L)
        println("World!")
    }
    pirntln("Hello,")
    // delay(500L) <-- 이 부분은 CoroutineScope에서 네트워크 같은 처리시 의미가 없어짐.
    job.join()		// join()으로 default thread 종료하기 전까지 대기
}

GlobalScope

전역에서 돌아가야할 경우

  • CoroutineScope 상속 받아 구현

  • Demon, Application 등에서 사용

  • Application의 lifetime에 따라 동작하는 scope에서 사용 추천

  • GlobalScope는 Dispatchers.Unconfirned(worker thread) 에서 동작

  • GlobalScope.launch(/* thread type */) { } 로 실행.

    위에서 thread가 이미 지정되어 있어 launch에서 쓰레드 타입을 지정. 기본은 Default임.

GlobalScope API 코드

object GlobalScope : CoroutineScope {
    /**
     * @suppress **Deprecated**: Deprecated in favor of top-level extension property
     */
    @Deprecated(level = DeprecationLevel.HIDDEN, 
                message = "Deprecated in favor of top-level extension property") 
    override val isActive: Boolean
    	get() = true
    
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
    	get() = EmptyCoroutineContext
}

GlobalScope 사용 예

fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = 
GlobalScope.produce(Dispatchers.Unconfined) {
    for(number in this@sqrt) {
        send(Math.sqrt(number.toDouble()))
    }
}

suspend

  • suspend를 활용하여 함수 분할을 할 수 있음.
  • suspend로 만들어진 함수는 사용하기 전까지는 동작하지 않음.
  • suspend 키워드를 사용하는 함수는 CoroutineScope에서만 사용할 수 있음.

suspend 사용 예

suspend fun CorountineScope.loadData(body: suspend CoroutineScope.(item: String) -> Unit) {
    val item = ""
    delay(100L)
    body(item)
}
CoroutineScope(Dispatchers.Main).launch {
    loadData { item ->
        // Coroutine scope 정의
    }
}

Job

  • CoroutineScope의 return에는 job 객체를 넘겨줌.
  • job을 통해 routine의 취소, 실행, 종료를 대기할 수 있음
    • job.cancel() : 종료하도록 유도함.
    • job.join() : 종료를 대기하며 호출은 suspend에서 가능함.
    • job.start() : coroutine의 시작을 확인할 수 잇으며, 시작 상태라면 true

Android onClick 잘 활용하기

연속해서 빠르게 입력하는 경우를 막을 경우

RxJava 버튼 처리

  • 첫 번째 이벤트만 허용하기 위해서

    • throttleFirst 활용

      • 시간으로 first의 시간을 지정하여 문제 발생 외 처리 필요.

        (문제점 : 특정 시간 후에 다음 이벤트가 발생함)

Coroutine으로 처리

  • 첫 번째 이벤트만 허용하기 위해서
    • 버튼의 상태를 변경해서 처리?
  • coroutine에서는 그럴 필요 없음.
  • GlobalScope + actor 활용

Coroutine GlobalScope.actor<T> 활용하기

private fun View.onClick(action: suspend (View) -> Unit) {
    // 5. 이 때 Higher-Order function 정의는 suspend가 포함되어야 함.
    val event = GlobalScope.actor<View>(Dipatchers.Main) { // 1. Singletone의 GlobalScope 활용
        for(event in channel) {						// 2. actor 이용 event 받기
        	action(event) // 4. 받은 event를 Higher-Order function으로 넘겨서 정의하도록 함.
        }
    }
    setOnClickListener {
        event.offer(it)								// 3. actor에 offer로 event 보내기
    }
}
var currentIndex = 0
fab.onClick {
    10.countDown(currentIndex++)				// 6. 람다 표현으로 countDonw 구현
}

Android Coroutines

안드로이드에서 코루틴 활용

  • UnitTest/Main Thread에서 활용하기 쉽게 Dispatchers 하나로 활용
    • UnitTest에서 Main thread를 활용할 수 없기에 기본으로 처리 할 수 있도록 작성
  • CoroutineScope를 Base에 작성하여 release를 쉽게 하도록 처리
  • onClick에 coroutine 활용을 위한 GlobalScope 적용

Dispatchers 정의(UnitTest/Default Thread)

sealed class DispatchersProviderSealed {
    open val main: CoroutineContext by lazy { Dispatchers.Main }
    open val default: CoroutineContext by lazy { Dispatchers.default }
}

/**
 * 기타 Thread를 위한 Dispatchers 정의
 */
object DispatchersProvider: DispatchersProviderSealed()

/**
 * Unit Test를 위한 Dispatchers 정의
 */
// Rx 기준으로 볼때 테스트시 전부 백그라운드 쓰레드로 돌려야할 경우
object TestDispatchersProvider: DispatchersProviderSealed() {
    override val main: CoroutineContext = Dispatchers.Unconfined
    override val default: CoroutineContext = Dispatchers.Unconfined
}

CoroutineScope를 상속받아 구현

abstract class CoroutineScopeActivity: AppCompatActivity(), CoroutineScope {
    private val job: Job = Job()	// Job을 미리 생성하여 CoroutineContext에 미리 지정할 수 있음
    
    override val coroutineContext: CoroutineContext	// Activity에서 사용할 기본 Context를 정의
    	get() = Dispatchers.Main + job
    
    override fun onDestroy() {
        super.onDestroy()
        job.cancel()				// onDestroy()에서 job을 종료하도록 함
    }
}
  • CoroutineScope를 상속받아 구현하면 기본 CoroutineScope로 정의되어 있음.
    • launch, actor<E> 를 사용하면 코드 간결 및 자동으로 종료 처리해 줌.
    • Default 상위 Activity에서 정의한 CoroutineScope의 Thread를 활용함.
    • 필요시 launch, actor에서 Thread를 언제든 변경 가능함.
  • 다양한 CoroutineScope 구현
    • ViewModel
    • LifeCycleObservable
    • Fragment

CoroutineScope 상속받은 Activity에서 routine 사용하기

abstract class CoroutineScopeActivity: CoroutineScopeActivity() {
    launch {
        // UI Thread에서 처리
    }
    launch(Dispatchers.Default) {
        // Default Thread에서 처리
    }
    actor<Generic Type> {
        // UI Thread에서 event 처리
        for(event in channel) action(event)
    }
    actor<Generic Type>(Dispatchers.Default) {
        // Default Thread에서 처리
        for(event in channel) action(event)
    }
}

onClick 처리

  • Higher-Order function + kotlin Extensions을 활용.
  • GlobalScope을 활용하거나, CoroutineScope을 활용 가능.
    • 동작 범위에 따라서 GlobalScope, CoroutineScope을 선택함이 좋음.

onClick 만들기, background에서 처리하고, UI에 던져주기

class CoroutinesSendChannelOnClickEvent<E>(
    private val view: View,							// View: click을 위한 View
    private val bgBody: suspend (item: View) -> E,	// background : Higher-Order Function
    private val dispatcherProvider: DispatchersProviderSealed = DispatchersProvider,	// Provider : 지정
    private val job: Job? = null) {					// Job : 종료 처리를 위한 job 추가
    
    fun consumeEach(uiBody: (item: E) -> Unit): CoroutinesSendChannelOnClickEvent<E> {
        val clickActor = CoroutineScope(dispatcherProvider.main + (job ?: EmptyCoroutineContext)).actor<View> {
            this.channel.map(context = dispatcherProvider.default, transform = bgBody).consumeEach(uiBody)
        }
        view.setOnClickListener { clickActor.offer(it) }	// Offer 처리를 위한 CoroutineScope 생성
        return this
    }
}
fun <E> View.onClick(dispatcherProvider: DispatchersProviderSealed = DispatchersProvider,
                     job: Job? = null, bgBody: suspend (item: View) -> E): CoroutinesSendChannelOnClickEvent<E> =
CoroutinesSendChannelOnClickEvent(this, bgBody, dispatcherProvider, job)

infix fun <E> CoroutinesSendChannelOnClickEvent<E>.consume(uiBody: (item: E) -> Unit) {	// 생성을 간단하게 하기 위한 function 2개
    this.consumeEach(uiBody)
}
fab.onClick(job = job) {		// click을 처리하고, background에서 loadnetwork()
    loadNetwork()
} consume {
    tv_message.text = it
}

private suspend fun loadNetwork(): String {		// Temp load network
    delay(300)
    return "currentIndex ${currentIndex++}"
}

Retrofit2 kotlin coroutines adapter

  • JakeWharton 배포

  • Dependency 추가

    • implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2'
  • Deferred<E>를 활용하여 return을 처리할 수 있음.

    interface RetrofitService {
        @GET("/posts")
        fun getPosts(): Deferred<Response<List<Post>>>
    }
    // 초기화시키는 코드
    object RetrofitFactory {
        const val BASE_URL = “https://url"
        
        fun makeRetrofitService(): RetrofitService {
            return Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addCallAdapterFactory(CoroutineCallAdapterFactory())
            .build().create(RetrofitService::class.java)
        } 
    }
    // 사용하는 코드
    launch {
        val request = service.getPosts() 
        val response = request.await() 
        if (response.isSuccessful) { 
            // Pass in the response.body() to your adapter 
        } else {
            toast("Error ${response.code()}") 
        }
    }

Q & A

Q : Dispatcher.Unconfined는 부모 코루틴 컨텍스트에서 코루틴을 실행하고 제일 처음 suspend 함수 이후에는 해당 suspend에서 사용했던 쓰레드에서 동작하는 걸로 알고 있음. 예제에서 Unconfined를 쓰셨던데 안드로이드에서 활용 가능한 부분이 있는지?

A : 테스트 코드에서 활용하기 위해 넣은 거임. UI Thread(Main Thread)에서 활용하기 위해 넣어야 함.

Q : Rx를 안쓰고 Coroutine 기반 라이브러리들로 코드 베이스를 변화시키거나 변화시킬 계획인지?

A : Coroutine을 조금씩 적용할 예정이며 Retrofit을 사용하기 위해 새로 팔 예정임. onClick에 먼저 적용 중.

Q : 코루틴이 rx java의 operator 기능들의 유연함을 못따라간다고 들었는데 어떤지?

A : 유연함을 못따라가는 것은 맞음. 아직 라이브러리들이 없음. 기본적인 틀만 제공 중인 상태.

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-1 요약  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 06  (1) 2019.01.14
[행사] Naver Tech Concert Day-2 04  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 03  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 02  (0) 2019.01.14

Naver Tech Concert Day-2

04. 안드로이드 웹뷰의 모든것

  • 발표자 : 이형욱 (NAVER / Whale Core)

  • 동영상 : https://tv.naver.com/v/4635576/list/272653

  • 슬라이드 : https://www.slideshare.net/NaverEngineering/24-121486425

  • 세션설명 : 안드로이드에서 웹컨텐츠를 렌더링 하는데 많이 사용하고 있는 웹뷰의 동작 원리 대한 설명과 이를 바탕으로 한 웹 성능 최적화를 위한 여러가지 툴 사용법을 설명하는 세션입니다.

  • 목차 :

    1. History of Android WebView
    
    2. Summary of how browsers work
    
    3. Rendering pipeline overview
    
    4. How chromium makes a frame?
    
    5. VSync aligned touch input & frame output
    

1. Android WebView overview

1.1 안드로이드 웹뷰란?

안드로이드에서 제공하는 뷰

사용하는 케이스

  • 안드로이드 기본 브라우저에서 사용
  • 앱에서 거의 대부분의 배너 광고에서 사용
  • 웹(HTML) 기반 앱에서 사용
  • 안드로이드 네이티브 뷰와 함께 사용

혹자는 안드로이드에 숨어있는 제 3의 플랫폼이다 라고도 함.

1.2 안드로이드 웹뷰의 역사

J(젤리빈 / 4.1 / API 16~18) 이하

custom WebKit-based "classic" WebView

K(킷캣 / 4.4 / API 19)

Chromium 30/33-based WebView

L(롤리팝 / 5.0 / 21) 이상

Unbundled Evergreen WebView

1.3 안드로이드 웹뷰와 파편화

  • 안드로이드 제조사에서 경쟁적으로 WebKit을 Customization

  • 안드로이드 버전별 서로 다른 WebKit 사용

    • Android 4.3 이전 버전은 WebKit 기반의 렌더링 엔진을 사용
    • Android 4.4 부터는 Blink 기반의 렌더링 엔진으로 교체

  • 현재는 사용자 웹뷰 업데이트 유무에 따라 서로 다른 버전을 사용 (앱 업데이트 없이도 이슈 생길 수 있음)

1.4 WebKit 브라우저 엔진의 역사

KHTML, KJS(1999.05 ~)

Apple Forks KHTML, KJS in 2001 -> WebKit (2001 ~), Open Source in 2005

Google Forks WebKit in 2013 -> Blink (2013.04 ~)

Webkit을 2008년 크롬 브라우저에서 사용하면서부터 유명해짐.

구글에서 개발인력이 더 늘면서 Blink로 포크 뜨게 됨.

1.5 안드로이드 웹뷰의 구조

Blink 엔진의 구조도

크로미움에서 브라우저 프레임워크 및 컴포넌트를 제공하며, 이를 브라우저 앱들이 사용함.

1.6 크롬과 크로미움의 차이점

크로미움 : 오픈소스

크롬 : 크로미움으로 상품화, 유료 비디오 코덱, 어도비 플래시, QA

2. How chromium works

2.1 브라우저는 어떻게 동작하는가?

크로미움은 브라우저 엔진으로, HTML을 처리하여 화면에 렌더링 역할.

HTML, CSS, JS를 입력으로 받아 파싱하여 렌더링함.

2.2 HTML Parser

<html>
   <head>
       <title>NAVER</title>
    </head>
    <body>
        <div>
            <h1>Hello</h1>
            <p>World</p>
        </div>
    </body>
</html>

DOM : Document Object Model

  • Docuement = HTML, well-formed XML
  • Object Model = Data + Method

DOM Tree로 만들어 놓으면 가공하기가 쉬워짐.

2.3 CSS Parser

body { font-size:16px }
p { font-weight: bold }
span { color:red }
p span { display:none }
img { float:right }

CSSOM : CSS Object Model

  • DOM 과 비슷하게 CSS도 CSSOM이 있음
  • CSS는 HTML Element의 스타일을 정의
  • 외부 링크로 정의된 경우 렌더링이 블로킹 됨
  • Cascade down 개념을 구현하기 위해 트리 구조 (상속개념)

2.4 Java Script Engine

구글에서 포크 뜨면서 자바스크립트 엔진을 다르게 썼었음.

  • 웹킷 : JSC (JavaScript Core)
  • 구글 : V8

두 엔진이 한동안 다르게 동작하였으나, 현재는 위 그림대로 동작하게 됨.

2.5 Render Tree

Rescalculate Style의 결과

  • Render Tree = DOM Tree + CSSOM Tree
  • DOM Tree와 Render Tree는 1:1 관계가 아님
  • 화면에 보이는 요소들을 중심으로 정리 (HEAD나 아래의 p span 등은 안보이는 요소라 트리에 없음)
body { font-size:16px }
p { font-weight: bold }
span { color:red }
p span { display:none }
img { float:right }

2.6 Layout

렌더링 전에 레이아웃팅(좌표 계산)을 거쳐야 함.

Layout 알고리즘

  • 각 박스의 넓이는 viewport (ICB) 기준
  • 각 박스의 높이는 contents (fonts)를 기준
  • 윈도우 사이즈를 변경하거나 폰트 변경시 Global Layout
  • Dirty bit system으로 incremental layout

2.7 Paint

결정된 레이아웃팅 결과를 가지고 페인팅.

브라우저의 페인팅은 프린트와 유사함. 굉장히 오래 걸리고 로드가 많이 걸리는 잡임.

2.8 VSync 기반 멀티 쓰레드 렌더링

브라우저도 안드로이드와 마찬가지로 VSync 기반으로 렌더링함. (16.6ms 내에 한 프레임을 찍어야 함)

현실은 요새 같이 웹이 표현할게 많고 고도화된 상태에서 60FPS 한 프레임에 표현하기 어려워짐.

크로미움쪽은 Compositor Thread를 도입함. (빠르게 스크롤시 흰 화면이 보이는 경우가 해당됨)

Raster Thred도 도입됨.

기존에 메인 스레드에서만 할 수 있는 Paint에서 Draw를 직접 안하고 Recording만 하고,

Raster에서 Bitmap만드는 과정을 분리하여 처리함.

안드로이드에서 2D 그래픽스를 담당하는 오픈소스.

크로미움에서도 사용 중.

3. How chromium powered WebView works

3.1 안드로이드에서 크로미움은 어떻게 동작하는가?

크로미움 브라우저는 멀티프로세서 기반의 브라우저.

UI 담당하는 프로세스와 렌더링를 담당하는 프로세스, GPU를 담당하는 프로세스 등 4가지 프로세스 구성

  • Browser 프로세스 : 크롬 액티비티 등이 동작하는 앱 프로세스.
  • Renderer 프로세스 : 안드로이드 서비스를 활용한 UI가 없는 프로세스. 렌더링이나 로케이팅 등 처리.
    • 보안에 취약하기 때문에 샌드박싱 되어 있음. (메모리와 CPU 리소스만 사용함)
  • GPU 프로세스 : CPU 자원을 접근해야 해서 샌드박스가 안되어 있음.
  • OS Winodw system : 안드로이드 SufaceView를 사용해 브라우저 화면을 렌더링함.

3.2 웨일 브라우저의 뷰 구조

SurfaceView만 사용하는 것은 아님.

3.3 크로미움 웹뷰의 구조적 차이점

Graphics components
  • Whale (Chrome)

    • SufaceView 사용 : 렌더링 성능을 위함. 자체적인 렌더링 싸이클을 가짐. (안드로이드 뷰보다 빠름)
    • Vsync based rendering
    • Async uploads using EGLImage and glTexSubImage2D() (OpenGL 함수들 사용)
  • Chromium powered webview

    • "Draw functor" : inject draw calls into system GL context

      • 안드로이드에서 하드웨어 엑셀레이션 동작시 HWPUI가 동작함. 안드로이드 뷰를 호출해 렌더링함.

        이를 Draw functor에 위임하게 됨.

    • Android based rendering

    • Private API. The WebView injects a callback onto the display list. (일반적인 웹뷰에선 못 씀.)

Architecture
  • Whale (Chrome)

    (Multi-process)

    • UI thread
    • GPU Process
    • Texture upload thread
    • Per renderer process :
      • Blink thread
      • Compositor thread
      • Raster thread
  • Chromium powered webview

    (Single-process/Multi-process)

    • Combined UI + Compositor thread
    • Android RenderThread (+ in-porcess GPU thread)
    • Canvas/WebGL GPU thread
    • Blink thread
    • Raster thread

큰 차이는 Compositor thread가 Whale(Chrome)의 경우 독립적이나 Chromium의 경우 UI thread와 합쳐짐.

(Chromium의 경우 View이기 때문에)

3.4 안드로이드 렌더링 파이프 라인 (KitKat 4.4 이하)

KitKat까지는 메인 쓰레드에서 렌더링함.

메인 쓰레드에선 보통 I/O를 담당해서 빠르게 처리하게 하는데, 렌더링도 메인 쓰레드에서 했지만 메인 쓰레드에서 할 일이 너무 많아짐.

3.5 크로미움 웹뷰 렌더링 파이프 라인 (KitKat 4.4 이하)

onDraw시 Private API인 DrawFunctor를 통해 함수 포인트를 연결해 하드웨어 캔버스가 호출됨.

그리고 콜백이 호출되어 Record, Raster, Composite 들이 돌게 됨.

KitKat까지는 Composite를 메인 쓰레드에서 하느라 좋은 구조는 아니었음.

3.6 안드로이드 렌더링 파이프 라인 (Lollipop 5.0 이상)

렌더 쓰레드가 도입됨. (크로미움의 Raster 쓰레드와 비슷함.)

크로미움과 안드로이드의 기술 발전 흐름은 유사함. (크로미움에서 괜찮으면 안드로이드에도 적용되는 느낌)

3.7 크로미움 웹뷰 렌더링 파이프 라인 (Lollipop 5.0 이상)

onDraw 때 Record와 Raster 쓰레드만 돌고 메인 쓰레드는 거의 안돌게 됨.

Render 쓰레드에서 플레이백하여 Composite하는 구조가 됨.

메인 쓰레드가 많이 Free해짐. 성능 좋아짐.

이 때부터 unbundled임. (AOSP에 없음. 크로미움 참조해야 함.)

쓰레드가 많아져서 쓰레드간 동기나 제어가 중요함.

크로미움엔 스케쥴러 모듈이 있어 메인 쓰레드에서 제어 및 스케쥴링을 하게 됨.

크로미움 웹뷰의 완성도가 많이 올라갔고, HTML5도 많이 지원하게 됨.

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-2 06  (1) 2019.01.14
[행사] Naver Tech Concert Day-2 05  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 03  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 02  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 01  (0) 2019.01.14

Naver Tech Concert Day-2

03. 내가 사랑했던 개발자들 : 더 나은 협업을 위한 디자이너, 기획자 이해하기

  • 발표자 : 유두선 (NAVER / 파파고 UX)

  • 동영상 : https://tv.naver.com/v/4635562/list/272653

  • 슬라이드 : https://www.slideshare.net/NaverEngineering/23-121486418

  • 세션설명 : 디자이너와 기획자는 어떤 일과 생각들을 하는지 소개합니다.

  • 목차 :

    1. 뭐하는 사람들인가
      - UI / 인터렉션 / BX 디자이너의 일
      - UX / 기획자의 일
    
    2. 그들의 생각
      - 디자이너 : 왜 1px을 옮겨달라고 할까
      - 기획자 : UX적으로(??) 해결해달라구요?!
    
    3. 어떻게 일하면 좋을까
      - 상세 설계 공유방식에 대한 고민
      - 커뮤니케이션
    
    4. 혹시 디자인에 관심이 있으시다면
    
    5. 내가 사랑했던 개발자들
    

1. 뭐하는 사람들인가

(* 주관적인 견해임)

개발자 : FE, BE, Markup, JAVA, Swift, React, ...

디자이너 : UI, UX, BX, 영상, 인터렉션, Graphic, 원화, 배경, 3D, ...

UI(User Interface) 디자이너

사용자 인터페이스를 디자인하는 사람

  • 툴 : 스케치, 제플린, 포토샵
  • 결과물 : UI 디자인 및 가이드

인터렉션(Interaction) 디자이너

화면 간 트랜지션 또는 서비스 UI에 필요한 애니메이션/인터렉션 디자인하는 사람

  • 툴 : 에펙, 프리미어, 프레이머, 포토샵, 스케치
  • 결과물 : 인터렉션 가이드, 애니메이션 json

BX 디자이너

브랜드 아이덴티티 및 전략 디자인.

브랜드 커뮤니케이션(주로 마케팅) 관련된 디자인하는 사람.

  • 툴 : 일러스트, 포토샵, 인디자인
  • 결과물 : BI, 브랜드 커뮤니케이션 관련 온오프라인 디자인

Graphic 디자이너

서비스 아이콘이나 일러스트와 같은 그래픽 요소를 그리는 사람

  • 툴 : 포토샵, 일러스트
  • 결과물 : 아이콘, 일러스트 등 다양한 그래픽 요소

UX 디자이너 / 기획자

리서치 : FGI, UT, 페르소나, Contextual inquiry 등등

설계 : IA, 상세 설계서

운영 : 고객문의대응, 서비스 운영, 제휴...

2. 그들의 생각

디자이너들이 1px을 옮겨달라고 하는 이유

  • 사용성(가독성)
    • 각도, 행 간격 : 시선의 흐름 디자인하여 가독성/사용성에 영향을 줌
  • 완성도 : 작은 디테일이 쌓여 완성됨

UX적으로 UX에서 해결해주세요?

UX는 문제를 해결해주는 간달프의 마법이 아님.

  • What : 문제 정의 (우리가 풀어야 하는 문제는 무엇인가)
  • How : 해결방법 찾기 (그 문제를 어떻게 풀 것인가)
    • UX 디자이너가 결국 해결책을 가지고 제시해야 하나 기술적인 부분 등에 대해서는 혼자 풀 문제가 아님.

3. 어떻게 일하면 좋을까?

상세 설계 공유방식에 대한 고민

PPT

처음 만들때는 어떻게든 만들지만...

  • 수정하는게 너무 큰 일
  • 의견 공유/취합 어려움
  • 급할때 1쪽 설계 난무 (문서 파편화)

스케치

그래서 스케치로 도입해서 사용해 봄

  • 장점
    • 심볼로 해서 이미지 수정은 좀 쉬워짐
    • 제플린으로 코멘트도 달 수 있음.
  • 단점
    • 모두가 스케치가 익숙하지는 않음
    • 텍스트 검색 불편

GitHub

그래서 스케치는 포기하고 GitHub을 사용함.

issue 간의 링크가 편해서 각각의 객체화가 가능함.

업데이트 방식은 스펙 v1.1 -> 의견 -> 스펙v1.2 식에서 스크롤이 길어져 merge 형식으로 바꿈

  • 장점
    • 문서 파편화 해결
    • 검색 용이해짐
    • 히스토리 파악 용이
    • 수정 편함
    • 누구든 쉽게 사용
  • 단점
    • 마크다운 가독성

3. 어떻게 일하면 좋을까

커뮤니케이션

기획자에게 안되는 이유에 대해서 개발자가 설명해주면 다음에 참고하게 됨.

기획자의 일이 시각적인 부분이 많아 의견을 자유롭게 내기 쉬움.

의견 제시는 좋지만 해결 방법에 대해서는 직접 제시보다는 맡기기.

좋은 관계가 제일임.

4. 혹시 디자인에 관심이 있으시다면..

눈이 손보다 중요함.

디자인은 만족하면 끝남.

참고할 사이트들

볼 때 생각할 점

  • 왜 좋은지 생각해보기
  • 왜 이렇게 디자인했는지 생각해보기
  • 따라 만들기

5. 내가 사랑했던 개발자들

개발자들과 친해지면서 개발과 관련된 부분들도 일부 볼 수 있게 됨.

잘 지냈으면 함.

Q & A

Q : 디자이너들이 아이폰식 디자인을 안드로이드에 적용하려는 이유는?

A : 디자이너들이 주로 아이폰을 많이 쓰다보니 익숙한 부분을 따라감. 머티리얼 디자인 등을 공부하지만 더 노력해야 할 부분임.

Q : 디자이너에게 Github 가 쉽지 않을텐데 도입 방법은?

A : 처음에 코드 메뉴 등은 어려워 이슈만 사용함. 거기에서 커뮤케이션 툴처럼 사용하면서 시작함.

Q : 타켓 유저가 원하는 디자인 취향은?

A : 리서치에 의해 타켓 유저의 취향이 정해졌다면 그대로 가야 함. 특정 결정권자의 의견보다 디자이너의 의견이 맞다고 봄.

Q : UX 전문가의 경우 어플리케이션의 사용성 및 요소 배치에 특히 집중하는 케이스를 본 적이 있는데 이런 부분을 개선하는 것이 UX 전문가의 주된 롤인지?

A : 당연히 중요한 문제이고 기획자도 중요하게 생각하는 롤임.

Q : 기획/디자이너/개발이 나뉘어져 있는 조직에서 서로 융화되기 위해 노력할 만한 것들은?

A : 같이 일하는 사람들과 밥과 커피로 친해졌음.

Q : 같이 일하기 싫은 개발자는?

A : 그냥 나쁜 사람. (ex. 화내는 사람들?)

Q : UX 리서치를 사업적인 지표와 직접적으로 연결시켜 해석할 수 있는 부분이 있을까?

A : 사용자의 만족도나 사용성 평가를 통해 도달시간/체류시간 등을 통해 지표로 연결할 수 있음.

Q : 현재 스토어에 있는 앱 중에 디자인적으로 좋은 앱 하나만 추천해준다면?

A : 파파고 (웃음). 상도 받았음.

Q : 수동적인 사람들을 설득할 방법은?

A : 인간적으로 친해지기 전까지 높게 벽을 치는 사람도 있기 때문에 일단 친해지는 게 우선될 수도 있지만, 결국 안되는 경우도 있었음.

Q : 디자이너가 PPT나 메일로 보고 공유해주는 형태에서 깃헙을 사용하면 좀더 효율적일지?

A : 스케치나 제플린을 사용하면 됨. 깃헙으로 커뮤니케이션을 하면 편했음.

Q : 이상적인 팀 구성에서 UX 디자이너의 업무 및 권한 수준은?

A : 팀마다 상황이 다르지만 UX가 포괄하는 의미가 너무 많음. 상황에 맞춰서 하면 됨.

Q : UX 디자이너들이 일에 관여하는 사람(데이터, PM, 엔지니어)이 많아 힘들어할 경우 최종 협의를 거쳐서 산출물을 내는데까지 비용이 너무 큰데 노하우가 있는지?

A : 의사결정 단계가 너무 많으면 힘들긴 하며, 개인이 조정하긴 어렵고 조직의 변화가 필요할 듯.

Q : 벤치마킹, 리서치 하다보면 창작이 아닌 카피의 경계가 무너지기도 할텐데 그럴때 윤리의식은 어떻게 관리하는지?

A : 사람마다 생각하는 정도가 있기에 디자이너 스스로 본인이 지켜나가야 함.

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-2 05  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 04  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 02  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 01  (0) 2019.01.14
[행사] Microsoft Azure Everywhere  (0) 2019.01.11

Naver Tech Concert Day-2

02. Efficient and Testable MVVM pattern

  • 발표자 : 김범준 (레이니스트 / 안드로이드 개발)

  • 동영상 : https://tv.naver.com/v/4635548/list/272653

  • 슬라이드 : https://www.slideshare.net/NaverEngineering/22efficient-and-testable-mvvm-pattern

  • 세션설명 : Koin으로 DI를 하고 AAC, Rx를 조합한 MVVM 패턴에 대하여 이야기 하고자 합니다. 어째서 효율적인지 Testable한지를 함께 고민 해 보고 더 나은 구조를 향한 이야기를 나누어 보았으면 합니다.

  • 목차 :

    1. 발표 동기
    
    2. AAC를 소개(나온 배경등)
    
    3. MVVM을 소개(livedata,ViewModel과 엮으며)
    
    4. Rx를 소개 (KickStarter를 참고하여)
    
    5. Koin을 소개(DI를 간단하게 설명하며)
    
    6. 위의 모든 것을 조합 한 예제 설명
    
    7. 효율적인 부분 고민이 되는 지점등 같이 이야기해볼만 한 부분들을 화두로 던져 소통
    
    8. 테스트코드 또한 위와같이 진행
    
    9. 마무리.
    

Android 코드 아키텍쳐

  • MVC
  • MVP
  • MVVM
    • with Clean Architecture
    • with RFP(rxJava2)
    • with AAC, Koin
    • with Spek
  • MVI
  • VIPER
  • etc...

MVVM

with CleanArchitecture

with RFP (rxJava2)

with AAC, Koin

with Spek

with에 적힌 라이브러리, 프레임워크는 간단히 설명하기로 함.

why MVVM?

MVP로 충분히 잘 구현해도 서비스 운영을 하면서 아래의 문제들이 생김.

요구사항, 비즈니스 로직 늘어남 -> 코드량, 복잡성 높아짐 -> 유지보수성, 테스트 용이성 하락

what MVVM?

(MVVM에 대한 개인적인 견해이므로 다른 의견이 있을 수도 있음.)

  • ModelViewViewModel로 구성되어 있는 패턴
  • ViewModel은 View의 추상화
  • View와 ViewModel은 n:m 의 관계
  • View는 ViewModel에 bindable함

ViewModel은 View의 추상화

  • ViewModel은 View의 상태와 행동이 추상화 된 것
  • ViewModel은 View의 input과 output이 명시되어 있는 인터페이스 (같은 input 엔 항상 같은 output 이라 테스트에 용이함)
  • output은 View의 상태와 Rooute로 나뉨

View와 ViewModel은 n:m 의 관계

  • 하나의 View가 여러 ViewModel에 조합 가능
  • 하나의 ViewModel이 여러 View에 적용 가능
  • 재 사용성이 용이함.

View는 ViewModel에 bindable함

  • 사용자 행동에 의해 입력 받았을 때 (View -> ViewModel)
  • 사용자 행동에 따른 View의 상태를 변경시켜야 할 때 (ViewModel -> View)
  • 모든 로직은 binding되는 시점에 결정됨.

장점

View에 대한 의존성이 제거되어 효과적으로 역할과 책임을 나눌 수 있음.

ViewModel 단독으로 테스트가 가능함으로 테스트 용이성 증가.

binding 되는 시점에 input 대비 output을 산출하는 로직이 정해지기 때문에 개발자가 상태 관리해야 하는 위험 줄여줌.

Databinding을 통해 보일러 플레이트 코드 줄일 수 있음.

How MVVM?

발표자가 생각한 android에서 MVVM

LiveData는 라이프싸이클 처리 등의 문제로 사용.

ViewModel 윗단 Model단부터는 Clean Architecher가 도입함.

Rx로 비동기 처리함.

Activity/Fragment/something은 View의 상태 변화를 제외한 Router역할+ViewModel과 View를 binding하는 역할 수행함.

추가적으로

  • Clean Architecture 지향
  • Koin을 사용하여 IOC(Inversion Of Control) 구현
  • Spek을 사용하여 행동 주도 결과 테스트 작성

예제 코드 : https://github.com/omjoonkim/GitHubBrowserApp

  • Github 이름을 입력하여 repository 리스트를 보여주는 테스트 앱

package 구조

Clean Architecture 스럽게

  • app
  • data
  • domain
  • remote

원래 UI와 Presentation 레이어가 나뉘어서 의존성을 주입해야 하지만 AAC의 ViewModel을 사용함으로 Android Framework에 의존성이 생길 수 밖에 없어서 App은 두 레이어에 걸치게 됨.

의존성을 바깥에서 주입한다는 것은 (의존성의 역전)

  • Domain은 Data가 어떤 코드인지 모름, Data는 Remote가 어떤 코드인지 모름
  • Domain은 Presentation이 어떤 코드인지 모름, Presentation은 UI가 어떤 코드인지 모름

Koin

Koin?

  • 제어의 역전을 구현할 수 있게 도와주는 Library. (DI가 아님)

  • Kotlin으로 구현되어 있음.

  • 간편한 사용 방법. (AAC도 지원)

  • 제어의 역전을 Service Locator 방식으로 구현함. (Runtime에서 에러 확인 가능)

    (Dagger같은 경우는 컴파일 시점에 에러 확인 가능)

app_module.kt

val myModule: Module = module {
    viewModel { (id: String) -> MainViewModel(id, get(), get()) }
    viewModel { SearchViewModel(get()) }

    //app
    single { Logger() }
    single { AppSchedulerProvider() as SchedulersProvider }

    //domain
    single { GetUserData(get(), get()) }

    //data
    single { GithubBrowserDataSource(get()) as GitHubBrowserRepository }

    //remote
    single { GithubBrowserRemoteImpl(get(), get(), get()) as GithubBrowserRemote }
    single { RepoEntityMapper() }
    single { UserEntityMapper() }
    single {
        GithubBrowserServiceFactory.makeGithubBrowserService(
            BuildConfig.DEBUG,
            "https://api.github.com"
        )
    }
}

App.kt

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        startKoin(
            this,
            listOf(myModule)
        )
    }
}

검색화면의 경우

  • SearchView의 Input : name, clickSearchButton
  • SearchView의 Output (STATE) : enableSearchButton
  • SearchView의 Output (ROUTER) : goResultActivity

SearchView

    <data>
        <import type="android.view.View"/>
        <variable
            name="viewModel"
            type="com.omjoonkim.app.githubBrowserApp.viewmodel.SearchViewModel" />
    </data>
...
        <EditText
            android:id="@+id/editText"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:onTextChanged='@{(s,start,end,before) -> viewModel.input.name(s.toString ?? "")}'
            />
        <Button
            android:id="@+id/button_search"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:enabled="@{viewModel.output.state().enableSearchButton}"
            android:onClick="@{(v) -> viewModel.input.clickSearchButton()}"
            android:text="search"
            />

SearchViewModel

class SearchViewModel(logger: Logger) : BaseViewModel() {    
    private val name = PublishSubject.create<String>()
    private val clickSearchButton = PublishSubject.create<Parameter>()
    val input = object : SearchViewModelInPuts {
        override fun name(name: String) =
            this@SearchViewModel.name.onNext(name)
        override fun clickSearchButton() =
            this@SearchViewModel.clickSearchButton.onNext(Parameter.CLICK)
    }
    private val state = MutableLiveData<SearchViewState>()
    private val goResultActivity = MutableLiveData<String>()
    val output = object : SearchViewModelOutPuts {
        override fun state() = state
        override fun goResultActivity() = goResultActivity
    }

    init {
        compositeDisposable.addAll(
            name.map { SearchViewState(it.isNotEmpty()) }
                .subscribe(state::setValue, logger::d),
            name.takeWhen(clickSearchButton) { _, t2 -> t2 }
                .subscribe(goResultActivity::setValue, logger::d)
        )
    }    
}

interface SearchViewModelInPuts : Input {
    fun name(name: String)
    fun clickSearchButton()
}
interface SearchViewModelOutPuts : Output {
    fun state(): LiveData<SearchViewState>
    fun goResultActivity(): LiveData<String>
}
data class SearchViewState(
    val enableSearchButton: Boolean
)
import androidx.lifecycle.ViewModel
...
abstract class BaseViewModel : ViewModel(){
    protected val compositeDisposable : CompositeDisposable = CompositeDisposable()
    override fun onCleared() {
        super.onCleared()
        compositeDisposable.clear()
    }
}

SearchActivity

class SearchActivity : BaseActivity() {
    private val keyboardController by lazy { getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivitySearchBinding>(this, R.layout.activity_search)
        binding.setLifecycleOwner(this)
        actionbarInit(binding.toolbar, isEnableNavi = false)

        val viewModel = getViewModel<SearchViewModel>()
        binding.viewModel = viewModel

        viewModel.output.goResultActivity()
            .observe {                keyboardController.hideSoftInputFromWindow(binding.editText.windowToken, 0)
                startActivity(
                    Intent(
                        Intent.ACTION_VIEW,
                        Uri.parse("githubbrowser://repos/$it")
                    )
                )
            }
    }
}

결과화면의 경우

  • ResultView의 Input : clickHomeButton, clickUser
  • ResultView의 Output(STATE) : title, showLoading
  • ResultView의 Output(ROUTER) : refreshListData, finish, showErrorToast, goProfileActivity

MainViewModel

class MainViewModel(
    searchedUserName: String,
    private val getUserData: GetUserData,
    logger: Logger
) : BaseViewModel() {
    private val clickUser = PublishSubject.create<User>()
    private val clickHomeButton = PublishSubject.create<Parameter>()
    val input: MainViewModelInputs = object : MainViewModelInputs {
        override fun clickUser(user: User) = clickUser.onNext(user)
        override fun clickHomeButton() = clickHomeButton.onNext(Parameter.CLICK)
    }

    private val state = MutableLiveData<MainViewState>()
    private val refreshListData = MutableLiveData<Pair<User, List<Repo>>>()
    private val showErrorToast = MutableLiveData<String>()
    private val goProfileActivity = MutableLiveData<String>()
    private val finish = MutableLiveData<Unit>()
    val output = object : MainViewModelOutPuts {
        override fun state() = state
        override fun refreshListData() = refreshListData
        override fun showErrorToast() = showErrorToast
        override fun goProfileActivity() = goProfileActivity
        override fun finish() = finish
    }

    init {
        val error = PublishSubject.create<Throwable>()
        val userName = Observable.just(searchedUserName).share()
        val requestListData = userName.flatMapMaybe {
            getUserData.get(it).neverError(error)
        }.share()
        compositeDisposable.addAll(
            Observables
                .combineLatest(
                    Observable.merge(
                        requestListData.map { false },
                        error.map { false }
                    ).startWith(true),
                    userName,
                    ::MainViewState
                ).subscribe(state::setValue, logger::d),
            requestListData.subscribe(refreshListData::setValue, logger::d),
            error.map {
                if (it is Error)
                    it.errorText
                else UnExpected.errorText
            }.subscribe(showErrorToast::setValue, logger::d),
            clickUser.map { it.name }.subscribe(goProfileActivity::setValue, logger::d),
            clickHomeButton.subscribe(finish::call, logger::d)
        )
    }
}

interface MainViewModelInputs : Input {
    fun clickUser(user: User)
    fun clickHomeButton()
}
interface MainViewModelOutPuts : Output {
    fun state(): LiveData<MainViewState>
    fun refreshListData(): LiveData<Pair<User, List<Repo>>>
    fun showErrorToast(): LiveData<String>
    fun goProfileActivity(): LiveData<String>
    fun finish(): LiveData<Unit>
}
data class MainViewState(
    val showLoading: Boolean,
    val title: String
)

MainView

    <data>
        <import type="android.view.View"/>
        <variable
            name="viewModel"
            type="com.omjoonkim.app.githubBrowserApp.viewmodel.MainViewModel" />
    </data>
...
            <androidx.appcompat.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:title="@{viewModel.output.state().title}" />
...
        <FrameLayout
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:visibility="@{viewModel.output.state().showLoading ? View.VISIBLE : View.GONE}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/appBar" >
            <ProgressBar
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center" />
        </FrameLayout>

MainActivity

...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        binding.setLifecycleOwner(this)

        val viewModel = getViewModel<MainViewModel> {
            parametersOf(intent.data.path.substring(1))
        }
        binding.viewModel = viewModel

        actionbarInit(binding.toolbar, onClickHomeButton = {
            viewModel.input.clickHomeButton()
        })

        with(viewModel.output) {
            refreshListData().observe { (user, repos) ->
                binding.recyclerView.adapter = MainListAdapter(
                    user,
                    repos,
                    viewModel.input::clickUser
                )
            }
            showErrorToast().observe { showToast(it) }
            goProfileActivity().observe {
                startActivity(
                    Intent(
                        Intent.ACTION_VIEW,
                        Uri.parse("githubbrowser://repos/$it")
                    )
                )
            }
            finish().observe {
                onBackPressed()
            }
        }
    }
...

Test

사전준비

  • SchedulersProvider 생성

    TestSchedulersProvier

    class TestSchedulerProvider : SchedulersProvider {
        override fun io() = Schedulers.trampoline()
    
        override fun ui() = Schedulers.trampoline()
    }
  • DummyApiService 생성

    TestDummyGithubBrowserService

    class TestDummyGithubBrowserService : GithubBrowserService {
        override fun getUserInfo(userName: String): Single<UserModel> =
            Single.just(
                UserModel("omjoonkim", "")
            )
        override fun getUserRepos(userName: String): Single<List<RepoModel>> =
            Single.just(
                listOf(
                    RepoModel("repo1", "repo1 description", "1"),
                    RepoModel("repo2", "repo2 description", "2"),
                    RepoModel("repo3", "repo3 description", "3")
                )
            )
    }
  • Spek + LiveData를 같이 테스트하기 위한 코드 작성

    (JUnit이 불가해 동작을 위한 코드 필요함)

        beforeEachTest {
            ArchTaskExecutor.getInstance().setDelegate(object : TaskExecutor() {
                override fun executeOnDiskIO(runnable: Runnable) {
                    runnable.run()
                }
                override fun isMainThread(): Boolean {
                    return true
                }
                override fun postToMainThread(runnable: Runnable) {
                    runnable.run()
                }
            })
        }
        afterEachTest {
            ArchTaskExecutor.getInstance().setDelegate(null)
        }

    MainViewModelSpec

    object MainViewModelSpec : KoinSpek({
        beforeEachTest { ... }
        afterEachTest { ... }
    
        lateinit var userName: String
        val viewModel: MainViewModel by inject { parametersOf(userName) }
        val getUserData: GetUserData by inject()
    
        Feature("MainViewModel spec") {
            Scenario("유저가 화면에 들어오면 검색한 유저의 프로필,저장소 데이터가 정상적으로 보여야 한다") {
                Given("검색하려는 유저의 이름은 omjoonkim이다"){
                    userName = "omjoonkim"
                }
                Then("화면에 검색한 유저의 데이터가 정상적으로 나타난다") {
                    assertEquals(
                        getUserData.get(userName).blockingGet(),
                        viewModel.output.refreshListData().value
                    )
                }
            }
            Scenario("유저 프로필을 클릭하면 유저의 프로필 화면으로 이동되어야 한다") {
                When("프로필을 클릭 했을 때") {
                    viewModel.input.clickUser(
                        viewModel.output.refreshListData().value?.first
                            ?: throw IllegalStateException()
                    )
                }
                Then("해당 유저의 프로필 화면으로 이동 된다") {
                    assertEquals(
                        viewModel.output.refreshListData().value?.first?.name
                            ?: throw IllegalStateException(),
                        viewModel.output.goProfileActivity().value
                    )
                }
            }
            Scenario("홈버튼을 클릭하면 화면이 정상적으로 종료되어야 한다.") {
                When("홈버튼을 클릭 했을 때") {
                    viewModel.input.clickHomeButton()
                }
                Then("화면이 정상적으로 종료 된다.") {
                    assertEquals(
                        Unit,
                        viewModel.output.finish().value
                    )
                }
            }
        }
    })

  • DI for Test

    test_modules

    val testModule = module {
        single(override = true) {
            TestSchedulerProvider() as SchedulersProvider
        }
        single(override = true) {
            TestDummyGithubBrowserService() as GithubBrowserService
        }
    }
    
    val test_module = listOf(myModule, testModule)

Spek을 이용한 테스트 코드 작성

  • Feature
  • Scenario
  • Given
  • When
  • Then

More... + TMI

Dagger2 vs Koin

  • Heavy vs light
  • Dependency Injection vs ServiceLocator
  • CompileTime vs RunTime

Spek과 Koin의 호환성

  • Spek + Koin을 사용하려면 추가적으로 작업해야 하는 코드들 있음.

    (Spek 구동방식과 Koin을 사용하는 방식이 서로 충돌되기 때문)

개선의 여지 + 아쉬운 점

  • Databinding이 kotlin에 100% 호환되지 않음 (람다 함수를 xml에서 값으로 지정해줄 수 없음)
  • Router
  • Presentation module 분리

Q & A

Q : MVVM 적용시 피곤한 점은?

A : 기존 뱅크샐러드의 경우 MVP 베이스임. 그래서 MVP에 Koin 추가나 코드 수정해야 했음.

​ 팀원 협의에 대한 부분. Rx나 MVVM 러닝커브가 높은 점.

Q : 클린 아키텍쳐면 presentation, domain, entity, data 4가지 레이어 정의를 보면 presentation이 ui에 해당되는데 왜 presentation에 data와 같은 레벨로 하고 ui는 remote로 한건지?

A : ui가 remote라기 보다는 ui와 remote는 서버에서 데이터를 받아오는가장 바깥 레이어로 생각하면 됨.

Q : Koin의 runtime error가 compile time error 대비 장점은?

A : Dagger는 적용하기 어려웠음. 선수지식과 많은 코드가 필요했음. Compile error시 어디서 에러가 나는지 찾아야 했음.

​ Koin을 하면서 코드 스타일로 의존성 주입을 하면서 에러 찾기가 편함.

​ 복잡한 서비스의 경우는 Dagger, 심플한 경우 Koin이 괜찮아 보였음.

Q : TDD가 아닌 BDD를 한 이유는?

A : TDD와 BDD의 가장 큰 차이는 구현 대신 행동 즉 행위를 테스트하는 것임. Functional Programming 관점에서 행동 기반이 맞다고 생각했음.

Q : Model의 수에 따라 Mapper의 수도 지속적으로 증가하는데, Mapper가 특별한 경우가 아닐 땐 1:1로 매핑되고 Koin으로 바인딩 해주는 것들이 보일러 플레이트로 느껴짐. 이런 코드를 좀 더 줄일만한 아이디어는?

A : 진정한 클린 아키텍쳐라면 레이어 별로 테스트가 가능해야 하고, Mapper도 테스트되어야 함. 똑같은 input이 들어갈 경우 똑같은 entity를 내보내주는 부분이 확인 되어야 함. 그것 때문에 인터페이스를 만들고 개선의 여지가 있다고 봄. 모델에 단순히 컨버팅해주는 코드들이 많아 필요 없다거나 확장함수로 처리하는 경우도 있지만 제대로 하겠다면 매퍼를 만드는게 필요하다 봄.

Q : 개발 일정을 단축시킨다거나 안드로이드 인력을 줄였다거나 하는 경우는?

A : input과 output을 먼저 정의하고 ViewModel을 View를 짜고 그 다음에 binding 코드를 짜는 방식이 사고의 흐름에 맞게 개발한다고 봄. 그래서 빠르게 개발한다고 보지만, 선수지식과 경험이 필요한 부분이라 경우에 따라 다를 것으로 봄.

Q : input & output 등을 인터페이스로 구현하고 ViewState 모델링한 부분은 MVVM과 별개로 적용한 것인지?

A : 고민했던 부분. output 자체를 state로 나누는 것과 아예 router명을 명시해서 나누는 것도 고민했음. 개념적으로 같고 코드상의 차이로 보고, 모든 결과에 state라는 것과 그 외에는 router 코드로 판단하기로 함. MVVM과 별개로 봐도 될 것으로 봄.

완벽한 아키텍처, 완벽한 패턴 그리고 정답은 없다고 봄.

각각의 프레임워크, 플랫폼, 서비스 그리고 어떤 사람들이 어떤 컨벤션을 가지고 개발하는지에 따라 달라진다고 봄.

어떻게 할지에 고민보다는 왜 이렇게 하는지에 대한 이유만 명확하게 생각하고 논의하면 될 것으로 봄.

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-2 04  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 03  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 01  (0) 2019.01.14
[행사] Microsoft Azure Everywhere  (0) 2019.01.11
[행사] Naver Tech Concert Day-1 06  (0) 2019.01.07

Naver Tech Concert Day-2

01. 변화의 시대 : 안드로이드 앱 어떻게 개발할 것인가?

  • 발표자 : 신동길 (NAVER / 네이버앱개발)

  • 동영상 : https://tv.naver.com/v/4635525/list/272653

  • 슬라이드 : https://www.slideshare.net/NaverEngineering/21-121507374

  • 세션설명 : 변화의 시대 : 안드로이드 앱 어떻게 개발할 것인가? 안드로이드는 끊임없는 OS 버전 뿐만아니라 개발 언어, 구조, GUI등 많은 부분에서 다양항 변화가 시도되고 있습니다. 많은 방법론과 라이브러리가 제공되다보니 어떤 전략과 기준으로 개발해야하는지 혼돈스러울 때가 많습니다. 네이버 앱의 개편에 적용한 기술 사례와 방법론을 통해서 효율적인 앱 개발애 대해서 얘기하고자 합니다.

  • 목차 :

    1. 안드로이드 앱의 구조
    
    2. 함수형과 객체형: 함수형과 객체형 개발 어떻게 적용할 것이가?
    
    3. GUI 기반 구조: Activity 구조,Fragment 구조, View 기반의 구조의 장단점은 무엇인가? 어떤 구조를 택할 것인가?
    
    4. 데이터 모델: 데이터 모델이 필요한가? MVC, MVP, MVVM 이 정말 필요한가?
    
    5. 프로세스 와 쓰레드 어떤 모델로 가져갈 것인가? : Service, AsyncTask, Thread, JobManager, WorkManager, Coroutine ,Loader 너무 많은데
    무엇이 정답인가? 멀티 프로세스 모델 어떻게 설계할 것인가?
    
    6. 통계와 설정: 어떤 정보를 모으고 최적할 것인가?
    

네이버 앱을 개편하면서 베타 버전까지 냈는데, 이 때까지 고민한 것들에 대한 이야기임.

(네이버에서도 베타 중에 기능 추가 요청이 들어오는 경우가 있음.)

https://play.google.com/store/apps/details?id=com.navercorp.techcon&hl=en_US

네이버 테크 콘서트 앱은 머티리얼 디자인만 적용하여 7시간 개발한 앱임.

(다른 프레임워크는 전혀 사용하지 않음)

앱번들, 사이닝 등록과정까지 10시간 정도 걸림.

1. 무엇이 변했는가?

Hardware

  • Multi-Core
  • Large Memory
  • Big Display

Platform

  • Dalvik -> JIT/ART
  • Many Strictions

다양한 프레임워크

  • Lottie
  • RxJava
  • Retrofit
  • Glide, Picasso, OkHttp

좋은 앱이란

  1. Big Sized
  2. Mulit-Core Processor
  3. Mulit-Featured
  4. Multi-Media

단말이 바뀌고 좋아지다 보니 보다 많은 기능을 더 빠르게 개발할 것을 요구함.

애니메이션도 많이 붙게 되고, 오히려 느려지는 현상도 있음.

환경에 효율적이어야 함. (네이버 앱도 전면 개편 필요....)

네이버 앱은 웹뷰 사용하지 않고 웹 엔진을 사용함. (웹뷰 문제와 웹뷰의 수정 지연 때문에)

크로스워크 XWalk를 사용하고 직접 수정 및 미지원 기능 등은 기본 웹뷰를 사용함.

웹뷰에서 불가한 low 레벨 통계도 사용함.

자바의 문제점

  1. 상속과 오버라이드 (3단계 이상시)
  2. 객체 내 모두 선언해야 함 (딱딱함)
  3. Listener 표현의 복잡함

즉 Big Size, 복잡한 GUI 환경에는 적합하지 않음.

좀 더 간결한 코딩 필요(많은 기능 필요)

Functional vs Objective Programming

Functional Programming

UI 코드를 간결히 표현할 도구 필요

코어 로직에 대해 간결히 표현할 방식의 필요

많은 함수를 가진 큰 객체 이벤트 처리 효율성 재고 필요

네이버 앱에서는 로직의 계층구조를 플랫하게 변경하기 위해 자바 8을 고려함.

자바 8의 스트림, 데이터의 파이프라이닝을 안드로이드 N부터 지원함.

레트로 람다 등의 도입을 고려했으나 협업을 고려했을 때 쉽지 않음.

결국 코틀린을 고려했지만, 언어를 바꾸는 것이라 고민하던 중 안드로이드 언어로 지정되어 적용하게 됨.

프레임워크를 쓰기 위해서 프레임워크에 대한 공부가 우선되기 때문에 대규모(5명 이상?) 프로젝트일 경우 신중해야 함.

그래서 협업자들에 대한 커뮤니케이션이 필요함. (다 생각이 다르고 설득이 어려움)

2. 앱의 구조

네이버 앱에서 Web Engine이 중심

Activity란?

라이프 싸이클은 왜 존재하는가? (다른 OS는 앱 단위에서 관리함)

Activity는 상태 관리와 UI 관리를 같이 함.

Activity는 각각이 앱이라 봐도 됨. (별도 프로세스, 런처 가능하여 멀티 런처 가능함)

안드로이드는 하나의 패키지 안에 복수개의 앱을 만들 수 있게 되어 있음.

Activity 간에는 Package 말곤 연계 없음

Activity 간에는 다 오픈 되있는 개념.

위가 안드로이드 UI 개념의 특징임.

Fragment는 화면 분할 개념이 시작이었으나 Activity 내에서 네비게이션 기능을 주로 함.

Intent란?

Intent는 RPC 역할. (프로세스 간의 통신)

Parcel로 Serialize한 정보 저장 역할.

Activity를 늘리는 건 좋은 방법이 아니고 비효율적임.

Ui Navigation

  • Activity만 사용
  • Activity 내 Fragment 사용
  • Activity 내 View 단위 사용 (많이 사용)

Activity나 뎁스가 깊어지면 안좋은 구조.

Fragment는 attach/dettach 시에 문제가 많음.

네이버 앱 내 탭의 Fragment는 좌우플리킹시 라이프 싸이클이 있어 제어에 문제가 있음.

라이프 싸이클 사용은 객체 각자 관리해야 하면 결국 상속 구조로 가야 함.

(꼭 프레임워크 제공 라이브러리 등에 의존할 필요 없음)

Event Dispatching (Big SizeComponent)

이벤트에 대해서 각 기능(ex. 툴바, 탭 등등)마다 처리함. (ex. Back 키 등)

각 기능에서 할 경우 최상위에서 상속받아 계층별로 사용하게 됨.

이를 루트 액티비티에서 처리하게 해도 됨.

루트 액티비티에서 이벤트 맵/리스트 관리 후 콜백 등을 처리하면 됨. (예전에는 기피했으나 멀티 코어 세상이라..)

네이버 앱의 경우 웹뷰의 NestedScroll이 필요한 경우 웹뷰의 엔진으로 어떤 것을 쓸지 몰라 추상화가 필요함.

인터페이스 상속과 큰 클래스에서 엔진 두가지를 다 관리하는 방법이 있었는데, 인터페이스 상속을 택함.

멀티프로세스

  1. 메인과 구별되는 기능요소
  2. 별도의 모듈(Dynamic Linked Library) 로드하는 요소 so 사용 등은 메모리 제거 안되므로 프로세스 분리 필요. (외부 라이브러리 등)
  3. 모듈의 크기가 큰 경우
  4. 멀티미디어와 같이 자원을 많이 소요하는 요소
  5. 크래시시 모듈 차단

추가로 UI 쓰레드도 멀티가 될 수 있음.

쓰레드

쓰레드풀(AsyncTask, Coroutine)

쓰레드풀은 멀티미디어 프로세스에서 못써서 쓰레드를 우선 순위 컨트롤해 사용. (쓰레드풀 사용시 성능 저하)

3. Design Architecture 적용

네이버 앱은 MVP (일반 브라우저와 Ai 기능 분리 사용)

객체 크기가 크다보니 MVVM보다 적합함

인앱 브라우저는 MV/Plugin (Processor(Controller)는 플러그인 형태)

Piped Filter Model

Piped Input(Output) Steam 사용.

source, transform, sink 필터 만들어 사용.

4. Multi Package

  1. Apk Extension(.obb)
  2. Another Apk
  3. App Bundle
  4. Instant App

앱 패키징은 앱번들 고려 중

5. 다양한 Framework 어떻게 활용할 것인가?

네이버 앱은 프레임워크를 많이 안씀.

Lottie, OkHttp, Glide, Retrofit 정도 사용 중.

Framework는 주로 native code, 오픈 소스 많지 않음.

대규모 서비스 앱에서 오픈 소스 사용에 신중해야 함.

(네이버 앱은 유저 3천만에 UV 억단위인데 Crash 천단위를 유지하기 위해 오픈 소스에 신중했음)

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-2 03  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 02  (0) 2019.01.14
[행사] Microsoft Azure Everywhere  (0) 2019.01.11
[행사] Naver Tech Concert Day-1 06  (0) 2019.01.07
[행사] Naver Tech Concert Day-1 05  (0) 2019.01.07

Microsoft Azure Everywhere

코엑스에서 Microsoft Azure 컨퍼런스가 열려 오전 세션만 듣고 옴.

개요

  • 행사명 : Azure Everywhere The 1st Wave

  • URL : http://www.microsoftazuresummit.com

  • 기조연설 동영상 : https://www.facebook.com/MicrosoftKorea/videos/291811018145452/

  • 일정 : 2019년 01월 11일(금) 08:30 - 17:30

  • 장소 : Coex GrandBallroom

  • 세션 :

    TimeSession
    08:30 - 09:30Registration & Booth tour
    09:30 - 09:50Microsoft Azure 오늘, 그리고 미래
    한국마이크로소프트, 최주열 이사
    09:50 - 10:30개발자 중심의 클라우드 플랫폼 응용과 진화의 방향
    Git Hub, Paul St. John, WW VP
    10:30 - 11:001% + 99% = AI 대중화
    Databricks, Jason Bissell, GM
    11:00 - 11:30클라우드 중심의 Digital Transformation 여정
    Johnson Controls, Youngchoon Park, VP
    11:30 - 12:00글로벌 기업들이 이야기 하는 Panel Discussion

현장

기타

01. Microsoft Azure 오늘, 그리고 미래

  • 발표자 : 최주열 이사 (한국마이크로소프트)

오픈소스 + 클라우드

Digital Transformation

Digital Transformation을 위해서는 인적 자원, 고객, 제품, 운영 피드백들이 필요.

Data - intelligence - Action

수 많은 기술 중 최선/차선/차악/최악 등을 찾아 개선하려 함.

Microsoft Azure가 이를 도울 수 있음.

Productive - Hybrid - Intelligent - Trusted

Productive

Azure regions는 54 곳으로 글로벌하게 서비스 중

한국은 중부(서울)과 남부(부산)으로 있음. (https://azure.microsoft.com/ko-kr/global-infrastructure/locations/)

개발자를 위한 통합된 도구를 제공하며,

120개 이상의 다양한 서비스를 제공하며,

단일화된 관리 환경을 제공함.

MS는 developer developer를 강조하며 폐쇄적이던 스티브 발머 사장 시절과 달라졌음.

2014년부터 MS love linux 를 시작으로 VS Code, Github 등을 제공하며 필요한 서비스를 대중화하려 함.

기술이 우선이 아니라 필요한 것들을 끌어내기 위함임.

MS의 이 모든 생각이 집약된 것이 Azure

현재는 Azure의 3~4개의 서비스로 간단히 비즈니스를 형상화할 수 있음.

고성능 컴퓨팅 환경도 지원함.

기존 단순한 Data와 Application의 연결이 아니라 연결된 Intelligent Edge를 연결하는 것이 될 것.

![](./images/intelligent edge.jpg)

AI 관련해서는 이미 학습된 모델들이 Azure에 올라가 있음. (사용자가 처음부터 학습시킬 필요 없음)

데이터의 양보다는 데이터의 모호함 등이 문제이며 이를 시각화 등으로 지원함.

Azure는 사용자의 요구를 받아들여 반영 개선하려 함.

02. 개발자 중심의 클라우드 플랫폼 응용과 진화의 방향

  • 발표자 : Paul St. John (Git Hub), WW VP

깃헙의 시작은 협업을 위한 툴 / 허브 / 개발자 커뮤니티.

깃헙에서는 혁신에 대한 고민 중.

예전 주요 회사들은 현재는 많이 도태된 경우가 많음.

현재 주요 회사들은 S/W 기업들이 주가 되며, S/W회사라고 해야 투자자들도 투자하기를 좋아함.

금융 기업들도 Cloud를 많이 쓰는 추세임.

자동차 제조사들도 S/W가 중요해지고 Walmart의 경우도 아마존을 따라가기 위해 실리콘 밸리에 랩을 차리고 혁신 중.

결국 개발자들이 혁신의 중심이 되가고 있음.

그래서 MS는 개발자에 대한 지원 등을 중요하게 생각함.

개발자들이 업무 중 코딩하는 것은 48% 정도라고 함.

회의, 문제 해결, work flow로 인한 대기 등으로 효율성이 떨어짐.

Innovation (Speed) <--------> Reliability (Controll)

개발자들이 운영에서 벗어나면 혁신에 치중해 새로운 것들을 만들수가 있음.

하지만 컨트롤이 쉽지 않음.

기업에서는 저 두 개념 사이에서 밸런스를 잡기 어려워 함.

깃헙이 이를 해결하기 위해 도와준다고 함.

주로 깃헙은 개발자들이 배우러 들어오는 경우가 많음.

그러면서 협업자도 찾을 수 있고, 창의력 넘치는 방법들도 찾게 됨.

현대 S/W 개발에서는 오픈소스가 필수임.

MS도 예전에는 오픈소스를 비웃었지만, 당시에도 확실한 위협이었음.

오픈 소스는 실제 프로젝트에 많이 사용되고, 깃헙에 가장 많이 있음.

InnerSource

기업에서 소스를 오픈하거나 폐쇄적으로 관리하는 방법이 일반적임.

이너소스를 통하면 누구가가 좋은 아이디어로 올려놓은 소스를 통해 도움이 될수 있고, 숨어있던 전문가와 협업이 가능해짐. (인력 관리 측면에선 인재 찾기도 가능해짐.)

이너소스나 코드 재사용은 최대한 줄이는게 필요하긴 함.

또 규제 등을 받는 부분은 아이디어 코드를 숨겨놔야 할 수도 있음.

Coinbase라는 회사의 경우는 깃헙을 배우고 적응하는 것이 쉬워 인력 관리에 도움을 받은 사례임.

깃헙은 오픈 소스 커뮤니티를 제공하여 업계의 공동대응을 지원하기도 함.

오픈소스를 사용하며, 알럿을 받을 수 있다면 더 좋을 것임.

고객 의견, 혁신에서 개발은 너무 멀리 있지만, 오픈소스가 이를 지원해 줌.

깃헙은 코드와 프로세스를 보다 개선시켜 줌.

기존에 IBM과 일하며 상호간에 좋은 영향을 미쳤던 사례가 있음.

당시 IBM 사람이 쓴 기사.

A fool with a tool is still a fool

바보가 툴을 가지면 단지 툴을 가진 바보일 뿐임.

툴과 함께 문화가 있어야 기업이 바뀔 수 있음.

그래서 깃헙에서는 문화에 대해서 기업에 많은 질문을 던지려고 함.

MS와 깃헙은 혁신을 만들고 실제 서비스를 만드는 것을 중요하게 생각함.

이제 깃헙에서 Azure의 많은 서비스 접근이 가능해짐.

MS와 깃헙은 빠른 혁신에 도움이 되려고 함.

이에 대한 댓가를 따로 바라지는 않음. (그 예로 private repository도 무료화 함)

엣지에서 하기 어려운 테스트 등도 클라우드와 깃헙의 오픈소스를 사용하면 가능함.

Q & A

Q : 깃헙 액션 베타의 사용은 언제부터 가능한지?

A : 깃헙 액션이란 개발자가 work road를 수정할 수 있게 하려는 서비스임. 범위 통제 등도 제어.

​ 곧 또는 다음 분기를 예상하며, 곧 깃헙 3천만의 유저들이 work flow를 복사해서 쓸수도 있게 될 거임.

03. 1% + 99% = AI 대중화

  • 발표자 : Jason Bissell (Databricks), GM

Databricks에서 현재 생각하는 것들

  • innovation
  • collaboration
  • AI

아직은 1%정도의 기업들이 AI를 사용 중일 것임.

데이터 관련 직군은 월요일 아침에 많은 문제가 쌓인다는 것을 앎.

AMPLab의 Lester와 Matei라는 사람이 Netfliz Prize를 통해 Data 알고리즘을 풀어 백만장자가 될뻔 했으나 제출시간이 20분 늦어 실패함.

(그만큼 시간이 중요함?)

Spark는 600개의 코드라인에서 시작됨.

현재는 Spark로 수많은 UseCase들이 깃헙에 있음.

이제는 많은 기없들이 AI를 논함. (99%?)

  1. Data is not ready for Analytics

    • 많은 Data가 Data Lake에 있지만 느리고 비용이 듦.
    • Data 분석이 안되서 그런 것이며 이는 Spark가 해결해 줌.
  2. A Zoo of new ML Frameworks

    • 너무 다양하고 많은 ML 프레임워크가 있음.
    • 하나의 솔루션으로는 문제 해결에 어려움이 있음.
    • Databricks를 통해 여러 Frameworm를 사용하는데 도움이 됨.
  3. Data Science & Engineering silos

    • 위 과정이 오래 걸리지만 databricks + ML Flow로 속도 개선이 가능함.

    • Azure Databricks 곧 한국에 서비스할 예정임.

기업 고객들이 원하는 것

  • 데이터나 소스 활용도를 높이는 것

  • 종합된 통계를 통해 Optimization하는 것

  • 위 flow 들을 실시간으로 하는 것

대기업들도 대규모 데이터의 Optimization 속도 문제를 겪고 있음.

데이터 프레임워크 서비스로 이를 해결할 수 있으며, AI와 ML로 아이디어를 빠르게 구현할 수 있을 것임.

04. 클라우드 중심의 Digital Transformation 여정

  • 발표자 : Youngchoon Park (Johnson Controls), VP

Johnson Controls는 130여년이 된 회사로 작년 37조 매출인 빌딩/명소 매니징 업체임.

매커니컬 중심의 회사가 변화를 어떻게 했는지 소개함.

Digital Transformation : 결국 value 창출의 대한 고민이 됨.

2011년에 Panoptix 라는 클라우드 기반 빌딩 제어시스템을 성공적으로 개발했으나, 사업적으로 망함.

2013년 Connected Product라는 서비스를 다시 만들어 절반의 성공을 거둠.

실패의 원인은?

너무 클라우드 중심이었고, 고객 경험을 고려치 못함.

데이터 엔지니어들을 모아 예상하지 않고, 바로 Product를 만들었음.

데이터 스케일링 아웃을 하려면 사람도 스케일링이 가능해야 함.

value 체인닝의 변화를 고려해야 했음.

기술 혁신으로 시작해 마켓 유통도 변화될 수가 있었음. (중간의 Reseller 가 필요없어진다던지)

AI 기술이 어떤 문제인지 어떻게 고쳐야 하는지를 제시해주지만,

비즈니스 관련 데이터 매칭이나 오프라인에서 걸리는 시간들(이동거리, 툴 렌트시간) 등이 실제 서비스 지연의 원인이 됨.

즉 Fault가 빨리 발견되어도 빨리 고칠수 있는지가 고객에겐 중요한 문제였음.

기술이 좋아도 실제 기존 서비스 경험과 고객의 비즈니스에 도움이 되어야 했음.

개발시 자기의 장점을 알아야 함. 조그만 밸류들이 모여 큰 밸류가 될 수 있음.

기술을 쓸 때 비즈니스 관점도 좋지만, 반복할 수 있을지도 생각이 필요함.

05. 글로벌 기업들이 이야기 하는 Panel Discussion

인텔, 팔로알토, Verizon, Microsoft 관계자 Q & A 형식의 좌담회?

Q : 인텔과 MS의 파트너십 전망은?

인텔 : 두 회사의 파트너십이 기술 시장을 이끌어 왔고, 앞으로도 이끌고 갈 거임.

Q : MS에서 고성능 CDN 지원이 필요해 네트워크 기술이 필요함.

버라이즌 : 버라이즌은 컴퓨팅 브로드밴드 사업도 함. 전세계 네트워킹의 10% 트래픽을 담당 중이며, 보안 및 트랙픽 대규모 지원을 함. 멀티미디어 서비스 지원 등의 호환성도 높이고 있음.

Q : Network 보안솔루션 소개? Azure 보안과의 차이는?

팔로알토 : Azure 마켓의 팔로알토 차세대 VM 시리즈는 알려진 위협, 알려지지 않은 공격에 대해 즉각적으로 분석 후 지원함. 보안 지표 등을 시각적으로도 제공함.

Q : SAP HANA를 위한 M-Series VM이란?

MS : M 시리즈는 핵심적인 피처임. 포괄적인 지원 머신(SAP를 위한?). 정확한 사이즈로 머신을 지원 가능케 함. 꼭 하이엔드 머신을 안써도 됨.

Q : 버라이즌 Digital Media Service의 공격적인 국내 투자는?

버라이즌 : MS 협업 전부터 확장을 고려했음. 컨텐츠 배포 등을 잘 지원하며 한국 게임이나 컨텐츠 시장을 특별히 생각함 (다른 시장과 다른 요구사항들이 나옴.)

Q : MS는 3500명의 인원과 1조원의 보안투자를 하는 중임. MS와 보안 협력 전략은?

팔로알토 : 보안은 크게 3가지로 보며 경계구간(네트워크), 실제 서비스 보안, 데이터 센터 보안으로 봄. 자산관리, 레드락 등을 제공하며 컨플루언스를 준수해 보안을 지원함.

Q : SAP와 MS의 파트너십 전망은?

MS : 30년 넘게 유지된 파트너십임. 현재 SAP 모든 시스템이 Azure로 옮김. Azure 팀이 SAP를 지원하며, SAP의 문제는 MS도 책임을 지고 개선하려 함.

Q : 미래형 클라우드 데이터 센터는? Project Brainwave란?

IBM : 고객이 더 쉽고 빠르게 Cloud AI 서비스를 사용 가능하도록 하는 것이 Project Brainwave임. 유연성이 높고 상대적으로 성능이 낮은 범용 프로세서와 유연성은 낮지만 성능이 좋은 특정 목적 프로세서의 중간인 FPG도 지원함.

Azure는 오픈 클라우드를 지향한다고 함.

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-2 02  (0) 2019.01.14
[행사] Naver Tech Concert Day-2 01  (0) 2019.01.14
[행사] Naver Tech Concert Day-1 06  (0) 2019.01.07
[행사] Naver Tech Concert Day-1 05  (0) 2019.01.07
[행사] Naver Tech Concert Day-1 04  (0) 2019.01.04

Naver Tech Concert Day-1

07. Obfuscation 101: 난독화, 프로가드, R8, 트랜스포머 API

  • 발표자 : 김용욱 (카카오뱅크)
  • 동영상 : https://tv.naver.com/v/4655623/list/272653
  • 슬라이드 : https://www.slideshare.net/NaverEngineering/16obfuscation-101-r8-api
  • 세션설명 : 안드로이드의 앱들은 현재 프로 가드를 통해서 보호되고 있고 향후로는 구글이 작성한 R8으로 대체할 예정에 있다. 난독화 도구들을 써봤지만 막연히 쓰는 경우가 많은데 난독화 도구가 어떤 일을 하고 있고 기본적인 메커니즘이 어떻게 구현되어있는지 프로가드, R8은 무엇인지, 그리고 안드로이드 빌드 과정에 어떻게 통합되는지를 살펴보는 시간을 갖는다.

안드로이드 코드의 특징

  • 이식에 좋은 바이트 코드 사용
  • 동적 컴파일에 기반한 성능 향상
  • 의존성도 동적으로 로드
  • 암호화되지 않은 클래스와 리소스로 구성
  • 공간 효율적인 데이터 포맷

바이트 코드에 대한 간략한 이해

자바 Bytecode

  • 스택 기반의 VM을 사용 (검증이 쉽지만 레지스터 기반의 실제 기기와 차이로 성능상 단점)
  • 32비트 스택 하나의 요소가 대부분의 타입 커버
  • 256개의 연산

자바는 상수의 종류를 구반하지 않음.

1+2 연산시 1과 2의 스택을 쌓은 후 두 스택을 꺼내 연산 후 3을 스택에 쌓음.

istore의 i는 int형을 의미.

기본적으로 ARM이나 x86 기계어보다는 단순함.

달빅 Bytecode

  • 레지스터 기반의 VM 사용 (실제 하드웨어와 매핑에 이점으로 메모리 덜 쓰나, 상대적으로 검증이 어려운 단점)
  • 32비트 레지스터 하나의 요소가 대부분의 타입 커버
  • 64K 개의 레지스터 하지만 주로 앞의 256개, 가끔은 16개만 사용.

const 명령을 통해 레지스터 v1, v2에 상수를 넣음.

add-int를 통해 v2와 v3의 합을 v0에 저장.

자바와 달빅 bytecode 차이점

desugar는 자바 8을 지원하기 위함. (람다 등을 제거하여 자바 7 컴파일러가 돌 수 있도록)

.class 파일을 dex(dx, d8)을 통해 .dex 파일로 변환.

  • DX - 안드로이드 스튜디오 3.0 전 (D8보다 시간이 오래 걸리고 용량도 큼)
  • D8 - 안드로이드 스튜디오 3.0 이후 (써드파티 툴에서 새로운 DEX를 지원하지 못하는 문제 있을 수도)
  • gradle.properties에서 android.enableD8 = true | false

DX/D8 비교는 안드로이드 스튜디오의 APK Analyzer로 비교 가능.

Jack & Jill을 통해 .java를 .dex로 한번에 빌드하는 것을 구글에서 시도했지만, 기술을 폐지하기로 함.

(써드파티 생태계에서 달빅 바이트코드를 제어하지 않기에)

Transform API

구글이 써드파티를 위해 제공한 API

.class -> Transformer -> .class 형태를 가짐.

프로가드 등의 도구들도 Transform API에 의존적이나 여전히 그러지 못한 도구들이 존재함.

Transform 을 상속받아 transform을 구현하여 입력받은 클래스 파일을 변조 후 출력하면 됨.

ProGuard, Realm Transformer, Desugar 등도 표준화된 API를 따름.

Transformer를 만들 때는 ASM, BCEL 같은 Low Level를 쓰거나 (주로 Google), AspectJ 같은 High Level을 쓰기도 함 (Jake Wharton). 또는 그 중간인 Javassist를 사용하기도 함 (Realm).

동적 컴파일에 기반한 성능 향상

인터프리터 vs JIT(코드 일부를 컴파일해 개선) vs AOT(미리 빌드)

  1. 아무 정보 없을 때 인터프리터로 해석. (느림)
  2. 일정 횟수 이상 수행된 메서드만 컴파일해 JIT 코드 캐쉬에 저장. (프로파일링 정보에 기반해 업데이트)
  3. 주기적으로 dex2aot 데몬이 코드를 빌드해서 oat 파일을 생성.
  4. 다른 앱에 의해 사용되면 전체 빌드, 아니면 프로파일에 기반해 빌드.
  5. JIT 컴파일과 AOT 컴파일이 있다면 JIT 컴파일 사용.(동적 최적화)

의존성도 동적으로 로드

자바는 모든 것이 동적으로 로딩.

메서드 명, 클래스 명, 필드 명이 공개되며, 안드로이드 앱의 경우 Activity, Fragment, Service 등을 외부로 공개해야 함.

난독화 도구에 exlcue/include 항목을 설정해야 함.

암호화되지 않은 클래스와 리소스로 구성

  1. 클래스와 리소스는 암호화되어 있지 않음.
  2. 기본 클래스 로더가 암호화를 지원하지 않아 한계가 존재. 기본 클래스 로더가 호출되기 전에 암호화가 풀린 클래스를 가로챌 수 있음.
  3. 클래스 암호화는 선호되는 보호 방법이 아님.

공간 효율적인 데이터 포맷

  1. LEB-128 사용해 가변 바이트(1-5바이트)로 32비트 잘 저장 7비트 단위로 나누어 그룹화 하여 앞에 첫문자인지 0/1로 구분
  2. 부호가 있는 수를 위한 SLEB128, 부호 없는 수를 위한 ULEB128과 -1만 지원하는 ULEB128p1이 있음. ULEB128p1일 경우 -1이면 0.
  3. 여러 클래스에서 상수 공유 (.class가 아닌 .dex 지원으로 가능)
  4. 상대 주소 사용 (특정 주소(1024) 이후엔 상대적인 주소(1)로 사용)
  5. 메서드 갯수까지 효율적으로. (64K)

난독화와 앱 보호에 대한 간략한 이론

  1. 클래스/리소스에 암호화를 적용
    • 클래스를 암호화하고 커스텀 로더 사용
    • Packer와 Protector로 나누어짐.
    • 해독을 위해 클래스를 전달해야 하며, 결국 커스텀 로더가 공격의 취약점이 됨.
    • 많은 패커와 프로텍터는 ODEX 복사나 심지어 Dex2Jar 툴에 의해 쉽게 풀림.
  2. 리소스 사이에 클래스를 매복
    • 이미지 파일로 dex 파일을 숨기는 방법
    • 수상하게 큰 파일을 찾거나 파일 포맷 검증으로 혐의가 좁혀짐.
  3. 네이티브 코드로 핵심 코드를 숨김
    • 성능상의 이점도 있지만 그래도 기계어 코드도 해석이 가능하긴 함.
    • VM 밖에선 온갖 하드웨어 이슈를 경험할 수 있음.
    • ABI 세트 맞추기 어려움.
  4. 서버에 핵심 코드를 숨김
    • 리얼 타임 응답성이 떨어짐. 로컬 DB 필요할 수도.
    • 서버 다운시 사용 불가. 로컬 캐쉬 이용해서 풀백 구현도 어려움.
  5. 템퍼 감지를 추가
    • 디버거, ptrace 를 발견하는 코드 삽입
    • 에뮬레이터 감지
    • 해당 부분 우회시 대부분 앱 진입이 가능함.
  6. 클래스, 필드, 메서드 명 변경
    • ProGuard부터 대부분의 도구가 지원
    • 짧고 간격한 이름으로 클래스, 필드, 메서드 명 변경하지만 외부에서 import 되거나 export 되는 명칭은 변경 불가
    • 위 처리로 공간 복잡도도 줄이고 실행시간에도 긍정적인 영향 줌.
    • 명칭만 바뀌는 것이라 결국 해결 가능.
    • 도구마다 고유의 네이밍 패턴이 있음. (APKiD에서 검출)
  7. 리플렉션 호출 추가
    • 메서드 호출 단계를 추가
    • ProGuard는 지원 못하지만, DexGuard나 Arxan 등 유료도구에서 지원
    • 리플렉션이 반복적인 패턴을 가지기 때문에 기계적으로 풀 수 있음. (ex. Dex-Oracle에서 DexGuard 패턴 해제)
    • 많은 단말에서 리플렉션 크래시 보고됨.
  8. 노이즈 추가
    • 메서드 외부에 아무런 영향이 없는 코드 추가
    • 클래스 상태 변경, I/O, 반환 값 등이 없음.
    • 디버거나 디스컴파일러가 깨지는 코드를 추가하기도 하지만, 그만큼 쉽게 고쳐짐.
  9. 제어 흐름
    • 반복문이나 분기문을 불필요하게 추가
    • If 문 대신 try-catch 블록을 사용하기도 함.
    • switch 문을 중첩적인 레이블로 변경하기도 함.
    • 브랜치를 jsr 명령 (goto)로 변환하기도 함.
    • 일반적으로 속도를 저하시키는 요인.

ProGuard와 R8의 역할

프로가드는?

  1. 클래스, 필드, 메서드 명 변경

  2. 사용하지 않은 클래스, 필드, 메서드 제거

.class->ProGuard->.class

위는 아래와 같음.

.class->Shrinker->.class->Name Obfuscator->.class

안드로이드 빌드에 통합된 프로가드

.java->javac->.class

​ ->Desugar Transform->.class

​ ->ProGuard Transform->ProGuard->.class

​ .dex<-Dex<-

통합되지 않은 난독화 도구들의 흐름

.java->javac->.class

​ ->Desugar Transform->.class

​ ->Dex->.dex->Dex2jar->.class

​ .dex<-Dex<-.class<-Obfuscator<-

R8

.java->javac->.class

​ ->Desugar Transform->.class

​ ->R8->.dex

R8 = D8 + Shrinker + Name Obfuscator

(ProGuard 버리려는 계획?)

'IT > 행사' 카테고리의 다른 글

[행사] Naver Tech Concert Day-2 01  (0) 2019.01.14
[행사] Microsoft Azure Everywhere  (0) 2019.01.11
[행사] Naver Tech Concert Day-1 05  (0) 2019.01.07
[행사] Naver Tech Concert Day-1 04  (0) 2019.01.04
[행사] Naver Tech Concert Day-1 03  (0) 2019.01.04

+ Recent posts