1. Layout Inflation (레이아웃 인플레이션)
앱이 실행될 때 XML 레이아웃의 내용이 메모리에 객체화되고 객체화된 XML 레이아웃을 소스 파일에서 사용한다.
XML 레이아웃의 내용이 메모리에 객체화 되는 과정을 Inflation(인플레이션)이라고 한다.
* setContentView() : 레이아웃을 메모리에 객체화 하는 메소드
- 인자로 R.layout.activity_main 과 같이 받아서, 해당 레이아웃 XML 파일을 메모리에 객체화한다.
- 화면 전체(메인 화면)에 나타낼 뷰를 지정할 수 있다.
- 화면의 일부분을 차지하는 부분 레이아웃을 객체화할 수는 없다.
- 부분 레이아웃을 메모리에 객체화 하려면 인플레이터를 사용해야 한다.
2. LayoutInflater (부분 화면) 클래스 사용하기
안드로이드는 부분 화면을 객체화 하여 화면에 띄우기 위해 시스템 서비스로 LayoutInflater 라는 클래스를 제공한다.
getSystemService() 메서드를 사용하여 LayoutInflater 객체를 참조할 수 있다.
cf) 시스템 서비스는 단말이 시작되면서 항상 실행되는 서비스이다.
혹은 LayoutInflater.from() 를 이용하여 LayoutInflater 객체를 참조할 수도 있다.
cf) 프로젝트에서 액티비티 추가하기
 |
[app 우클릭] -> [New] -> [Activity] -> [Empty Activity] |
 |
Activity Name을 MenuActivity 라고 입력하고 Finish 클릭 |
 |
MenuActivity로 실습을 할것이므로 Manifest 파일에 첫 실행 액티비티를 위처럼 수정한다. |
public class MenuActivity extends AppCompatActivity {
LinearLayout container;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
container = findViewById(R.id.container);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
LayoutInflater inflater = (LayoutInflater) getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.sub1, container, true);
CheckBox checkBox = container.findViewById(R.id.checkBox);
checkBox.setText("로딩되었어요.");
}
});
}
}
 |
sub1.xml |
 |
activity_menu.xml |
 |
추가하기 버튼을 클릭하면 부분화면이 아래에 추가된다. |
3. 여러 화면 만들고 화면간 전환하기
 |
프로젝트에 MenuActivity를 새로 추가하면 Manifest 파일에 자동으로 추가된다.
label 속성은 화면의 제목, theme 는 대화상자 형태로 액티비티를 설정한다. |
 |
activity_menu.xml 단순하게 버튼 한개만 추가한 형태이다. 이 화면은 대화상자 형태로 표시 될 것이다. |
 |
activity_main.xml
메인 액티비티 역시 버튼 하나로 구성되었다. |
메인 액티비티에서 버튼을 클릭하면, 메뉴 액티비티를 띄울 것이고
메뉴 액티비티에는 돌아가기 버튼이 있어 클릭 시 메인 액티비티로 돌아가게 된다.
 |
MainActivity를 작성하기 앞서 부모 클래스들에게서
상속받은 메서드들 중 하나인 onActivityResult()를 Override 하여 구현한다.
새로 띄운 액티비티로부터 응답을 처리 하기 위한 메소드 이다. |
* MainActivity.java
public class MainActivity extends AppCompatActivity {
public static final int REQUEST_CODE_MENU = 101;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
startActivityForResult(intent, REQUEST_CODE_MENU);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_CODE_MENU) {
Toast.makeText(getApplicationContext(),
"onActivityResult 호출됨. 요청 코드 : " + requestCode +
", 결과 코드 : " + resultCode, Toast.LENGTH_LONG ).show();
if(resultCode == RESULT_OK){
String name = data.getStringExtra("name");
Toast.makeText(getApplicationContext(), "응답으로 전달된 name : " + name, Toast.LENGTH_LONG).show();
}
}
}
}
* 새로 띄울 액티비티(MenuActivity.java)
public class MenuActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v){
Intent intent = new Intent();
intent.putExtra("name", "mike~");
setResult(RESULT_OK, intent);
finish();
}
});
}
}
 |
버튼을 클릭하면 다이얼로그 방식의 MenuActivity가 보여진다. |
 |
메인 액티비티로 돌아가게 되면 응답을 처리하는 메서드가 호출되며 요청, 결과 코드를 확인할 수 있다. Toast가 두개여서 나중것이 가려졌는데 Toast 한개를 주석처리하면 확인 할 수 있다. |
4. Intent (인텐트) 사용하기
인텐트는 앱 구성 요소 간에 작업 수행을 위한 정보를 전달하는 역할을 한다.
* 인텐트의 기본 구성 요소 : 액션(Action) , 데이터(Data)
액션은 수행할 기능이며 데이터는 수행될 대상의 데이터를 의미한다.
액션과 데이터를 이용해 인텐트 객체를 생성하고 필요한 액티비티를 띄어줄 수 있다.
* 인텐트 종류 : 명시적 인텐트, 암시적 인텐트
명시적 인텐트는 클래스 객체나 컴포넌트 이름을 지정하여 호출할 대상을 확실히 아는 경우를 말한다.
암시적 인텐트는 액션과 데이터를 지정하였으나 호출할 대상이 달라질 수 있는 경우를 말한다.
- 암시적 인텐트의 여러가지 속성
- 범주(Category) : 액션이 실행되는데 필요한 추가적 정보
- 타입(Type) : 인텐트에 들어가는 데이터의 MIME 타입을 명시적으로 지정
- 컴포넌트(Component) : 인텐트에 사용될 컴포넌트 클래스 명을 명시
- 부가 데이터(Extra Data) : 추가적인 정보가 들어있는 번들 데이터를 전달
아래 코드는 인텐트에 액션과 데이터를 넣어 다른 앱의 액티비티를 띄우는 코드이다.
 |
단순히 입력상자와 버튼을 만들었다.
입력상자에는 전화번호 포맷 형식의 데이터를 입력한다. |
public class MainActivity extends AppCompatActivity {
EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.editText);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data = editText.getText().toString();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(data));
startActivity(intent);
}
});
}
}
 |
전화걸기 버튼을 클릭했을 때,
자동으로 전화번호 앱이 실행되는 것을 확인할 수 있다. |
아래 두번째 코드는 컴포넌트 이름을 이용해 새로운 액티비티를 띄우는 경우이다.
위 화면 레이아웃에서 id가 button2인 버튼을 하나더 추가하고, 새로운 액티비티(MenuActivity)를 하나 생성한다.
Button button2 = findViewById(R.id.button2);
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
ComponentName name = new ComponentName("org.techtown.samplecallintent",
"org.techtown.samplecallintent.MenuActivity");
intent.setComponent(name);
startActivityForResult(intent,101);
}
});
컴포넌트이름을 지정할 때 클래스 명은 앞에 패키지 경로까지 전부다 적어주어야 한다.
인텐트를 이용해 다른 화면을 띄울 수가 있는데 직접 만든 화면이나, 다른 사람이 만든 앱의 화면을 띄울 수도 있다.
5. 플래그와 부가 데이터 사용하기
startActivity 또는 startActivityForResult 메서드를 여러 번 호출 하게 될 경우 메모리에 여러 개가 중복되어 만들어질 것이다.
따라서 중복된 액티비티는 띄우지 않게 하기 위해 플래그를 사용한다.
액티비티가 만들어질 때마다 액티비티 스택이라는 곳에 차곡차곡 쌓이게 되는데, startActivity 또는 startActivityForResult를 호출하게 되면 이전의 액티비티는 액티비티 스택에 저장되고 새로 만들어진 액티비티가 화면에 보이게 된다.
즉 가장 상위에 쌓인 것부터 화면에 보여지면서 back 시스템 버튼을 눌렀을 때 그 아래의 액티비티가 보여지게 된다.
동일한 액티비티들을 여러 번 실행한다면 동일한 액티비티가 스택에 여러 개 들어가게 되는 문제를 해결해주는 것이 플래그이다.
* 대표적인 플래그들
- FLAG_ACTIVITY_SINGLE_TOP : 이미 생성된 액티비티가 있다면 그 액티비티를 그대로 사용하라는 플래그. 기존에 사용하는 액티비티에서 인텐트 객체만 전달받으려면 onNewIntent()를 오버라이드하면 된다.
- FLAG_ACTIVITY_NO_HISTORY : 처음 이후에 실행된 액티비티는 액티비티 스택에 쌓이지 않는다.
- FLAG_ACTIVITY_CLEAR_TOP : 이것이 설정되어 있는 액티비티 위에 있는 다른 액티비티를 모두 종료하게 된다. 홈 화면과 같이 항상 우선하는 액티비티를 만들때 유용함.
* 인텐트에 객체를 직렬화하여 데이터 보내기
객체를 직렬화 하기 위해서는 데이터를 담고 있는 클래스는 Parcelable 인터페이스를 구현해야한다.
자바의 Serializable 직렬화와 동일한 개념이다.
* Parcelable 직렬화 필수 구현
- CREATOR 상수 정의
- describeContents() 구현
- writeToParcel(Parcel dest, int flags) 구현
import android.os.Parcel;
import android.os.Parcelable;
public class SimpleData implements Parcelable {
int number;
String message;
public SimpleData(int num, String msg){
number = num;
message = msg;
}
public SimpleData(Parcel src){
number = src.readInt();
message = src.readString();
}
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
public SimpleData createFromParcel(Parcel in){
return new SimpleData(in);
}
public SimpleData[] newArray(int size){
return new SimpleData[size];
}
};
public int describeContents(){
return 0;
}
public void writeToParcel(Parcel dest, int flags){
dest.writeInt(number);
dest.writeString(message);
}
}
public class MainActivity extends AppCompatActivity {
public static final int REQUEST_CODE_MENU = 101;
public static final String KEY_SIMPLE_DATA = "data";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
SimpleData data = new SimpleData(100, "Hello Android!!");
intent.putExtra(KEY_SIMPLE_DATA, data);
startActivityForResult(intent, REQUEST_CODE_MENU);
}
});
}
}
public class MenuActivity extends AppCompatActivity {
TextView textView;
public static final String KEY_SIMPLE_DATA = "data";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
textView = findViewById(R.id.textView);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("name", "mike");
setResult(RESULT_OK, intent);
finish();
}
});
Intent intent = getIntent();
processIntent(intent);
}
public void processIntent(Intent intent){
if(intent != null){
Bundle bundle = intent.getExtras();
SimpleData data = bundle.getParcelable(KEY_SIMPLE_DATA);
textView.setText("전달 받은 데이터\nNumber: "+data.number +
"\nMessage:"+data.message);
}
}
}
6. 태스크(Task) 관리 이해하기
태스크는 앱이 어떻게 동작할지 결정하는데 사용된다.
태스크를 이용하면 프로세스처럼 독립적인 실행 단위와 상관없이 어떤 화면들이 같이 동작해야 하는지 흐름을 관리할 수 있다.
프로세스끼리는 정보를 공유할 수가 없다.
따라서 하나의 프로세스에서 다른 프로세스의 화면을 띄우려면 시스템의 도움이 필요하다.
시스템에서 이런 액티비티의 각종 정보를 저장해두기 위해 태스크라는 것을 만든다.
시스템에서는 액티비티들의 정보를 저장해두기 위해 태스트라는 것을 만들고 알아서 관리하지만 직접 제어할 수도 있다.
AndroidManifest.xml 에 액티비티를 등록할 때 태스크도 함께 설정할 수 있다.
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
}
});
}
}
위 코드는 버튼을 클릭할 때마다 자기자신의 액티비티를 스택에 넣는다. 이것은 AndroidManifest.xml 파일에서 activity 태그의 속성에서 android:launchMode="standard" 을 넣어주는 것과 같다.
만약 android:launchMode="singleTop" 으로 설정한다면 태스크의 가장 위쪽에 있는 액티비티는 더 이상 새로이 만들지 않게된다.
이는 5번의 플래그의 FLAG_ACTIVITY_SINGLE_TOP 와 같은 효과이다.
따라서 중복되어 액티비티가 생성되지 않으므로 (onCreate메서드가 실행되지 않음)
인텐트는 onNewIntent()메서드로 전달받으면 된다.
singleTask : 액티비티가 실행되는 시점에 새로운 태스크를 만들게 된다.
singleInstance: 액티비티가 실행되는 시점에 새로운 태스크를 만들면서, 그 이후에 실행되는 액티비티들은 이 태스크를 공유하지 않는다.
7. 액티비티의 수명주기와 SharedPreferences 이해하기
onCreate -> onStart -> onResume -> onPause ->onStop -> onDestroy
앱이 갑자기 중지되어 앱 데이터의 저장과 복원이 필요할 때가 있다.
간단한 데이터를 저장하거나 복원시에는 SharedPreferences 를 사용할 수 있다.
아래 코드는 앱을 실행했을 때 입력상자에 사람 이름을 입력한 상태에서, 앱을 종료한 후 다시 실행했을 때 사람 이름이 그대로 보이도록 만드는 내용이다.
앱이 갑자기 중지 될때 항상 호출되는 메서드는 onPause이다.
앱이 시작될 때 항상 호출되는 메서드는 onResume이다.
즉 중지 시에 데이터를 저장하고 시작할 때 데이터를 복원하면 된다.
public class MainActivity extends AppCompatActivity {
EditText nameInput;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(getApplicationContext(), MenuActivity.class);
startActivity(intent);
}
});
Toast.makeText(this, "onCreate 호출됨.",Toast.LENGTH_LONG).show();
nameInput = findViewById(R.id.nameInput);
}
@Override
protected void onStart() {
super.onStart();
println("onStart 호출됨.");
}
@Override
protected void onStop() {
super.onStop();
println("onStop 호출됨.");
}
@Override
protected void onDestroy() {
super.onDestroy();
println("onDestroy 호출됨.");
}
public void println(String data){
Toast.makeText(this, data, Toast.LENGTH_LONG).show();
Log.d("Main", data);
}
@Override
protected void onPause() {
super.onPause();
Toast.makeText(this, "onPause 호출됨", Toast.LENGTH_LONG).show();
saveState();
}
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, "onPause 호출됨", Toast.LENGTH_LONG).show();
restoreState();
}
protected void restoreState(){
SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
if( (pref != null) && (pref.contains("name"))){
String name = pref.getString("name","");
nameInput.setText(name);
}
}
protected void saveState(){
SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putString("name", nameInput.getText().toString());
editor.commit();
}
protected void clearState(){
SharedPreferences pref = getSharedPreferences("pref", Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.clear();
editor.commit();
}
}