본문 바로가기

카테고리 없음

[안드로이드] 이벤트(Event)

※ 저는 안드로이드 프로그래밍 정복(김상형 著, 한빛미디어) 책을 이용해 공부하고 있으며

예제와 코드는 이 책을 통해 공부중임을 밝힙니다.

개인적인 공부를 하면서 정리한 형식이기 때문에 심각한 오류가 있을 수 있습니다. 피드백 주시면 정말 감사하겠습니다.


※ 안드로이드 프로그래밍 정복(321p~341p) 참조


저는 자바 문법 공부가 매우 미약한 상태이기 때문에, 자바 문법과 같이 병행하면서 작성을 하겠습니다.

책에서는 인터페이스 구현 및 상속, 내부 클래스, 익명 클래스 등 자바의 고급 문법을 숙지하라고 알립니다.


0. 들어가기에 앞서

(이미지 출처 : http://arabiannight.tistory.com/entry/)


핸들러(Handler) : 안드로이드의 시스템은 사용자가 작성한 UI에서 빠른 응답을 요구하고 있다. 만약 5초 이상의 응당이 없을 경우 최악의 경우 작성된 프로그램이 강제로 종료가 되는 경우도 발생할 수 있다. 이런 상황을 방지하기 위해서 시간이 오래걸리는 작업이 필요한 경우 두가지의 방법으로 해결을 할 수 있다. 


첫번째는 시간이 오래걸리는 작업은 서비스로 만들어서 처리하는 방법 


두번째는 새로운 쓰레드로 처리를 하는 방법 

두번째 방법으로 쓰레드를 생성해서 데이터 처리등의 시간이 오래걸리는 작업을 지원하기 위한 클래스가 존재하는데 그것이 핸들러(Handler)이다.



1. 이벤트 핸들러

 1.1 이벤트 처리


① 콜백 메서드 재정의

콜백(Call Back) : 특정 이벤트 발생시 시스템에 의해 자동으로 호출되는 메서

  이 메서드에 코드를 작성함으로써 이벤트 발생시의 동작을 정의한다.


이벤트를 받는 가장 쉬운 방법은 해당 클래스를 상속받아 콜백 메서드를 재정의하는 것이다.


대표적인 콜백 메서드는 다음과 같다.


boolean onTouchEvent (MotionEvent event)                        // 사용자가 화면을 터치

boolean onKeyDown (int keyCode, keyEvent event)              // 키를 누를 때

boolean onKeyUp (int keyCode, KeyEvent event)                  // 키를 뗄 때

boolean onTrackballEvent (MotionEvent event)                    //  트랙볼을 굴릴 때


 

<가운데 튀어나와 있는게 트랙볼(TrackBall)이다. 나도 찾아보면서 처음 알았다. 이런 핸드폰이 있다니!>


사용자가 트랙볼을 굴리거나, 화면을 터치하거나, 키를 누르거나 뗄 떼, 각 상황에 맞는 메서드가 호출이 된다. 사용자가 이런 저런 행동을 하는 것을 이벤트(Event)라고 하며 이에 대한 상세한 정보가 인수로 전달된다. 


위에서 살펴본 메서드를 다시 한 번 보도록 하자.

boolean onKeyDown (int keyCode, keyEvent event)  이 메서드는 "눌러진 키"가 전달된다.

boolean onTouchEvent (MotionEvent event) 이 메서드는 "터치한 화면의 좌표"가 전달된다.


가장 당순한 이벤트인 onTouchEvent를 받아서 처리해보는 코드를 작성해본다.


View 클래스로부터 MyView 클래스를 파생시키고 있다. 사용자와 상호작용하는 주체가 "VIew"이므로

이벤트 콜백은 주로 "뷰(View)"가 제공한다. 그래서 View를 상속받아 콜백메서드를 재정의한다.


이와 같이 콜백 메서드를 재정의하는 방법은 아주 간단하고 직관적이다. 그러나 이 방법에는 몇 가지 단점 및 한계가 있다.


① 메서드를 재정의하기 위해 반드시 슈퍼 클래스를 상속받아야 한다. View의 onTouchEvent 메서드를 수정할 수 없으므로 MyView를 파생시켜야만 원하는 코드를 집어넣을 수 있다. 뷰는 여러 가지 이유로 상속받지만 Button이나 TextView 같은 위젯은 이벤트를 처리하기 위해 일일이 MyButton, MyTextView 클래스를 만들어야 하는 번거로움이 있다.


② 프레임워크(Framwwork)는 자주 발생하는 일반적인 이벤트에 대해 콜백 메서드를 제공하지만 모든 이벤트에 대한 콜백이 다 정의되어 있는 것은 아니다. 터치나 키 입력 외에도 선택 변경, 포커스 이동, 드래그, 진동 센서, 조도 센서 등 다양한 이벤트가 있는데 모든 이벤트에 대해 일일이 메서드를 다 정의할 수 없다.


이러한 이유로 콜백 메서드를 재정의하는 방법은 자주 사용하는 몇 가지 이벤트에만 제한적으로 적용할 수 있으며 그것도 반드시 상속 받아야만 쓸 수 있다. 그래서 범용적이고 간편한 이벤트 처리 방법이 제공된다. 그것이 바로 리스너(Listener)이다.


② 리스너 인터페이스(Listener Interface) 구현


리스너(Listener)는 특정 이벤트를 처리하는 인터페이스이다.

 [ JAVA 복습 : 인터페이스(Interface) ] 

  인터페이스는 "추상메서드" 와 "상수"만을 멤버로 가질 수 있다.

  이때 "추상메서드"는 구현부가 없는 (즉, 로직이 없는) 메서드인데 이를 구현(implements)해야 한다. 


  ① 이어지는 내용에서 인터페이스 (즉, 리스너) 를 구현(implements)하는 클래스를 선언하고 

  ② 추상 메서드에 해당하는 핸들러의 구현부를 작성해주고

  ③ 클래스의 객체를 생성한 뒤

  ④ 리스너가 어떤 이벤트를 처리한다는 것을 "등록"한다는 것으로 내용이 이어진다.

    ※  등록 메서드

    void setOnTouchListener (View.OnTouchListener I)
    void setOnKeyListener (View.OnKeyListener I)
    void setOnClickListener (View.OnClickListener I)
    void setOnLongClickListener (View.OnLongClickListenere I)
    void setOnFocusChangeListener (View.OnFocusChangeListener I)


리스너는 View 클래스의 내부 인터페이스로 정의되어 있으며 대응되는 이벤트를 받는 단 하나의 메서드가 선언되어 있다.


OnTouchListener : boolean onTouch (View v, MotionEvent event)

OnKeyListener : boolean onKey (View v, int keyCode, KeyEvent event)

OnClickListener : void onClick (View v)

OnLOngClickListener : boolean onLongClick (View v)

OnFocusChangeListener : void onFocusChange (View v, boolean hasFocus)

위 메서들은 리스너 ( 인터페이스 ) 에 선언된 메서드들이다. 인터페이스라고 했으니 메서드의 "구현부"가 없다는 걸 알 수 있다. 이 메서드들을 "이벤트 핸들러(Event Handler)"라고 한다.



이벤트를 처리하는 순서는 다음과 같다.


1. 리스너를 구현하는 클래스 선언하고,  추상 메서드를 구현한다.


2. 구현한 리스너 객체를 생성

(class TouchListenerClassr가 인터페이스를 구현)


3. 준비된 리스너 객체를 뷰의 이벤트와 연결



콜백메서드와 달리 View를 상속받을 필요 없다. 리스너는 인터페이스 구현을 위해 별도의 클래스를 하나 더 선언해야 한다는 면에서 번거롭다. 모든 이벤트에 대해 일일이 클래스를 만든다면 소스양도 많아지고 각 클래스마다 이름을 붙이는 것도 보통 일이 아니다.


③ 액티비티가 리스너를 구현


앞에 예제는 인터페이스를 구현하는 별도의 클래스를 따로 정의했다. 이 방법은 기존에 존재하는 클래스가 인터페이스를 구현하는 것이다.

액티비티가 인터페이스를 자체 구현하므로 별도의 클래스를 선언할 필요가 없다. 또한 액티비티 객체가 이미 있으므로 리스너 객체를 생성할 필요도 없다.  메서드를 구현해 놓고 "내가 처리하겠노라"는 의미의 this를 등록 메서드로 전달하면 된다.


장점 : 간편하고 소스의 길이가 짧은 이점이 있다

단점 : 큰 단위에 해당하는 "액티비티"가 하위의 뷰를 위한 메서드를 제공해 구조적이지 못함



④ 뷰가 리스너를 구현

콜백메서드 예제와 비슷해서 헷갈릴 수 있다.

콜백메서드는 boolean onTouchEvent (MotionEvent event) 이고

위 예제는 인터페이스의 추상메서드 boolean onTouch (View v, MotionEvent event) 를 구현해주어야 한다.


코드를 살펴보면 MyView 선언문에 implements 구문이 있어 View.OnTouchListene 인터페이스를 구현한다는 것을 명시(두번째 문단) 했다.


그리고 onCreate 클래스에서는 setContentView로 뷰를 setOnTouchListener로 등록했다. 터치 이벤트가 발생하면 뷰에 등록된 리스너(setOnTouchListener)를 찾고, 이 리스너의 onTouch 메서드를 호출한다.


결국, 자신에게 발생되는 이벤트를 자기 스스로 처리하는 것이다. 이벤트를 처리하는 메서드를 내부에 포함한다는 면에서 구조상 깔끔하고 뷰를 재활용하기도 유리하다.


⑤ 익명 내부 클래스 사용

액티비티 또는 뷰가 리스너를 구현하는 것은 인터페이스를 구현해 줄 적당한 클래스가 있을 때만 가능하다. 그렇다면, 2번 방법을 ( 리스너 인터페이스 구현 ) 다시 살펴보자. 

리스너 인터페이스를 구현하는 것의 문제는 리스너 하나를 위해 클래스를 일일이 만들어줘야 한다는 것이다. 위 코드를 보면 "onTouch" 메서드 구현을 위해서 TouchListener 클래스를 만들어주었다.

그다음 TouchListenerClass 객체를 생성해주었다. 자바 이벤트 처리 방식의 특성상 이벤트 하나당 하나의 객체를 만들어 주어야 한다. 그런데 만들다보면 여러 리스너들이 필요할 텐데, 이렇게 하는 것은 굉장히 번거로운 일이다.


이벤트 처리를 위해서 필요한 것은 "핸들러 메서드" 뿐인데, 메서드가 홀로 존재할 수 없으니 클래스 안에 넣어야 하고 객체를 생성한 후에는 등록해야 한다.


이를 쉽게 처리하기 위해서 자바에서는 "익명 내부 클래스"라는 문법을 제공한다. 클래스를 선언할 필요 없이 상속과 재정의를 동시에 할 수 있다.

new를 사용했다고 해서 TouchListner가 View.OnTouchListener 인터페이스의 인스턴스처럼 보이지만

인터페이스는 구현이 없으므로 객체를 생성하지 못한다. 


이 경우에 TouchListener는 인터페이스를 상속받아 onTouch 메서드를 "내부에서" 구현하는 이름없는 자손(서브) 클래스 타입의 객체이다. 이 경우 객체는 "딱 하나만" 생성할 수 있다.


⑥ 익명 내부 클래스의 임시 객체 사용

가장 함축된 형태의 이벤트 처리 코드이다. 5번 방법(익명 내부 클래스)을 살펴보면 TouchListener 객체를 생성하고 있는데, 6번 방법에서는 이를 setOnTouchListener 호출문 안에 넣어버릴 것이다.

위 코드를 살펴보면, 5번 방법과 달리 객체 이름이 없다. 대신 setOnTouchListener 호출문 안에 임시객체를 만들어 인수로 바로 전달한다.


인터페이스의 이름도 없고, 클래스 객체 이름 또한 없다. 


Class obj = new Class();

Method(obj); 


이렇게 두 줄을 만들 것을


Method ( new Class() ) ; 이렇게 한 줄로 만들어 버린다.


안드로이드에서는 주로 이 방법으로 이벤트 핸들러를 작성한다. 자바에서도 이러한 방법을 많이 사용한다.



1.2 핸들러의 우선순위


① 뷰의 리스너 ( 이벤트 리스너에 처리하기 )

② 뷰의 onTouchEvent 콜백 메서드에 처리하기

③ 액티비티의 onTouchEvent 콜백 메서드에서 처리하기


뷰의 리스너가 가장 작은 범위이고, 이후 뷰, 그다음 액티비티 순이다.

따라서 뷰의 리스너가 가장 먼저 우선순위를 갖는다.


1.3 외부 변수 액세스


핸들러 내부에서 외부의 변수를 액세스하는 방법


onTouch 메서드가 속한 객체는 익명의 리스너 타입이며, onCreate 메서드 내에 선언된 지역 내부 클르시이다.  그래서 리스너 내에서 액티비티의 findViewById 메서드를 호출하여 텍스트뷰 검색이 가능하다.


findViewById 메서드는 리소스를 뒤져 뷰를 찾아내는 복잡한 동작을 하므로 꽤 느리다.

변하지도 않는 값을 매번 화면을 누를 떄마다 조사해야 한다.


이를 수정해보자



텍스트뷰 객체를 mText 멤버로 선언하고 onCreate에서 딱 한 번만 조사한다.

아까처럼 findViewById 할 필요 없이 mText 멤버를 참조하면 된다.


여기서 자바문법이 이용된다.


"내부 클래스(onTouch)는 외부 클래스(Exercise)의 멤버를 자유롭게 참조할 수 있다."


하지만 이 방법도 리스너가 아주 많으면 외부 클래스가 너무 비대해진다.

외부 클래스를 날씬하게 하려면 특정 리스너로 전달할 값은 메서드의 지역변수로 선언해야 한다.



onCreate의 지역 변수로 선언되어 있다. 이때 final 지정자를 꼭 붙여야 한다.


만약에 final을 붙이지 않으면  다른 메서드(onCreate)내에 정의된 내부 클래스(onTouchListener의 익명 서브 클래스)는 final이 아닌 변수를 참조할 수 없다는 에러코드가 발생한다. 지역 변수이니까 onCreate가 리턴되면 사라진다. 이를 방지하기 위해서 final을 붙여 상수로 만들어야 한다.


onTouch 메서드에서 참조하는 outText는 리스너가 등록될 시점의 값을 가지는 상수 객체이다.

outText 변수와 onTouchListener는 생성 시점과 등록 시점이 같지만 존재 기간은 서로 다르다.

outText는 일개 지역변수이지만 onTouch는 new로 동적으로 생성되어 시스템에게 전달할 객체 소속이며

리스너로 등록되므로 사실상 전역적으로 지속된다.


따라서 생명 지속 기간을 일치시켜주는 것이 final 지정자이다.