Pass를 만드는 변수에 넣을 정보를 받아올 웹 페이지를 제작 중에 있습니다.
QR 사진을 올리면 QR을 인식할 라이브러리를 찾고 있는데 혹시 관련 정보 아시면… 레퍼런스 좀 보내주세요 흑흑 ㅜㅜ
HTML로 일단 만들긴 했는데 CSS로 스타일도 넣어주려 합니다.
우선 라이브러리 찾는게 우선이지만…
JS를 안해봐서 쉽지 않네요 껄껄
Pass를 만드는 변수에 넣을 정보를 받아올 웹 페이지를 제작 중에 있습니다.
QR 사진을 올리면 QR을 인식할 라이브러리를 찾고 있는데 혹시 관련 정보 아시면… 레퍼런스 좀 보내주세요 흑흑 ㅜㅜ
HTML로 일단 만들긴 했는데 CSS로 스타일도 넣어주려 합니다.
우선 라이브러리 찾는게 우선이지만…
JS를 안해봐서 쉽지 않네요 껄껄
알림 기능을 구현해 보겠습니다.
실시간 알림은 websocket 등으로 구현 하면 되지만 디지털액자는 그렇게까지 실시간으로 알림을 보지 않아도 되기 때문에 2~5초 간격으로 서버에 ajax 요청을 보냅니다. 서버는 알림 배열을 반환합니다.
클라이언트는 이 배열을 읽고 알림 표시 기한(expire)이 지났는지 확인한 후 알림을 display_time 초 동안 표시합니다. 랜덤 id 값이 부여되기 때문에 클라이언크는 알림을 표시했으면 localstorage 에 이 id 값을 저장합니다.
이 localstorage 는 새로고침해도 데이터가 지워지지 않습니다. 따라서 한번 표시된 알림은 다시 표시되지 않습니다.
이제 notification 을 자동으로 넣어주는 코드를 짜야 합니다. 우선 일정 시작 전 알려주는 코드를 짭니다.
매 1분마다 작업을 돌려줘야 하기 때문에 fastapi_utils 라이브러리를 사용했습니다.
@app.on_event("startup")
@repeat_every(seconds=60 * 1) # 1 minute
def event_notification() -> None:
now = datetime.datetime.now(tz=tzoffset(None, 32400))
evt_list = get_event_list()
expire = 60 * 10 # 10 분
display_time = 30 # 30 초
for evt in evt_list:
for reminders in evt.reminders:
reminder_at = get_evt_start_date(evt) - datetime.timedelta(minutes=reminders.minutes_before_start)
if now <= reminder_at < now + datetime.timedelta(minutes=1):
start_date = get_evt_start_date(evt).strftime("%m-%d %H:%M")
add_notification(f"{start_date}<br>{evt.summary}<br>일정이 {reminders.minutes_before_start}분 후 시작합니다.",
expire=now.timestamp() + expire, display_time=display_time, show_notiboard=False)
if len(evt.reminders) == 0:
reminder_at = get_evt_start_date(evt) - datetime.timedelta(minutes=30)
if now <= reminder_at < now + datetime.timedelta(minutes=1):
start_date = get_evt_start_date(evt).strftime("%m-%d %H:%M")
add_notification(f"{start_date}<br>{evt.summary}<br>일정이 {30}분 후 시작합니다.",
expire=now.timestamp() + expire, display_time=display_time, show_notiboard=False)
임의의 초에 1분마다 실행되는 것이기 때문에 (현재시각 <= 리마인더시각 < 현재시각 + 1분) 범위로 잡아야 알림을 빠짐없이 잡을 수 있습니다.
다음은 학사·학과공지 코드로 알림을 구현할 예정입니다.
안드로이드 소개
안드로이드(Android)는 리눅스를 기반으로 구글에서 제작된 모바일 운영체제입니다. 흔히
우리나라 휴대폰 시장에서는 삼성 – 애플 – 화웨이 등 독자적인 os인 ios를 탑재중인 애플
외에는 안드로이드를 채택하여 제작 출시후 판매중입니다. 2008년 처음 출시 후 계속 새 버전
을 내놓아 전 세계 모바일 플랫폼 시장에서 많은 비율로 차지하고 있습니다.
안드로이드의 특징
1. 안드로이드는 공개 운영체제인 리눅스를 기반으로 합니다.
2. 안드로이드 앱은 Kotlin or Java를 이용해 개발합니다.
3. 안드로이드 운영체제의 주요 부분과 라이브러리, 구글에서 만든 앱 등의 코드는 대부분
공개 되어 있습니다.
4. 안드로이드 스마트폰은 구글 뿐만 아니라 여러 제조업체에서 만들수 있습니다.
(위에 안드로이드 소개 문단 참조)
5. 안드로이드 앱은 다양한 방법으로 사용자에게 배포가 가능합니다.
안드로이드 운영체제의 구조
[리눅스 커널] 안드로이드는 리눅스에 기반을 둔 오픈소스 소프트웨어 스택입니다. [하드웨어 추상화 레이어] 하드웨어의 추상화 계층으로, 상위의 자바 API 프레임워크에서 하드웨어 기능을 이용할 수 있게 표준 인터페이스를 제공합니다. [안드로이드 런타임] 흔히 ART라고 하며 앱을 실행하는 역할을 합니다. [네이티브 C/C++ 라이브러리] 안드로이드 앱은 대부분 자바 프레임워크로 개발하지만 네이 티브 C/C++ 라이브러리를 이용할수 있는데 이를 안드로이드 NDK라고 합니다. [자바 API 프레임워크] 앱을 개발할때 사용하는 자바 API 입니다.
앱 개발 언어
원래 안드로이드가 초기에 나왔을때 부터는 Java로 앱 개발을 해왔었는데 2017년 구글 IO 행사에서 Kotlin을 안드로이드 공식 언어로 지정하면서 이제 안드로이드 개발 언어로는 Java와 Kotlin을 사용합니다.
다양한 디바이스
ios os를 탑재한 아이폰은 애플에서만 제작할수 있지만 안드로이드폰은 다양한 제조업체에서 만들 수 있습니다. (삼성전자, 화웨이 등) 사용자는 다양한 휴대폰을 접할수 있어서 장점이지만 개발자는 주의해야할점이 있습니다.
다양한 제조업체에서 출시된 휴대폰에서 앱이 똑같이 보이도록 호환성을 고려해야 합니다. 또한 제조업체에서 휴대폰을 생산할 때는 구글에서 만든 운영체제 API와 주소록 같은 기본앱 등을 조금씩 바꾸므로 자신이 만든 앱이 여러 장치에서 제대로 동작하는지를 주의 및 점검해야 합니다.
안드로이드 버전
구글은 2008년 안드로이드 1.0 버전을 출시한 이후 새로운 버전을 출시하고 있습니다. 새 버전을 출시하면서 새로운 기능이 추가되거나, 기존 API가 변경 또는 제거되는 등 앱 개발에 영향을 미치는 변화가 뒤따랐습니다. 따라서 새로운 안드로이드 버전이 나오면 개발자는 변경사항을 파악해서 앱에 적용해야 합니다.
또, 안드로이드 버전은 11.0 12.0처럼 운영체제 버전을 가리키지만 앱을 개발할 때 사용하는 버전은 API 레벨(SDK 버전)입니다. 운영체제 버전별로 API 레벨이 지정돼 있어서 소스 코드에서는 대부분 API 레벨을 이용합니다. 따라서 개발자는 운영체제 버전과 API 레벨을 함께 알고 있어야 합니다.
코드명은 안드로이드 버전의 별칭으로 사용하는데, 2012′ Android Jelly Bean , 2014′ Android Lollipop 등 코드명으로 부릅니다. 그런데 구글에서는 안드로이드 10.0 버전부터 코드명을 사용하지 않겠다고 선언했습니다.
-> Q) 새로운 버전이 나오더라도 이전 버전과 호환이 될텐데 새로운 내용을 앱에 적용할 필요
가 있는가?
-> A) 이전 버전에서 사용한 API가 변경될수 있기때문에 새로 추가된 API를 적용하지 않으면
새버전을 탑재한 휴대폰에서 앱이 동작하지 않을수도 있기 때문입니다.
안드로이드 앱 개발의 특징
컴포넌트가 제일 중요합니다. 안드로이드 앱 개발의 구조를 이해하려면 컴포넌트가 무엇이고 어떻게 동작하는지 반드시 알아야 합니다.
컴포넌트는 애플리케이션의 구성요소
컴포넌트를 쉽게 정의하자면 애플리케이션의 구성요소라고 할수 있습니다.
컴포넌트는 애플리케이션을 구성하는 단위입니다. 즉, 하나의 애플리케이션은 여러 컴포넌트로 구성됩니다. 컴포넌트가 어떤 형태인지는 상황에 따라 달라지는데 JAR 파일이나 DLL로도 개발합니다.
안드로이드 앱의 기본구조도 컴포넌트에 기반을 두므로 하나의 앱은 여러 컴포넌트로 구성됩니다. 그리고 안드로이드에서는 클래스로 컴포넌트를 개발합니다. 즉, 하나의 클래스가 하나의 컴포넌트가 되는것입니다.
안드로이드 컴포넌트 종류
[액티비티] 화면을 구성하는 컴포넌트 [서비스] 백그라운드 작업을 하는 컴포넌트 [콘텐츠 프로바이더] 앱의 데이터를 공유하는 컴포넌트 [브로드캐스트 리시버] 시스템 이벤트가 발생할 때 실행되게 하는 컴포넌트
구분방법
컴포넌트는 앱이 실행될 때 안드로이드 시스템에서 생명주기를 관리하는 클래스지만 개발자가 만들어야하는 클래스입니다. 개발자가 컴포넌트 클래스를 만들 때는 지정된 클래스를 상속받아야 하는데 이 상위 클래스를 보고 구분할 수 있습니다.
액티비티는 Activity
서비스는 Service
콘텐츠 프로바이더는 ContentProvider
브로드캐스트 리시버는 BroadcastReceiver 클래스를 상속받아서 만듭니다.
앱 개발할 때 컴포넌트 구성
컴포넌트는 개발자가 만들고자 하는 앱의 기능과 화면 등을 고려해 필요한 만큼 구성하면 된다. 앱을 개발할 때 어떤 컴포넌트를 어떻게 구성하는지는 설계에 따라 달라지며 정해진 규칙은 없습니다. 심지어 액티비티가 없는, 그래서 사용자에게 화면을 제공하지 않는 앱도 개발할 수 있습니다.
◆ 작업 환경
◆ 참고 사항
Controller, Service, Repository 패턴
Controller, Service, Repository 패턴은 정형화 되어있음
Controller = 외부 요청을 받음
Service = 비즈니스 로직을 만듦
Repository = 데이터 저장
Test 시, given, when, then 패턴을 사용하는 것에 익숙해지자
◆ Simple Member Manage System
컴포넌트 스캔, 자동 의존관계 설정 방식 사용
Controller
HomeController
MemberController : 회원 가입 / 조회 기능 제공
MemberForm : 회원 가입 관련 Form 제공
Service
MemberService : 회원 정보 등록 / 조회
Repository
MemberRepository : Interface (회원 등록, 이름으로 조회, ID로 조회, 전체 조회)
MemoryMemberRepository
Domain
Member : 회원 ID, Name 저장
Application
SpringApplication
Templates
home.html
/members/createMemberForm.html : 회원 가입
/members/memberList.html : 회원 조회
TestCase
Repository TestCase
Service TestCase
Application TestCase
◆ 처음부터 다시 만들어본 과정 브리핑
controller, domain, repository, service 디렉토리 만듦
1. Home.html과 HomeController 작성
(@Controller, @GetMapping(“/”), @Autowired)
2. Member domain 작성
– 저장할 회원 정보 private로 멤버 생성 (Getter, Setter)
3. MemberRepository interface 작성
( save, findById, findByName, findAll )
4. 위 interface를 implements한 MemoryMemberRepository 작성
– @Override 자동 생성
– <Long, Member>를 저장할 HashMap type의 store 객체 생성
– Id는 단순 Long ++sequence로 대체
– save(Member) : 받아온 객체에 Id를 ++sequence로 부여, HashMap에 .put( , )
– findById(Id) : return Optional.ofNullable(store.get(id))
– findByName(Name) : return store.values().stream()
.filter(member -> member.getName().equals(name))
.findAny();
– findAll() : return new ArrayList<>(store.values())
– clearStore() : store.clear(); // test를 위한 store 비우기
5. MemberService 작성 (controller에서 repository의 메서드에 직접 접근 할 수 없도록 중간자 역할)
– 생성자로 memberRepository와 연결 (@Autowired)
– findByName()을 통한 중복 회원 검증 메서드 작성 (IllegalStateException)
– join(Member) : 회원가입 (중복 검증 메서드 포함)
– findMembers() : 전체 회원 조회
– findOne(Id) : 해당 Id의 회원 조회
6. MemberController 작성
– 생성자로 memberService와 연결 (@Autowired)
* 회원 가입
– @GetMapping createForm() : 회원가입 html로 이동
– html에서 사용자가 작성한 정보를 받아올 MemberForm 작성 (private String name + Getter/Setter)
– @PostMapping create(Member) : new Member – setName – join – “redirect:/”(Home 화면으로)
* 회원 조회
– @GetMapping list(Model) : service의 findMembers()를 통해 repository의 전체 회원 조회
– service의 findMembers()를 통해 List<Member>를 받아옴
– model에 addAttribute( , )를 통해 받아온 list<>를 추가
– thymeleaf의 th:each문 : for문과 유사하게 받아온 model의 list 인덱스를 반복
– 회원을 조회할 수 있는 html문 작성
◆ 이번주 IntelliJ 단축키
– Extract Variable = Crtl + Alt + V (저장 객체 자동 작성)
– Extract Method = Crtl + Alt + M (드래그를 범위 method로 작성)
– Declaration or Usage = Crtl + B (정의된 method로 이동)
– Creat Test = Ctrl + Shift + T (Test Case 자동 생성)
– 주석 처리 = Ctrl + /
– ReRun = Shift + F10
– Constructor = Alt + Insert (생성자 등 여러가지 자동 생성)
◆ 그 외 사항
포스팅 계획 – 3월 : 스프링 / 4월 : 정처기 실기(+중간) / 5월 : 스프링 / 6월 : 졸업 작품(+기말) / 7월 : 스프링 / 8월 ~ : 알고리즘과 CS를 포함한 미정
정적 팩토리와 생성자에는 동일한 제약 조건이 있다. -> 선택적 매개변수가 많을 때 적절히 대응하기 어렵다.
매개 변수가 많아질 경우 사용할 수 있는 세 가지를 고려해 볼 수 있다.
(1) Telescoping constructor pattern
필수 매개변수와 선택 매개변수를 갖는 생성자의 형태를 띤다. 아래에 예시를 나타내겠다.
필수 매개변수만 갖는 생성자 필수 매개변수 + 하나의 선택 매개변수 생성자 필수 매개변수 + 두 개의 선택 매개변수 생성자 ... ...
위와 같이 필수 매개변수만 갖는 생성자를 생성할 수 있고, n개의 선택 매개변수를 생성하는 생성자를 함께 갖는 경우의 방식이다.
예제) 식품의 영양 정보를 표현하는 클래스를 생각해보자, 식품의 양, 개수, 칼로리, 총 지방, 포화 지방 등 20개 이상의 선택 필드를 가질 수 있다. 몇 가지만 값을 가지고 대부분은 0의 값을 가진다. 생성자를 어떻게 만들까?
public class NutritionFacts{ private final int servingSize; //required private final int servings; //required private final int calories; //optional private final int fat; //optional private final int sodium; //optional private final int carbohydrate; //optional public NutritionFacts(int servingSize, int servings){ this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories){ this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat){ this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium){ this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate){ this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; } } NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
위와 같이 오버로딩을 사용하여 나타내는 방식이 점층적 생성자 패턴이라고 한다.
대신 위와 같은 방법을 사용하면 원하지 않은 변수에도 초기값을 설정해야 한다.
오버 로딩 -> 같은 이름이지만 변수의 개수 또는 타입이 달라 여러 개를 정의할 수 있다.
단점
위처럼 점층적 생성자 패턴도 쓸 수 있지만, 매개변수의 개수가 많아지면 클라이언트 코드 작성이 힘들고 가독성이 떨어진다.
저 생성자 코드를 보면 내가 무엇에게 값을 주는지 알기 어렵고 어떤 파라미터에 값을 입력하는지 주의해서 봐야 한다.
또한, 동일한 타입의 매개변수가 늘어져 있다면 찾기 어려운 버그로 이어질 수 있다.
클라이언트가 실수해서 다른 생성자를 선택하거나, 알아채지 못할 수 있다.
(2) JavaBeans pattern
매개변수가 없는 생성자로 객체를 만든 후, Setter 메서드를 이용하여 원하는 값을 설정하는 방식
public class NutritionFacts{ private int servingSize = -1; //required private int servings = -1; //required private int calories = 0; //optional private int fat = 0; //optional private int sodium = 0; //optional private int carbohydrate = 0; //optional public NutritionFacts(){} public void setservingSize (int val) { servingSize = val }; public void setservings (int val) { servings = val }; public void setcalories (int val) { calories = val }; public void setfat (int val) { fat = val }; public void setsodium (int val) { sodium = val }; public void setcarbohydrate (int val) { carbohydrate = val }; }
위처럼 아까 Telescoping constructor pattern 보다는 생성자 코드가 길지만 보기는 쉽게 변한다는 장점이 있다.
어떻게 매개변수에 setter 메서드가 존재하기에
cocaCola.setServingSize(240); 과 같이 값을 지정해주면 된다.
단점
1. 여러 메소드 호출로 나누어져 인스턴스가 생성되므로, 생성 과정이 이뤄지는 동안 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓기에 된다.
2. 클래스를 불변으로 만들 수 없어서 프로그래머가 추가 작업을 해주어야 한다.
(3) Builder pattern
-> 점층적 생성자 패턴의 안전성 + 자바 빈즈의 가독성
장점
1. 작성이 쉽고 가독성이 좋다. -> 롬복을 이용하면 더 쉽게 된다.
2. 불변 규칙을 이용할 수 있고, 검사 또한 가능하다. -> IllegalStateException을 통해 예외 처리 가능하다.
단점
1. 어떤 객체를 생성하기 위해 빌더를 만들어야 가능하기에 성능상 문제가 될 수 있다.
2. 매개 변수가 4개 이상이 될 경우 사용하는 것이 좋다. Telescoping 보다 더 긴 코드가 생성될 수 있다.
-> 그래도 Builder pattern을 염두에 두고 코드를 구현해라.
public class NutritionFacts { private final int servingSize; private final int servings; private final int calories; private final int fat; private final int sodium; private final int carbohydrate; public static class Builder { private final int servingSize; // 필수 private final int servings; // 필수 private int calories = 0; private int fat = 0; private int sodium = 0; private int carbohydrate = 0; public Builder(int servingSize, int servings) { this,servingSize = serginsSize; this.servings = servings; } public Builder fat(int val) { fat = val; return this; } public Builder sodium(int val) { sodium = val; return this; } public Builder carbohydrate(int val) { carbohydrate = val; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } private NutirionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.fat; carbohydrate = builder.carbohydrate; } } NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8) .calories(100) .sodium(35) .carbohydrate(27) .build();
생성자나 정적 팩토리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바 빈즈보다 훨씬 안전하다.
하드웨어를 구성했으니 웹 서버를 만들 차례입니다.
FastAPI 와 Jinja2 템플릿을 사용하여 빠르게 만들겠습니다.
FastAPI: https://fastapi.tiangolo.com/ko/
레이아웃은 아래와 같이 할겁니다
눈이 부시면 안 되기 때문에 다크 테마로 적용시켰고 미래 일정까지 다 보이도록 일정 공간을 넓게 잡았습니다. 그리고 매주 금요일마다 서울에 다녀와야 해서 예매 까먹는걸 방지하기 위해 예매 내역을 조회할 수 있는 칸을 만들었습니다.
그리고 웹 페이지기 때문에 주기적으로 데이터를 변경해줘야 합니다. 아래 코드로 120초마다 페이지를 새로 고칩니다.
function reloadFunction() {
window.location.reload();
}
let timeout = window.setTimeout(reloadFunction, 120 * 1000);
이제 데이터를 만들어 넣어야 합니다.
파이썬 srt 라이브러리로 티켓 가져오는 코드를 짭니다.
def srt_get_reservations() -> List:
srt = SRT(srt_id=srt_id, srt_pw=srt_pw)
reservations = srt.get_reservations()
return reservations[0:3]
레이아웃이 깨지지 않게 하려면 3개까지 보여지게 합니다.
페이지가 120초마다 새로고침 되므로 위 코드도 계속 실행되면 SRT 서버에 부하를 발생시키고 심하면 차단당할 것입니다. 따라서 적절하게 캐시를 사용해야 합니다. 함수 실행결과를 disk 에 캐시해주는 라이브러리인 diskcache 를 사용하였습니다.
from diskcache import FanoutCache
cache = FanoutCache("./.cache/memoize")
@cache.memoize(expire=60 * 30)
def srt_get_reservations() -> List:
...
위와 같이 어노테이션 한줄만으로 쉽게 캐시할 수 있습니다.
마찬가지로 구글 캘린더도 gcsa 라이브러리를 사용하여 작업합니다. 타임존 관련으로 어려운 부분이 있었는데 이 부분은 따로 포스팅 하겠습니다.
다음은 일정이 임박하면 소리와 함께 알려주는 코드를 작성해 보겠습니다.
아이슬란드 상위 동아리인 MiscThings의 각종 활동을 준비하기 위해 서류 작업 및 초기 활동을 정리하였습니다.
현재 계획하고 있는 활동 중 아래 3가지의 활동을 준비하였습니다.
Pass를 만들 때 사용되는 주요 정보는 다음과 같습니다.
해당 코드는 예시로 Pass를 제작할 때 사용했던 코드이며 SM Ent. 세계관을 인용하여 만들었습니다. 에스파 사랑해
저기에 들어가는 정보들을 이제 원본 승차권에서 받아와서 Pass로 만드는게 목표
승차권 예매 플랫폼별로 표시되는 정보도 달라서 이 점 감안하여 만들어야 할 것 같습니다.
pass.barcodes = [{message : "20200208600351000461620200208164002111", format : "PKBarcodeFormatQR", messageEncoding : "iso-8859-1", key: "passCode"},];
pass.primaryFields.add({label: 'Pangyo', value: '판교', key: "origin"});
pass.primaryFields.add({label: 'Kwangya', value: '광야', key: "destination"});
pass.secondaryFields.add({key: 'time', value: '3:40', label: '소요시간'});
pass.auxiliaryFields.add({key: 'date', value: '01/14 04:00', label: '출발일자'});
pass.auxiliaryFields.add({key: 'grade', value: '심야우등', label: '등급'});
pass.auxiliaryFields.add({key: 'gate', value: '1', label: '승차홈'});
pass.auxiliaryFields.add({key: 'seat', value: '01', label: '좌석번호'});
pass.transitType = ('PKTransitTypeBus')
학기중 수강한 프로그래밍 언어(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까지 준비했으므로 앱을 실행해 보겠습니다. 툴바에서 실행할 앱을 선택하고 오른쪽에서 앱을 실행할 기기를 선택하고 재생버튼을 클릭하면 앱을 빌드한 후 실행합니다.
에브리타임에서 시간표를 불러오는 알고리즘을 조금 개선 하였습니다.
파이썬으로 제작하였고 먼저 에브리타임의 시간표를 XML 형식으로 파싱해주는 코드는 다음과 같습니다.
def get_timetable(path):
xml = requests.post(
"https://api.everytime.kr/find/timetable/table/friend",
data={
"identifier": path,
"friendInfo": 'true'
},
headers={
"Accept": "*/*",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Host": "api.everytime.kr",
"Origin": "https://everytime.kr",
"Referer": "https://everytime.kr/",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
}).text
soup = BeautifulSoup(xml, 'lxml')
data_list = soup.find_all('data')
return data_list
Request 를 이용하여 에브리타임 API에 요청하면 에브리타임의 시간표 URL 에서 시간표만 반환합니다. 반환된 시간에서 시간 정보만 가져올 수 있도록 다시 리스트로 반환 하여 temp 리스트에 넣어둡니다.
def extract_time_data(data_list):
for i in data_list:
return time_list.append([int(i['day']),int(i['starttime']),int(i['endtime'])])
반환된 시간표 정보들을 이용하여 빈 시간을 가져 와야 함으로 빈 배열을 생성하여 임시로 지정합니다.
TIME_TABLE_ARRAY = [[0 for i in range(0,288)] for i in range(0,5)]
빈 배열의 시간표와 추출한 시간표를 이용하여 빈 배열에 시간표 시간을 추가하여 빈 시간을 확인할 수 있도록 합니다.
for time in time_list:
for i in time:
for y in range(i[1], i[2]):
TIME_TABLE_ARRAY[i[0]][y] += 1
TABLE_ARRAY 배열에서 0이면 서로의 시간이 빈 시간이며 1 이상이면 겹치는 시간 입니다. 전체적으로 시간을 반환하는 코드는 아래와 같습니다.
def extract_time_data(data_list):
for i in data_list:
return time_list.append([int(i['day']),int(i['starttime']),int(i['endtime'])])
def get_timetable(path):
xml = requests.post(
"https://api.everytime.kr/find/timetable/table/friend",
data={
"identifier": path,
"friendInfo": 'true'
},
headers={
"Accept": "*/*",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Cache-Control": "no-cache",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
"Host": "api.everytime.kr",
"Origin": "https://everytime.kr",
"Referer": "https://everytime.kr/",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36"
}).text
soup = BeautifulSoup(xml, 'lxml')
data_list = soup.find_all('data')
return data_list
time_list = []
table_id_list = ['','','']
for table_id in table_id_list:
time_list.append(extract_time_data(get_timetable(table_id)))
TIME_TABLE_ARRAY= [[0 for i in range(0,288)] for i in range(0,5)]
for time in time_list:
for i in time:
for y in range(i[1], i[2]):
TIME_TABLE_ARRAY[i[0]][y] += 1
for day in range(5):
test = TIME_TABLE_ARRAY[day][108:216] # 09:00 ~ 18:00
empty_time = []
for index, value in enumerate(test):
if value < 1:
empty_time.append(index)
increment = 5
idx = empty_time[0]
result = []
for i in range(0, len(empty_time) - 1):
if empty_time[i+1] - empty_time[i] == 1:
increment += 5
else:
result.append([idx, increment])
increment = 5
idx = empty_time[i+1]
result.append([idx, increment])
중간에 의미없는 리스트 배열이 있는거 같아 다음주는 조금더 개선해 보고자 합니다.
안건우 선생님 헬프 개선 “해줘”