학기중 수강한 프로그래밍 언어(python, C, Java) 외에 새로운 프로그래밍 언어에 대해 학습하고 싶었습니다. 이미 수강했던 Java 와의 언어적 상호관계가 있는 Kotlin 언어를 새롭게 학습해 보는것으로 하며 그리고 이번 학기에 수강하는 앱프로그래밍 과목도 배우니 혼자서 공부하면 꿩먹고 알먹고 좋을꺼같아서 Kotlin 안드로이드 앱 프로그래밍을 한번 공부해보는것으로 결정하였습니다.
1. 개발 환경 준비
구글은 13′ 에 안드로이드 전용 앱을 개발하는 도구로서 ‘안드로이드 스튜디오’ 를 발표하였습니다. 안드로이드 앱을 개발하기 위해서는 거의 필수적입니다. 안드로이드 개발자 사이트에서 (Android 스튜디오)를 손쉽게 내려받아 설치할수 있습니다. 흔히 각 프로그래밍 언어의 개발환경 파이참, vscode, eclipse 등 설치 과정과 유사합니다. 손쉽게 따라할수 있습니다.
2. 첫번째 앱 만들기
New project 선택 -> 새로운 프로젝트 생성
앱이 실행될 플랫폼과 템플릿을 선택해야합니다. 안드로이드는 다양한 실생활 방면에서 구동됩니다. 휴대폰, TV, 자동차 등 다양합니다.
Phone and Tablet을 선택하고 템플릿은 빈 화면이 기본인 Empty Activity를 선택합니다.
그다음 프로젝트 정보를 입력합니다.
Name / Packge name / Save location / Language / Minimum SDK 가 있습니다.
[Name] 프로젝트나 패키지 이름은 개발자가 직접 정할수 있습니다.
[Packge name] 앱의 식별값입니다. 안드로이드 앱은 개발자가 작성한 패키지명으로 식별됩니다. 동일한 패키지
명으로는 스토어에 등록될수도 없고 기기에 설치도 불가능 합니다. 패키지명은 대체로 도메인을
역순으로 입력하고 끝에 프로젝트명을 붙이는 형태로 작성합니다.
[Save location] 프로젝트의 파일들이 저장되는 루트 디렉터리입니다. 앱 개발을 진행하면서 소스나 이미지
파일들이 이곳에 저장됩니다.
[Language] 기본값은 Kotlin 물론 Java로도 가능합니다.
[Minimum SDK] 앱이 설치되는 최소 SDK 버전입니다. 설정해서 개발된 앱은 설정한 버전 이상의 휴대폰에서만
설치됩니다.
SDK 매니저
안드로이드 스튜디오 화면의 오른쪽 위를 보면 툴바에 아이콘이 있습니다. 안드로이드 SDK를 관리할수 있는 SDK 매니저가 열립니다.
SDK Platforms에서는 안드로이드 SDK 목록을 보여줍니다. 안드로이드 스튜디오를 설치할때 최신버전의 SDK가 기본으로 설치됩니다. 최신버전의 SDK를 설치해서 개발할 필요가 있으나 호환성 문제로 낮은 버전에서 테스트 하기 위해서 다른 버전의 SDK를 설치할수도 있습니다.
SDK Tools를 클릭하면 개발자 도구들이 표시됩니다. 기본적으로 필요한 도구는 안드로이드 스튜디오를 설치하면 기본적으로 포함됩니다.
SDK 도구
Android SDK Bulid-Tools 32-rc1 / 앱을 빌드하는 데 필요한 도구 (필수설치)
Android Emulator / 앱을 실행하는 데 필요한 도구 (필수설치)
Android SDK Platform-Tools / 안드로이드 플랫폼과 연동되는 adb,fastboot,systrace와 같은 도구 모음 (필수설치)
Android Emulator Hypervisor Driver for AMD Processors(Installer) / AMD용 하이퍼바이저 드라이버 (AMD CPU 설치용)
Intel x86 Emulator Accelerator(HAXM intaller) / 인텔 에뮬레이터 가속기 (인텔 CPU 설치용)
위에 필수설치 3가지는 반드시 설치해야하며 AMD-인텔 설치용은 자신의 CPU에 따라 설치하면 됩니다.
3. 앱 실행하기
앱을 테스트 실행하는 방법을 살펴보겠습니다. 2가지 방법이 있습니다.
[안드로이드 스튜디오가 제공하는 가상기기] [실제 스마트폰을 이용] 2가지 방법이 있습니다.
이중 가상기기인 에뮬레이터 방식만 설명하겠습니다.
가상 기기에서 실행
안드로이드 가상 기기는 AVD 라고 하며 에뮬레이터라고 부릅니다. AVD 매니저에서 Create Virtual Device를 클릭하면 에뮬레이터 만들기를 시작합니다.
하드웨어 크기인 스마트폰 선택을 할수 있으며 여러 종류가 있습니다. 그 다음으로 시스템 이미지를 선택하는 창이 나옵니다. AVD 설정에서 이 부분이 가장 중요한데 앞에서 선택한 하드웨어에 설치할 시스템 이미지, 즉 안드로이드 운영체제 버전을 선택해야합니다. 모든 설정을 확인한 다음에 AVD 만들기를 완료하면 됩니다.
에뮬레이터 실행
이렇게 AVD를 만들면 매니저 창에 추가됩니다. 여기서 Actions 항목에 있는 버튼을 누르면 실행, 수정, 기타 메뉴를 실행할 수 있습니다.
애뮬레이터에서 앱 실행
AVD까지 준비했으므로 앱을 실행해 보겠습니다. 툴바에서 실행할 앱을 선택하고 오른쪽에서 앱을 실행할 기기를 선택하고 재생버튼을 클릭하면 앱을 빌드한 후 실행합니다.
public class item1 {
private String name;
// public 생성자
public item1(String name) {
this.name = name;
}
// static factory method
public static item1 myName(String name) {
return new item1(name);
}
public static void main(String[] args) {
item1 my = myName("haessae0"); // static factory method
item1 my2 = new item1("haessae0"); // public 생성자
}
}
이렇게 클래스는 정적 팩토리 소드를 제공할 수 있다.
장점
– 이름을 가질 수 있다.
– 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
– 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
– 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
– 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
단점
– 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가
– 정적 팩토리 메소드는 프로그래머가 찾기 어렵다.
장점
1. 이름을 가질 수 있다.
정적 팩토리 메소드는 이름만 잘 지으면 반환되는 객체의 특성을 명시적으로 표현할 수 있다.
public class item1 {
private String name;
// public 생성자
public item1(String name) {
this.name = name;
}
// static factory method
public static item1 myName(String name) {
return new item1(name);
}
public static void main(String[] args) {
item1 my = myName("haessae0"); // static factory method
item1 my2 = new item1("haessae0"); // public 생성자
}
}
앞서 작성한 코드를 보자면
1.
public 생성자로 만든 것 -> 호출하였을 때, ‘haessae0’라는 것이 어떤 인스턴스 변수인지 알기 어렵다. -> 클래스 이름인 ‘item1’에게 전달하여 생성하는 것이기 때문이다.
2.
정적 팩토리 메소드로 만든 것 -> 호출하였을 때, ‘haessae0’라는 것이 myName이라는 것을 알 수 있다. -> 내 이름이 haessae0라는 것을 알 수 있다.
당연히 알기 쉬운 정적 팩토리 메서드를 사용할 것이다.
또한,
public 생성자는 하나의 시그니처로 하나만 생성할 수 있다.
public class item1 {
private String name;
private String birth;
// public 생성자
public item1(String name) {
this.name = name;
}
// public 생성자 -> 불가
public item1(String birth) {
this.birth = birth;
}
}
이미 name 변수를 받는 생성자가 존재 하기 때문에 birth를 받는 생성자는 생성할 경우 오류를 범하고 만다.
왜? 이미 하나의 변수를 받는 생성자가 존재하기 때문에 사용을 하여도 엉뚱한 메소드를 호출할 수 있다.
하지만! 정적 팩토리 메서드는 가능하게 해준다.
public class item1 {
private String name;
private String birth;
// static factory method
public static item1 myName(String name) {
item1 my = new item1();
my.name = name;
return my;
}
// static factory method
public static item1 myBirth(String birth) {
item1 my = new item1();
my.birth = birth;
return my;
}
}
이렇듯이 하나의 시그니처로 여러 가지를 구현할 수 있다.
2. 호출될 때마다 인스턴스를 새로 생성하지는 않아도 된다.
불변 클래스는 인스턴스를 미리 만들거나 캐싱하여 재활용하는 방식이기에 불필요한 객체 생성을 막을 수 있다.
대표적으로 Boolean.valueOf(boolean) 메서드가 있다.
public final class Boolean implements java.io.Serializable,Comparable<Boolean> {
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
}
상수 객체로 선언해주기 때문에 새로운 객체를 매번 만들지 않는다.
-> 생성 비용이 큰 객체가 자주 불려지는 상황에 정적 팩토리 메서드를 사용하면 그 성능을 많이 이끌어 낼 수 있다.
또한, 반복되는 객체 요청을 언제 어느 순간에 인스턴스를 살고 죽게 할지 통제하는 인스턴스 통제 컨트롤이 있다.
왜? 사용할까?
– 인스턴스를 통제하면 싱글톤이나 인스턴스화 불가 상태로 만들 수 있다.
– 불변 값 클래스에서 동치인 인스턴스가 하나인 것을 보장 -> a == b일 때만, a.equals(b)가 성립된다.
– 플라이 웨이트 패턴의 근간이고, 열거 타입은 인스턴스가 하나만 만들어지는 것을 보장한다.
3. 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.
코드의 유연성을 제공해준다. -> 인터페이스를 정적 팩토리 메서드 반환 타입으로 사용하는 인터페이스 기반 프레임워크를 만드는 핵심 기술이기도 하다.
동반 클래스에서 주로 사용된다.
자바 1.8 이전 -> 인터페이스에 정적 메서드를 선언할 수 없었다. 따라서 인터페이스에 기능을 추가하기 위해서는 동반 클래스라는 것을 만들어 그 안에 정적 메서드를 추가했다.
-> 굳이 별도의 문서를 찾아가며 수현 클래스가 무엇인지 알아보지 않아도 된다.
4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.
반환 타입의 하위 타입이기만 하면 어떤 클래스의 객체를 반환하든 상관없다. 심지어 다른 클래스가 객체를 반환해도 된다.
예시로 EnumSet 클래스가 존재한다.
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum<?>[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
클래스에는 noneOf라는 메서드가 존재하는데 잘 보면 universe의 개수가 64개 이하면 RegularEnumset의 인스턴스를 반환하고, 65개 이상이면 JumboEnumset의 인스턴스를 반환한다.
두 타입 모두 알려지지 않기 때문에 클라이언트는 인지하지 않아도 되며 나중에 삭제하거나 새로운 타입을 만들어도 문제없이 사용할 수 있다. 그저 Enumset의 하위 클래스에만 존재하면 된다.
5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.
이러한 유연함은 서비스 제공자 프레임워크를 만드는 기반이 된다. -> 대표 JDBC
서비스 제공자 프레임워크의 구성요소
Service Interface : 구현체의 동작 정의
Connection
Provider Registration API : 제공자가 구현체를 등록할 때 사용
DriverManager.registerDriver
Service Access API : 클라이언트가 서비스의 인스턴스를 얻을 때 사용
DriverManager.getConnection
Service Provider Interface : 서비스 인터페이스의 인스턴스를 생성하는 팩토리 객체
Driver
단점
– 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가
– 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메서드만 제공하면 하위 클래스 생성 불가
java.util.Collections로 만든 구현체는 상속할 수 없다.
2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.
Javadoc 문서에서 따로 정리하지 않는다. API 문서를 잘 써놓고 메소드 이름도 알려진 규약을 따라 짓는 식으로 문제를 해결해줘야 한다.
비동기 작업을 손쉽게 관리해주는 라이브러리 Celery 에 대해 간단히 소개 드리겠습니다.
파이썬에서는 비동기 모듈을 사용하지 않으면 기본적으로 동기 방식으로 작업을 처리합니다. asyncio를 사용하지 않고 비동기 작업을 원할 때 Celery 를 사용하면 됩니다.
Celery 는 라이브러리지만 broker 와 backend 가 필요합니다. broker는 큐이고 backend 는 샐러리가 처리한 결과물을 저장합니다. 아래 그림을 보시면 이해가 쉽습니다.
python 에서 celery 라이브러리를 이용해 작업 요청을 하면 celery 라이브러리는 broker 에 메시지를 삽입합니다(큐에 추가). 다른 프로세스인 worker 는 대기하고 있다가 큐에 메시지가 들어오면 읽고 작업을 수행합니다. 작업이 완료되면 backend 에 작업 결과물을 저장합니다.
그림에서는 마치 동기적인 작업처럼 그려 놓았는데 작업 요청은 non-blocking 입니다. 따라서 시간이 오래 걸리는 여러 개의 작업을 먼저 요청해 두고 다른 작업을 하다가 나중에 결과 수신을 해도 되는 것이죠. 수신의 경우엔 blocking 입니다. 작업이 완료될때까지 계속 대기합니다. 이 경우 코드는 아래와 비슷해질 것입니다.
def main():
# 시간이 오래 걸리는 작업 요청
hw1 = heavy_work_1.delay()
# 다른 작업 수행
work2_result = work2()
...
# 결과 수신
hw_result = hw1.get()
return hw_result, work2_result
이번에는 흔한 예제를 보겠습니다. 회원가입 예시인데요. 회원가입을 하게 되면 database 에 레코드를 추가하는 작업은 오래 걸리지 않지만 이메일 발송이 2초에서 3초 정도 소요되는 경우가 발생합니다. 이런 절차를 전부 기다리게 되면 고객이 가입 버튼을 누르고 2초에서 3초정도 기다리게 됩니다. 이 때 celery 에게 메일 발송 작업을 요청하고 고객에게 응답을 바로 전송하면 지연 시간 없이 다음 페이지로 넘어가게 됩니다. 확인 메일 발송에는 보통 반환값을 기다릴 필요가 없기 때문에 결과를 수신하는 코드는 제거했습니다.
def register_user(username, password, email):
# 데이터베이스에 회원 레코드 삽입
db_success = insert_to_database(username, password)
if not db_success:
return False
# 확인 메일 발송 (non-blocking)
send_verification_email.delay(username, email)
# 메일 발송 기다리지 않고 응답 전송
return True
두 가지 시나리오로 간단하게 celery 라이브러리를 알아봤는데요. 기회가 된다면 celery 의 지원 범위와 사용 예시를 포스팅하도록 하겠습니다.