본문 바로가기

Android ᙏ̤̫͚/Android Programming

[Do it 안드로이드 프로그래밍] 둘째마당 04. 여러 화면 간 전환하기

<표현> 속성 메소드 개념

 

둘째마당 04

 

레이아웃 인플레이션

  • XML 레이아웃은 단순히 XML로 정의된 파일로, 화면의 배치와 구성만 정의
  • 안드로이드 앱은 화면 배치 담당 XML 레이아웃 파일과 화면의 기능 담당 소스 코드 파일 분리하여 개발해야 함
  • MainActivity.java
    • MainActivity 클래스는 AppCompatActivity 상속
      • AppCompatActivity: 화면에 필요한 기능/메소드 들어있음
    • setContentView(R.layout.xml레이아웃파일명): XML 레이아웃 파일 이름 파라미터로 전달하여 레이아웃과 소스 코드 연결
      • R: res폴더, layout: res/layout폴더Inflation
      • 두 가지 역할 수행
        • 화면에 나타날 뷰 지정
        • 레이아웃 내용을 메모리에 객체화
      • 화면 전체(메인 레이아웃) 설정하는 역할만을 수행
        • 부분 화면을 메모리에 객체화하기 위해서는 인플레이터 사용해야 함
        • 안드로이드는 LayoutInflater 클래스 제공 + getSystemService() 메소드 이용하여 사용해야 함
  • 앱 실행 시 XML 레이아웃의 내용이 메모리에 객체화되는 과정
  • XML 레이아웃은 앱 실행되는 시점에서 메모리에 객체화되므로, 앱은 실행되기 전까지 어떠한 위젯이 있는지 알지 못함.
    • 위의 코드 실행 시 앱 자체를 멈추는 심각한 오류 발생함
      • 메모리에 객체화되지 않은 버튼 객체 참조하는 오류
      • Caused by: java.land.NullPointerException: Attempt to invoke virtual method
  • @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "버튼이 눌렸어요", Toast.LENGTH_LONG).show();
            }
        });
        setContentView(R.layout.activity_main);
    }
  • 새로운 화면 추가 시 app 폴더 마우스 오른쪽 버튼 클릭 후 New -> Activity -> Empty Activity 선택
    • res/layout/xx.xml, app/java/xx.java 파일 생성됨
  • 화면 내 새로운 부분 화면 추가 시 app/res/layout 폴더 마우스 오른쪽 버튼 클릭 후 New -> Layout resource file 선택
    • 메인 레이아웃의 세트 자바 소스 파일 수정 필요
      • activity_menu 레이아웃의 Linear Layout 객체에 sub1.xml 파일 레이아웃 설정
      • View inflate(int resource, ViewGroup root) 메소드
        • 첫 번째 파라미터로는 XML 레이아웃 리소스, 두 번째 파라미터는 부모 컨테이너 지정
    • 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("로딩되었어요.");
              }
          });
      }

 

여러 화면 만들고 화면 간 전환

안드로이드 앱의 네 가지 구성 요소

  • Activity
    • <activity> 태그에 정보 포함하며, 새로운 액티비티 추가 시 정보 추가 해야함
    • startActivity() 메소드
      • 액티비티 소스 코드에서 띄울 때 사용하며, 단순히 액티비티를 띄워 화면에 보이도록 만들어줌
    • startActivityForResult(Intent intent, int requestCode) 메소드
      • 띄웠던 화면 닫고 메인 화면으로 돌아올 때 응답 받아 처리할 때 사용하여 해결
      • requestCode 값은 각 액티비티 구별 위해 사용
    • android:label 속성
      • 화면의 제목 설정
    • android:theme 속성
      • ="@style/Theme.AppCompat.Dialog": 대화상자 형태의 액티비티로 설정
    • MenuActivity.java
      • intent.putExtra("name", "mike");: 인텐트 객체 생성하고 name의 값을 부가 데이터로 넣기
      • setResult(RESULT_OK, intent): 응답 보내기
      • finish(): 현재 액티비티 없애기
    • @Override
      public void onClick(View v) {
          Intent intent = new Intent();
          intent.putExtra("name", "mike");
          setResult(RESULT_OK, intent);
          finish();
      }
    •  MainActivity.java
      • REQUEST_CODE_MENU는 새 액티비티 띄울 때 보낼 요청 코드이며, 중복되지 않도록, 마음대로 지정 가능
      • onActivityResult(int requestCode, int resultCode, Intent data) 메소드
        • 새로 띄웠던 메뉴 액티비티의 응답 처리하는 역할
        • requestCode: 액티비티 띄울 때 전달한 요청 코드, 어떤 액티비티의 응답 받은 것인지 구분 가능
        • resultCode: 새 액티비티에서의 처리 결과가 정상인지 아닌지 구분하는 데에 사용
          • 보통, Activity.RESULT_OK 상수 전달로 정상 처리임을 전달
        • data: 새 액티비티로부터 전달 받은 인텐트로, 새 액티비티의 데이터 전달 가능
          • 보통, putExtra() 메소드 사용하여 인텐트 객체에 데이터 넣어줌
          • Key, Value 쌍으로 넣어주어야 하며, Key를 사용하여 데이터 값 가져옴
    • public static final int REQUEST_CODE_MENU = 101;
      
      @Override
      protected void onActivityResult(int requestCode, int resultCode, 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();
              }
          }
      }
      
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          ...
          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);
              }
          });
      }
  • Service
  • Broadcast Receiver
  • Content Provider
  • 이 정보들은 새 프로젝트 생성 시 AndroidManifest.xml 파일에 담겨 있음

 

새로운 액티비티 추가 후 서로 간 데이터 주고받는 과정

  1. 새로운 액티비티 생성
  2. 새로운 액티비티의 XML 레이아웃 정의
  3. 메인 액티비티에서 새로운 액티비티 띄우기
    • 메인 액티비티 버튼 클릭 시 startActivityForResult() 통해 새로운 액티비티 띄우기
  4. 새로운 액티비티에서 응답 보내기
    • 새로운 액티비티 버튼 클릭 시 setResult() 통해 응답 보내기
  5. 응답 처리하기
    • 메인에서 onActivityResult() 재정의 통해 응답 처리

 

인텐트

: 앱 구성 요소가 해야 할 일 지정

인텐트 역할과 사용 방식

  • android:content 패키지 내에 정의된 인텐트는 앱 구성 요소 간 작업 수행 위한 정보 전달하는 역할
    • startActivity()/startActivityForResult()
      • 액티비티 화면에 띄울 때 사용
    • startService()/bindService()
      • 서비스 시작 시 사용
    • broadcastIntent()
      • 인텐트 객체를 broadcasting 방식으로 전송할 때 사용
    • 메소드 호출 시 인텐트가 파라미터로 전달된 후, 앱 구성요소로 전달됨
  • 인텐트의 기본 구성요소
    • Action: 수행할 기능
      • ACTION_VIEW
        • ex) 객체 생성 시, ACTION_VIEW와 웹페이지 주소 전달하면, 단말 내 설치되어 있던 웹브라우저 화면 뜨면서 해당 웹페이지 보여줌
      • ACTION_EDIT
      • ACTION_DIAL
    • Data: 액션이 수행될 대상의 데이터 의미
      • 포맷이 어떤 것인가를 확인 후 적절한 액티비티를 자동으로 띄워주기도 함
    • EXAMPLE
      • ACTION_DIAL tel:01012341234
        • 주어진 전화번호 이용하여 전화걸기 화면 보여줌
      • ACTION_VIEW tel:01012341234
        • 주어진 전화번호 이용하여 전화걸기 화면 보여줌, URL값 유형에 따라 다른 기능 수행
      • ACTION_VIEW content://contacts/people
        • 전화번호부 데이터베이스의 내용 보여줌
      • ACTION_EDIT content://contacts/people/2
        • 전화번호부 데이터베이스에 있는 정보 중 ID 값이 2인 정보 편집하기 위한 화면 보여줌
  • 인텐트 생성자
    • Intent()
    • Intent(Intent o)
    • Intent(String action [,Uri uri])
    • Intent(Context packageContext, Class<?> cls)
    • Intent(String action, Uri uri, Contexgt packageContext, Class<?> cls)
    • 명시적 인텐트(Explicit Intent)
      • 인텐트에 호출할 대상 지정하여 확실히 알 수 있는 경우
    • 암시적 인텐트(Implicit Intent)
      • 액션과 데이터 지정했지만, 호출 대상이 달라질 수 있는 경우
      • MIME 타입에 다라 시스템에서 적절한 다른 앱의 액티비티 찾아 띄우는 방식 사용
      • 액션, 데이터 이외 여러 속성 가짐
        • Category
          • 액션이 실행되는 데 필요한 추가적인 정보 제공
            • ex) CATEGORY_LAUNCHER: 설치된 앱들의 목록 보여주는 화면에 이 앱 보여주는 것 의미
        • Type
          • 인텐트에 들어가는 데이터의 MIME 타입 명시적으로 지정
          • 데이터만으로도 구별 가능하지만, 명시적으로 지정할 필요가 있는 경우도 존재
        • Component
          • 인텐트에 사용될 Component 클래스 이름 명시적으로 지정
          • 인텐트의 다른 정보 통해 보통 결정
          • 새로운 액티비티 정의하고 액티비티의 클래스 객체를 인텐트에 전달하여 실행하는 방법과 지정방식 동일
        • Extras: 부가데이터
          • Bundle 객체 통해 인텐트에 더 많은 정보 넣어 다른 앱 구성 요소에 전달 가능
            • 추가적인 정보 넣을 수 있도록 함
            • 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);  
                  }  
              });

 

@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);
}
  • ComponentName name = ...: 컴포넌트 이름 지정 가능한 객체 생성
    • 인텐트에 설정할 때 2개의 파라미터는 각각 패키지 이름과 클래스 이름
  • intent.setComponent(name): 인텐트 객체에 컴포넌트 지정
  • startActivityForResult(intent, 101);: 액티비티 띄우기

 

플래그와 부가 데이터 사용

플래그

  • 같은 액티비티에 대해 인텐트 중복하여 띄우지 않기 위해 플래그 사용하여 조정
  • 대표적인 플래그: 실제 앱 만들 때 주로 사용하는 것
    • FLAG_ACTIVITY_SINGLE_TOP
      • 액티비티 생성 시 이미 생성된 액티비티 존재한다면, 그 액티비티 그대로 사용
      • ex) 액티비티#2가 두번 실행되어도 플래그 사용한다면, 새로 생성되지 않고 그대로 사용함
        • 액티비티 스택: 액티비티#1 - 액티비티#2
        • 재사용하는 경우, 액티비티의 onCreate() 호출되지 않고, onNewIntent(Intent intent) 재정의하여 인텐트 객체 전달받음
      • ex) 플래그 사용하지 않은 경우
        • 액티비티 스택: 액티비티#1 - 액티비티#2 - 액티비티#2: 하나 더 생성됨
        • onCreate 내에 getIntent() 참고하여 전달받음
    • FLAG_ACTIVITY_NO_HISTORY
      • 플래그 설정 시, 처음 이후 실행된 액티비티는 액티비티 스택에 추가하지 않음 → Back 버튼 클릭 시 첫 액티비티만 보이게 됨
      • 알람 이벤트 발생 시, 사용자에게 알림 화면 한 번 보여주고 싶을 때 유용하게 사용
      • ex) 플래그 설정
        • 액티비티 스택: 액티비티#1 - 액티비티#2 → 액티비티#1 - 액티비티#1 → 액티비티#1
      • ex) 플래그 설정하지 않은 경우
        • 액티비티 스택: 액티비티#1 - 액티비티#2 → 액티비티#1 - 액티비티#2 - 액티비티#1 → 액티비티#1 - 액티비티#2
    • FLAG_ACTIVITY_CLEAR_TOP
      • 이 액티비티 위에 있는 다른 액티비티 모두 종료시킴
      • 홈 화면 같이 우선시 되는 액티비티 생성 시 유용하게 사용할 수 있음부가데이터
  • Bundle 객체 안에 넣은 데이터, 시스템에서 건드리지 않고 다른 앱 구성 요소로 전달함
  • 액티비티 띄울 때 전달되는 인텐트 안에 부가 데이터 넣어 전달하는 방법 권장
  • 인텐트 내 Bundle 객체에 putExtra()와 get---Extra() 메소드 통해 데이터 넣고 빼는 것 가능
    • Key, Value 쌍으로 부가 데이터 넣어줌
    • 문자열 데이터: getStringExtra()
      • 데이터 값 없다면 디폴트 타입으로 값 반환됨
    • 기본 자료형, 바이트 배열, Serializable 객체 모두 넣고 빼는 것 가능
      • 객체 자료형인 경우, 바이트 배열 변환 혹은 Serializable 인터페이스 구현하는 객체 만들어 직렬화 한 후 전달해야 함
      • 안드로이드에서는 Parcelable인터페이스를 데이터 전달 시 자주 사용
        • public abstract int describeContents()
          • 직렬화하려는 객체 유형 구분 시 사용
        • public abstract void writeToParcel(Parcel dest, int flags)
          • 객체가 가진 데이터를 Parcel 객체로 만들어주는 역할
        • 두 가지 메소드 모두 구현해야 함
          → static final CREATOR : Parcel 객체로부터 데이터 읽어들여 객체 생성하는 역할하는 상수 선언
        • 객체 정의하여 데이터 전달이 가능하므로 코드가 좀 더 단순하며 재사용이 높아지지만, 새로운 객체를 일일이 정의해야 한다는 단점 존재

 

태스크 관리 이해

  • 프로세스 하나 실행되면 그 위에 Virtual Machine이 만들어지고, 그 위에서 앱이 실행됨
  • 태스크
    • 앱이 어떻게 동작할지 결정하는 데 사용됨
    • 태스크 이용 시 어떤 화면들이 같이 동작해야 하는지 흐름 관리 가능
  • 프로세스
    • 독립적인 하나의 상자 같아 다른 프로세스와 정보 공유 불가능
    • 시스템은 알아서 태스크 관리하지만, 직접 제어하는 경우도 존재
      • AndroidManifest.xml 파일에 액티비티 등록할 때, 태스크도 함께 설정 가능
        • 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);
              }
          });
  • 앱 실행 후, 버튼 클릭 시마다 첫 화면 반복하여 뜨게 되며, BACK 버튼 통해 여러 화면이 중첩된 것 확인 가능
  • AndroidManifest.xml 파일에서 <activity> 태그에 launchMode = standard 속성과 값 추가한 것과 동일
    <activity android:name=".MainActivity"
                android:launchMode="singleTop">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
  • /app/manifests/AndroidManifest.xml 파일에 속성 추가 = "singleTop"로 설정
    • 태스크 가장 위쪽에 있는 액티비티는 더 이상 새로 생성하지 않음 의미
    • 인텐트의 FLAG_ACTIVITY_SINGLE_TOP 플래그 설정과 같은 효과
  • 앱 실행 후, 여러 번 버튼 클릭 후 BACK 버튼 클릭하더라도 앱의 화면이 사라짐
    • MainActivity로 전달되는 인텐트는 onNewIntent() 메소드 통해 전달받아야 함

 

액티비티의 수명주기와 SharedPreferences 이해하기

  • 안드로이드 시스템은 실행되는 앱의 상태 직접 관리핢
    • 관리가 없다면, 실행 앱의 과도한 메모리 사용 등의 문제 발생 가능성 존재
  • 액티비티의 대표적인 상태 정보
    • Running
      • 화면 상에 액티비티 보이며, 실행되어 있는 상태
      • 액티비티 스택 최상위에 있으며 포커스 가지고 있음
    • Paused
      • 사용자에게 보이지만, 다른 액티비티가 위에 있어 포커스 받지 못하는 상태
      • 대화상자가 위에 있어 일부가 가려진 경우 해당
    • Stopped
      • 다른 액티비티에 의해 완전히 가려져 보이지 않는 상태액티비티 수명주기/생명주기
  • 새로운 액티비티
    • onCreate()
      • 액티비티 처음 생성 시 호출
      • 화면에 보이는 뷰들의 일반적인 상태 설정
      • 이전 상태 저장되어 있는 경우 Bundle 객체 참조하여 복원 가능
      • 후에 항상 onStart() 메소드 호출
    • onStart()
      • 액티비티가 화면에 보이기 바로 전에 호출
      • 화면 상에 보이면 onResume() 메소드 호출
      • 화면에서 가려지면 onStop() 메소드 호출
    • onResume()
      • 액티비티가 사용자와 상호작용하기 바로 전에 호출
  • 실행
    • onPause()
      • 또 다른 액티비티 시작하려고 할 때 호출
      • 저장되지 않은 데이터 저장소에 저장 혹은 애니메이션 중 작업 중지 등의 기능 수행하는 메소드
      • 리턴 이전 다음 액티비티 시작 불가하므로, 매우 빨리 수행 후 리턴되어야 함
      • 시스템은 액티비티 강제 종료 가능
  • 일시정지
    → 실행: onResume()
    → 중지: onStop()
    • 액티비티가 사용자에게 더 이상 보이지 않을 때 호출
    • 액티비티 소멸되거나 또 다른 액티비티가 화면 가릴 때 호출
    • 시스템은 액티비티 강제 종료 가능
  • 중지
    → 실행: 
    • onReStart()
      • 액티비티 중지된 이후 다시 시작되기 바로 전에 호출
      • 후에 항상 onStart() 메소드 호출
    • onStart()
    • onResume()

        → 중지: onDestroy()

  • 액티비티가 소멸되어 없어지기 전에 호출
  • 액티비티가 받는 마지막 호출
  • 시스템 강제 종료시키는 경우 혹은 액티비티가 앱에 의해 종료되는 경우 호출 가능
  • isFinishing() 메소드 이용
  • 시스템은 액티비티 강제 종료 가능
  • 소멸
    • 앱 내에서 간단한 데이터 저장 혹은 복원하기 위해서는 SharedPreferences 사용 가능
      • 앱 내부에 파일 생성하여 파일 내에서 데이터 저장, 읽어올 수 있도록 함
      • 개발자는 실제로 파일 생성 필요 없이 SharedPreferences의 저장, 복원 메소드 호출
        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();
        }
  • saveState()
    • 현재 입력상자에 입력된 데이터 저장
    • SharedPreferences 객체 사용 위해 getSharedPreferences() 메소드 참조
    • edit() 메소드 호출 후 put__() 메소드로 저장 데이터 설정 가능
    • commit() 메소드 호출해야 실제 저장됨
    • onPause() 메소드에 들어가야 함
  • restoreState()
    • 설정 정보에 저장된 데이터 가져와서 토스트 메시지로 보여줌
    • onResume() 메소드에 들어가야 함