[안드로이드 스튜디오] Ch13. 플래그 & 핸들러 (Flag & Handler)
2019년 9월01일 (일)
창업의 길로 가기 위한 두번째 걸음, [안드로이드 스튜디오]로 향해보자!
*질적으로 많이 부족한 글입니다. 지적 및 조언 환영합니다.
*이 글에선 "안드로이드 스튜디오"를 줄여서 "안스" 라고 표현합니다. 참고 바랍니다.
우선, 본인은 학원에서 'JAVA' 기초 수업을 선수강했다. 안드로이드 스튜디오를 배우기 위해선 피할 수 없는 언어이기 때문이다.
잡담: 이번주 토요일은 애니메이션 '원피스'와 보냈다. 그래서 프로그래밍 복습을 오늘 (일요일 오후)에나 하게되었다.. 내일부터는 또 개강인데 벌써부터 피곤과 걱정이 밀려온다. 그나저나 원피스속 루피의 기어 세컨드,, 너무 멋있어..
이번글에서는 바로 바로 저번 포스팅에서 배운 intent에서 flag라는 메소드가 어떤 방면에서 사용될수있는지 알아본다.
그 다음으로는 handler에 대해서 깊게 배워본다.
<차례>
1. MyFlagActivity (FLAG_ACTIVITY)
2. MyHandler (handler)
1. MyFlagActivity (FLAG_ACTIVITY)
1.1 결과
설명: 매우 간단한 UI이다. TextView1개, EditText란 1개, 버튼2개로 첫화면을 구성하고 NEXT버튼 클릭시 Sub라는 제목으로 다시 이전화면으로 돌아갈수있도록 해주는 'Prev'버튼만 추가해주면 된다. 그렇다면 이 프로젝트를 하는이유는? 당연히 intent의 복습이..... 땡!
intent에 대해서 배우고 싶다면 밑의 링크를 이용하자
(https://lepenseur-blog.tistory.com/16)
[안드로이드 스튜디오] Ch12. 다이얼로그 & 인텐트 (Dialog & Intent)
2019년 8월30일 (금) 창업의 길로 가기 위한 두번째 걸음, [안드로이드 스튜디오]로 향해보자! *질적으로 많이 부족한 글입니다. 지적 및 조언 환영합니다. *이 글에선 "안드로이드 스튜디오"를 줄여서 "안스" 라..
lepenseur-blog.tistory.com
이 프로젝트를 하는 진짜 이유는 바로 FLAG_ACTIVITY 때문이다.
이전글의 거의 맨 마지막 부분에서 필자는 이런 의미심장한 말을 한적이 있다.
"(아무런 장치없이) 이전과 다음의 연속되는 로직은 그 데이터가 쌓여 위험할수있다."
그렇다. 바로 이 부분.
Intent 메소드를 그냥 이용할 경우에는 아주 커다란 문제점에 직면하게 되는것이다.
짠!
잉? 갑자기 왠 카드?
바로 저 쌓인 카드가 Intent를 독립적으로 사용했을경우 생기는 문제점의 핵심, 즉 쌓이는 위험이다.
프로그램은 데이터를 제어하는 방법을 말해주지 않으면 전-혀 모른다. 즉, intent는 다음 뷰로의 이동 메소드인데, 이 이동기능을 제어할줄 모른다면? 당연히 [다음-이전-다음-이전-다음-이전-다음-이전] 이라는 끝없는 뫼비우스의 저주에 걸리고 만다.
이렇게 된다면 뒤로가기를 통해 어플을 나가고싶을때 당연히 아~주 힘들다 (...)
그래서 이러한 상황을 막아주기 위한 우리의 구원자가 FLAG_ACTIVITY 라는것!
바로 코드로 달려가보자!
1.2 코드
하지만 본격적인 코드에 들어가기 앞서, 큰그림을 살펴보고 들어가려한다.
안스는 이러한 과정의 연속이다 [디자인 -> 기능 -> *엑티비티 선언]
*엑티비티 선언: 그냥 AndroidManifest에서 생성한 Activity를 추가하는걸 말한다.
그리고 이 안에서 또 나뉘어진다. [초기 UI 디자인 -> 서브 UI디자인 -> 초기 UI 기능 -> 서브 UI 기능 -> 엑티비티 선언]
이 점을 유의해서 밑의 코드를 읽어보자.
**아직 나는 한참 멀었지만, 감히 예상을 해보자면... 아마 대형 프로젝트 (시중에 내놓을 어플) 제작시에도
모든 UI (메인부터 세부적인 서브들 까지) 전부 디자인 해놓은 상태에서 기능 파트로 들어가지 싶다.
물론 그 UI디자인도 기능적인부분, 즉 자바 언어를 잘 숙지해있는 디자이너가 만들것이라고 생각한다.
자, 이제 진짜 코드로 간다.
1.2.1 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="25dp"
android:text="Main"
android:gravity="center"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/btn_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Next"/>
<Button
android:id="@+id/btn_exit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Exit"/>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
설명무
1.2.2 activty_sub.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sub"
android:textSize="25dp"
android:gravity="center"/>
<Button
android:id="@+id/btn_prev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Prev"
android:layout_gravity="right"
android:layout_marginEnd="20dp"/>
</LinearLayout>
설명무
1.2.3 MainActivity.java
package com.example.myflagactivity;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private Button btn_next, btn_exit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_next = (Button)findViewById(R.id.btn_next);
btn_exit = (Button)findViewById(R.id.btn_exit);
btn_next.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this,SubActivity.class);
// intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// SUB에 설명 참고
startActivity(intent);
}
});
btn_exit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
System.exit(0);
}
});
}
}
설명: 이전과 다름없이, [변수선언 -> 객체화 -> 기능추가] 과정이다.
응? 그럼 우리들의 FLAG는 언제..?
1.2.4 SubActivity.java
package com.example.myflagactivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class SubActivity extends Activity {
Button btn_prev;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sub);
btn_prev = (Button)findViewById(R.id.btn_prev);
btn_prev.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(SubActivity.this,MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
// FLAG? '화면제어' 정도로 이해하자.
// 밑의 두 ACTIVITY 가 가장 많이 사용된다.
// Intent.FLAG_ACTIVITY_CLEAR_TOP?
// ACTIVITY STACK 에서 호출하려는 ACTIVITY 의 인스턴스가 이미 존재하고 있을경우
// 새로운 인스턴스를 생성하는것이 아니라 이미 존재하고 있는 ACTIVITY 를 가장 앞으로 가져온다.
// 최 상단의 ACTIVITY 로 부터 가장앞으로 가져올때 호출 인스턴스의 모든 ACTIVITY 를 삭제한다.
// Intent.FLAG_ACTIVITY_SINGLE_TOP?
// ACTIVITY 를 호출할 경우 ACTIVITY 가 현재 테스크 최 상단에 존재한다면 새로운 인스턴스를 호출하지 않는다.
startActivity(intent);
}
});
}
}
설명: 아앗! 나왔다! 보이는가? Intent를 재정의하고 startActivity로 마무리하는 그 중간에 위치해있는 FLAG가!
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
중요하기때문에 일부러 때왔다.
FLAG_ACTIVITY도 여러 종류가 있지만, 그중에서도 가장 많이 사용되는 CLEAR_TOP과 SINGLE_TOP에 대해서만 간단히 알아보자.
(1) CLAER_TOP
위 사진CLEAR_TOP을 설명할수있는 가장 적합한 사진이다. E라는 엑티비티에서 C를 호출할경우, C가 가장 상단으로 올라오면서 그 위에 있던 E-D는 삭제가 되는 FLAG다.
(2) SINGLE_TOP
위 사진은 SINGLE_TOP을 가장 잘 설명해준다. 싱글탑, 즉 "탑에는 하나만 올수있다!" 라고 짐작 할수있듯이, 실제로도 이 기능은 맨위에 하나의 ACTIVITY만이 올수있다. 바꿔서 말하면, 중복되는 ACTIVITY는 올수없는것이다. 그 예시로 위 사진5에서 두번째와 세번째 과정을 잘 살펴보자. Activity2를 두번 불러왔는데도 불구하고 가장 위에는 하나의 Activity2만 올라와있다.
하지만, 바로 다음 과정을 보면 맨밑에 Activity1이 있음에도 불구하고 바로밑의 Activity2와 중복되지 않는다는 이유로 또하나의 Activity1은 올라올수있다.
1.2.5 AndroidManifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myflagactivity">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SubActivity"/>
</application>
</manifest>
설명: 화룡정점. manifest로 activity를 선언한다.
2. MyHandler
2.1 결과
설명: 3초라는 시간이 사용자에게 주어지는데, 이 시간안에 뒤로가기 버튼을 두번 터치해야만 뒤로갈수있는 재미있는 기능 추가시킨것이다.
우선 버튼을 한번 터치하면 시간이 카운팅되고 동시에 Toast를 통해 "뒤로가기 한번더" 라는 경고 메시지가 나타난다.
근데... 어떻게 만드는가?
이를 위해 필요한 메소드가 바로 Handler다!
Q.Handler란?
- 웹을 위한 자바에서의 Thread와 비슷한 의미로, ACTIVITY 와 순환 주기와는 별개로 독립적인 작업을 반복하는 클래스를 의미한다.
- 쉽게 말하자면 동시에 두개의 독립된 다른행동을 취할때 필요한것이 바로 이 handler라는 의미다.
2.2 코드
2.2.1 activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txt_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3"
android:textSize="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.19999999" />
</androidx.constraintlayout.widget.ConstraintLayout>
설명: 특별한 디자인은 없다. 시간을 나타낼 텍스트뷰 하나만 만들어 주자. 물론 id생성은 필수!
2.2.1 MainActivity.java
package com.example.myhandler;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.widget.TextView;
import android.widget.Toast;
// 프로젝트 목표: 뒤로가기버튼을 두번눌렀을때 종료시키기
// 단, 3초이내로! (여기서 handler 사용됨)
public class MainActivity extends AppCompatActivity {
private TextView txt_count;
private int num = 3; // textVIew 의 내용을 갱신하기위한 변수
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txt_count = (TextView)findViewById(R.id.txt_count);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode){
// 뒤로가기 버튼을 눌렀을때
case KeyEvent.KEYCODE_BACK:
// 3초가 아닐때
if (num != 3){
// 시스템을 종료하라
System.exit(0);
}else{
Toast.makeText(getApplicationContext(),"뒤로가기 한번더!!!",Toast.LENGTH_SHORT).show();
}
//Handler 실행을 해야한다. (물론 그전 준비단계는 return false; 밑에 정의되어있어야 한다.
mHandler.sendEmptyMessage(0);
}
return false;
}
//Handler 정의(멤버변수화)를 먼저 해야한다.
Handler mHandler = new Handler(){
// HandlerMessage 를 구현한다.
// 그게뭔가?
@Override
public void handleMessage(@NonNull Message msg) {
// @NonNull: Null 값을 집어넣을수없다는 뜻.
//1초에 한번씩 핸들러가 반복적으로 수행시키는 메소드
mHandler.sendEmptyMessageDelayed(0,1000);
// () -> 어느 핸들러에 딜레이를 줄것인지 정하라.
if(num>0){
num--;
}else {
num = 3;
//핸들러 종료.
// - 핸들러 종료 메소드가 sendEmptyMessageDelayed보다 위에 있으면
// - 종료하지 못하고 다시 sendEmptyMessageDelayed를 통해 핸들러를 다시 구동해버린다.
// - 따라서 removeMessage 메소드는 항상 sendEmptyMessageDelayed 보다 밑에 두어야 한다.
mHandler.removeMessages(0);
}
txt_count.setText(num+"");
super.handleMessage(msg);
}
};
}