안드로이드 완벽 가이드 - 03 애플리케이션 구성하기

출처

정재곤, 『Do it! 안드로이드 앱 프로그래밍 - 개정 4판』, 이지스퍼블리싱(주)(2017-06-26), 248-331p


03-1 레이아웃 인플레이션 이해하기

  • XML 레이아웃: 단순 화면을 어떻게 배치하는지 정의하는 파일
  • 화면 배치; XML 레이아웃 파일 + 자바 소스 파일이 필요
  • setContentView(): XML 레이아웃 파일과 자바 소스코드 파일을 매칭시키는 메소드
  • R.layout.레이아웃 파일 이름: XML 레이아웃 파일 확장자 없이 지정하여 보는 형식
  • 인플레이션(Inflation): XML 레이아웃에 정의된 내용이 메모리에 로딩된 후 객체화되는 과정

1. SampleLayoutInflater 프로젝트 생성

image

package com.example.kai01.samplelayoutinflater;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(getApplicationContext(), "버튼이 눌렸어요.", Toast.LENGTH_LONG.show());
            }
        };
        setContentView(R.layout.activity_main);
    }
}
  • activity_main.xml에 있는 텍스트뷰를 삭제한 후 버튼 추가
  • setContentView() 메소드를 호출하는 코드 위에 버튼을 찾아 변수에 할당
  • setOnClickListener() 메소드를 호출하는 코드 입력

2. 오류 발생 확인

image

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.Button.setOnClickListener(android.view.View$OnClickListener)' on a null object reference

  • 메모리에 객체화되지 않은 버튼 객체를 참조하려고 했기 때문에 오류 발생
  • 로그로 오류 내용 확인 가능

setContentView() 메소드의 두 가지 역할

  1. 화면에 나타낼 뷰를 지정하는 역할

public void setContentView(int layoutResID)

  1. XML 레이아웃의 내용을 메모리에 객체화하는 역할

public void setContentView(View view [, ViewGroup.LayoutParams params])

LayoutInflater

  • setContentView() 메소드를 이용하여 전체가 아닌 부분 화면을 보여줄 때 사용
  • LayoutInflater는 시스템 서비스로 제공됨
  • 시스템 서비스: 단말이 시작되면 항상 실행되는 서비스

getSystemService(Context.LAYOUT_INFLATER_SERVICE)

화면의 일부분을 XML 레이아웃 파일의 내용으로 적용하는 과정

image

  • SampleLayoutInflater 프로젝트 안에 새로운 화면을 더 추가하기

1. app 폴더 마우스 오른쪽 버튼 클릭 후 [New –> Activity –> Empty Activity]

image

  • Activity Name: MenuActivity
  • 생성하면 activity_menu.xml 레이아웃 파일과 MenuActivity.java 소스 파일로 구성됨

2. activity_menu.xml 파일에서 [Text]탭을 눌러 다음 코드 입력

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_menu"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- 안내글을 표시하는 텍스트뷰 정의 -->
    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="버튼을 눌러 부분 화면을 추가하세요."
        android:textSize="20dp" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="추가하기"/>
    
    <!-- 컨텐츠를 따로 추가할 레이아웃 -->
    <LinearLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

    </LinearLayout>

</LinearLayout>

image

  • 이제 부분 화면으로 추가할 XML 레이아웃 파일을 만들어 봄

3. res/layout 폴더 마우스 오른쪽 버튼 누르고 [New –> Layout resource file]

image

  • File name: sub1.xml

4. sub1.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"
    android:background="#ffaaccff">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="부분 화면 1"
        android:textSize="30dp"/>
    <CheckBox
        android:id="@+id/checkBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="동의합니다. "/>
</LinearLayout>

image

5. MenuActivity.java 파일 안에 다음 코드 입력

package com.example.kai01.samplelayoutinflater;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;

public class MenuActivity extends AppCompatActivity {
    LinearLayout container;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_menu);

        container = (LinearLayout) findViewById(R.id.container);

        Button button = (Button) findViewById(R.id.button2);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                inflater.inflate(R.layout.sub1, container, true);

                CheckBox checkBox = (CheckBox) container.findViewById(R.id.checkBox);
                checkBox.setText("로딩되었어요.");
            }
        });
    }
}
  • activity_menu.xml 파일 안 리니어 레이아웃의 id는 container
  • findViewById(R.id.container);: id가 container인 리니어 레이아웃 객체를 전달 –> 리니어 레이아웃 객체 참조 가능해짐
  • getSystemService(Context.LAYOUT_INFLATER_SERVICE);: LayoutInflater 객체를 참조
  • inflater.inflate(R.layout.sub1, container, true);: container를 id로 갖는 리니어 레이아웃 객체에 sub1.xml 파일의 레이아웃을 설정하라는 의미
  • 위 메소드는 레이아웃에 정의된 뷰의 내부적인 객체화 과정을 거침
  • sub1.xml이 메모리에 객체화되면 container 객체의 findViewById() 메소드를 사용해 sub1.xml의 레이아웃 텍스트뷰나 체크박스를 참조할 수 있음

6. AndroidManifest.xml 파일에 MenuActivity와 MainActivity 이름 바꾸기

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.samplepdfview">
    <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.VIEW" />
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

7. 실행결과 확인

image

image

  • 위 화면: XML 레이아웃을 로딩하기 전
  • 아래 화면: 인플레이션을 통해 레이아웃을 적용한 이후의 것
  • XML 레이아웃을 만들지 않고 코드에 직접 new Button(this)과 같은 코드를 추가할 수도 있음

inflate() 메소드 정의

View inflate (int resource, ViewGroup root)

  • 첫 번째 파라미터: XML 레이아웃 리소스
  • 두 번째 파라미터: 뷰들을 객체화하여 추가할 대상이 되는 부모 컨테이너 지정
  • 보통 LayoutInflater 객체는 getSystemService() 메소드를 사용할 수 있지만
  • LayoutInflater 객체의 from() 메소드를 사용할 수도 있음

from() 메소드 정의

static LayoutInflater LayoutInflater.from(Context context)

  • LayoutInflater를 내부적으로 지원하는 View 클래스 메소드를 이용하는 방법도 있음

다른 inflate() 메소드 정의

static View inflate (Context context, int resource, ViewGroup root)

  • 인플레이션 과정에 익숙해지자!

03-2 화면 구성과 화면 간 전환

  • 안드로이드에서 하나의 화면 = 액티비티

안드로이드 애플리케이션의 기본 구성 요소 네 가지

  1. 액티비티(Activity)
  2. 서비스(Service)
  3. 브로드캐스트 수신자(Broadcast Receiver)
  4. 내용 제공자(Content Provider)
  • 이들의 정보를 담고 있는 곳 = AndroidManifest.xml
  • startActivity(): 액티비티를 띄우기 위해 사용하는 메소드
  • startActivityForResult(): 액티비티로부터 다시 원래의 액티비티로 돌아오면서 응답을 받아 처리하는 메소드

startActivityForResult(Intent intent, int requestCode)

  • 첫 번째 파라미터: 인텐트
  • 두 번째 파라미터: 정수로 된 코드 값 –> 액티비티 구분용으로 사용
  • 기본 액티비티 외 새로운 액티비티를 추가하고 전환하는 프로젝트를 만들어 보겠음

1. SampleIntent 프로젝트 만들고 app 폴더에 오른쪽 마우스 클릭 후 [New –> Activity –> Empty Activity]

  • Activity name: MenuActivity

2. AndroidManifest.xml 파일에 다음 코드 추가

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.kai01.sampleintent">

    <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=".MenuActivity"
            android:label="메뉴 액티비티"
            android:theme="@style/Theme.AppCompat.Dialog">
        </activity>
    </application>

</manifest>
  • android:label: 화면의 타이틀 설정
  • android:theme: 테마 설정

3. activity_menu.xml 파일에 돌아가기 버튼 추가

  • 돌아가기 버튼을 누르면 정말 돌아가게 하려 함

4. MenuActivity.java에 다음 코드 추가

package com.example.kai01.sampleintent;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MenuActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_menu);

        Button button = (Button) findViewById(R.id.button); // 버튼 객체 참조
        button.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Intent intent = new Intent(); // 인텐트 객체 생성하고 name의 값을 부가 데이터로 넣기
                intent.putExtra("name", "mike");
                setResult(RESULT_OK, intent);

                finish(); // 현재 액티비티 없애기
            }
        });
    }
}
  • onClick() 메소드 안에 Intent 클래스를 사용, 객체를 만든 후 setResult() 메소드 호출
  • setResult(): 새로 띄운 액티비티에서 이전 액티비티로 인텐트를 전달할 때 사용

setResult(응답 코드, 인텐트)

  • finish(): 액티비티를 화면에서 없애고 싶을 때 사용

4. activity_main.xml 파일에서 텍스트뷰 삭제 후 버튼 추가

image

  • 버튼 내용은 “메뉴 화면 띄우기”
  • onClick 속성에 onButton1Clicked 메소드 입력

5. MainActivity.java 코드를 다음과 같이 수정

package com.example.kai01.sampleintent;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;

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);
    }

    public void onButton1Clicked(View v) {
        Intent intent = new Intent(getApplicationContext(), MenuActivity.class); // 또 다른 액티비티를 띄우기 위한 인텐트 객체 생성
        startActivityForResult(intent, REQUEST_CODE_MENU); // 액티비티 띄우기
    }
}
  • startActivity()와의 차이점은 startActivityForResult() 메소드를 쓴다는 것
  • startActivityForResult()는 새로 띄운 액티비티로부터 응답을 받을 수 있음

image

onButton1Clicked() 메소드

  • onButton1Clicked() 메소드 안에서 인텐트 객체를 만든 후 startActivityForResult() 메소드를 호출
  • 인텐트 객체: 액티비티를 띄울 목적으로 사용되며 액티비티 간에 데이터를 전달하는 데에도 사용
  • 인텐트 객체의 첫 번째 파라미터: Context 객체 –> 주로 this 변수 사용하나 이벤트 처리 메소드 안에서는 this 변수로 MainActivity 객체를 참조하지 못함
  • 따라서 getApplicationContext() 메소드를 사용해 이 애플리케이션의 Context 객체를 참조한 후 전달
  • 인텐트 객체의 두 번째 파라미터: MenuActivity.class
  • 이제 응답하는 코드를 작성해봄

6. MainActivity 클래스안에 마우스 커서를 두고 오른쪽 버튼 누른 후 [Generate –> Override Methods] 클릭

image

  • onActivityResult 메소드를 찾아 선택 후 [OK] 클릭

7. MainActivity.java의 onActivityResult 메소드에 다음 코드 추가

package com.example.kai01.sampleintent;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    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.getExtras().getString("name");
            Toast.makeText(getApplicationContext(), "응답으로 전달된 name: " + name, Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onButton1Clicked(View v) {
        Intent intent = new Intent(getApplicationContext(), MenuActivity.class); // 또 다른 액티비티를 띄우기 위한 인텐트 객체 생성
        startActivityForResult(intent, REQUEST_CODE_MENU); // 액티비티 띄우기
    }
}
  • onActivityResult() 메소드는 새로 띄운 메뉴 액티비티가 응답을 보내면 그 응답을 처리하는 역할을 함

protected void onActivityResult(int requestCode, int resultCode, Intent intent);

  • 첫 번째 파라미터: 액티비티를 띄울 때 요청한 코드 –> 어떤 액티비티로부터 응답을 받았는지 구분
  • 두 번째 파라미터: 응답을 보내 온 액티비티에서 온 응답 코드 –> 처리 결과가 정상인지 아닌지 구분, Activity_RESULT_OK 상수가 정상 처리를 뜻함
  • 세 번째 파라미터: 새로 띄운 메뉴 액티비티로부터 받은 인텐트 –> 인텐트 안에는 데이터를 넣을 수 있음
  • putExtra(): 인텐트 객체에 데이터를 넣는 메소드 –> 키(Key)와 데이터(Data) 값을 넣어야 함

8. 실행하여 결과 확인하기

image

image

image

9. 정리

image

  1. 새로운 액티비티를 추가하면 XML 레이아웃 파일 하나와 자바 소스 파일 하나가 만들어지고 매니페스트 파일에 액티비티 태그가 추가됨
  2. 새로 만들어진 XML 레이아웃을 수정하여 새롱누 액티비티의 화면이 어떻게 배치될지 작성
  3. 메인 액티비티의 버튼을 클릭하면 startActivityForResult() 메소드로 새로운 액티비티를 띄움
  4. 새로운 액티비티가 보이고 그 안에 들어 있는 버튼을 클릭하면 setResult() 메소드로 응답을 보냄
  5. 메인 액티비티에서 onActivityResult() 메소드를 재정의하여 새로 띄웠던 액티비티에서 보내오는 응답을 처리

03-3 인텐트 살펴보기

  • 인텐트: 다른 액티비티를 띄우거나 기능을 동작시키기 위한 수단

인텐트의 역할과 사용 방식

  • android.content 패키지: 인텐트가 정의되어 있는 장소

다른 애플리케이션 구성 요소에 인텐트를 전달할 수 있는 대표 메소드

  • startActivity(): 액티비티를 화면에 띄울 때 사용
  • startActivityForResult()
  • startService(): 서비스를 시작할 때 사용
  • bindService()
  • broadcastIntent(): 브로드캐스팅을 수행할 때 사용

인텐트의 기본 구성 요소

  1. 액션(Action): 수행할 기능 –> ex) ACTION_VIEW, ACTION_EDIT
  2. 데이터(Data): 액션이 수행될 대상 데이터

액션과 데이터를 사용하는 대표적인 예

속성 설명
ACTION_DIAL tel:01077881234 주어진 전화번호를 이용해 전화걸기 화면을 보여줌.
ACTION_VIEW tel:01077881234 주어진 전화번호를 이용해 전화걸기 화면을 보여줌. URI 값의 유형에 따라 VIEW 액션이 다른 기능을 수행함.
ACTION_EDIT content://contacts/people/2 전화번호부 데이터베이스에 있는 정보중에서 ID 값이 2인 정보를 편집하기 위한 화면을 보여줌.
ACTION_VIEW content://contacts/people 전화번호부 데이터베이스의 내용을 보여줌.

인텐트 생성자

  1. Intent()
  2. Intent(String action, [,Uri uri])
  3. Intent(Context packageContext, Class<?> cls)
  4. Intent(String action, Uri uri, Context PackageContext, Class<?> cls)
  • 인텐트의 데이터는 적절한 MIME 타입으로 구분함
  • 인텐트 객체는 액션과 데이터를 인수로 하여 만들 수도 있지만 다른 인텐트나 클래스 객체를 인수로 하여 만들기도 함
  • 명시적 인텐트(Explicit Intent): 인텐트에 클래스 객체나 컴포넌트 이름을 지정하여 호출할 대상을 확실히 알 수 있는 인텐트
  • 암시적 인텐트(Implicit Intent): 액션과 데이터를 지정하긴 했지만 호출할 대상이 달라질 수 있는 인텐트
  • 암시적 인텐트는 안드로이드 시스템이 인텐트를 이용해 요청한 정보를 처리할 수 있는 적절한 컴포넌트를 찾고 사용자에게 그 대상과 처리 결과를 보여줌
  • 범주(Category): 액션이 실행되는데 필요한 추가적인 정보 제공
  • 타입(Type): 인텐트에 들어가는 데이터의 MIME 타입을 명시적으로 지정
  • 컴포넌트(Component): 액티비티와 같은 독립적인 구성 요소
  • 부가 데이터(Extra Data): 번들(Bundle) 객체를 통해 추가적인 정보를 넣음
  • 인텐트에서 액션과 데이터를 이용하는 경우와 컴포넌트 이름을 이용하는 경우를 비교해보겠음

1. SampleCallIntent 프로젝트 생성 후 activity_main.xml에 입력상자 하나와 버튼 하나 추가

image

  • editText는 안드로이드 스튜디오 팔레트에 없고 xml에 직접 editText 태그 입력하면 됨 참고
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="164dp"
        android:layout_marginLeft="164dp"
        android:layout_marginTop="80dp"
        android:onClick="onButton1Clicked"
        android:text="전화걸기"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="132dp"
        android:layout_marginLeft="132dp"
        android:text="tel:010-1000-1000"
        app:layout_constraintStart_toStartOf="parent"
        tools:layout_editor_absoluteY="170dp" />

</android.support.constraint.ConstraintLayout>

2. MainActivity.java 파일 다음과 같이 수정하기

package com.example.kai01.samplecallintent;

import android.content.Intent;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.editText); // 뷰 객체 참조

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String data = textView.getText().toString(); // 입력상자에 입력된 전화번호 확인

                Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(data)); // 전화걸기 화면을 보여줄 인텐트 객체 생성
                startActivity(intent); // 액티비티 띄우기
            }
        });
    }
}

3. 실행하여 확인하기

image

image

  • 이제 인텐트를 사용하여 액티비티를 띄우는 두 번째 경우를 보겠음

4. SampleIntent 프로젝트를 열고 MainActivity.java 코드 수정하여 다른 방식의 인텐트 여는 법 확인하기

package com.example.kai01.sampleintent;

import android.content.ComponentName;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    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.getExtras().getString("name");
            Toast.makeText(getApplicationContext(), "응답으로 전달된 name: " + name, Toast.LENGTH_LONG).show();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void onButton1Clicked(View v) {
        Intent intent = new Intent();

        ComponentName name = new ComponentName("com.example.kai01.sampleintent", "com.example.kai01.sampleintent.MenuActivity"); // 컴포넌트 이름을 지정할 수 있는 객체 생성

        intent.setComponent(name); // 인텐트 객체에 컴포넌트 지정
        startActivityForResult(intent, REQUEST_CODE_MENU); // 액티비티 띄우기
    }
}
  • 그럼 위에서 한 sampleintent 프로젝트와 똑같이 작동함

인텐트 클래스에 정의된 액션 정보

ACTION_MAIN ACTION_SENDTO
ACTION_VIEW ACTION_ANSWER
ACTION_ATTACH_DATA ACTION_INSERT
ACTION_EDIT ACTION_DELETE
ACTION_PICK ACTION_RUN
ACTION_CHOOSER ACTION_SYNC
ACTION_GET_CONTENT ACTION_PICK_ACTIVITY
ACTION_DIAL ACTION_SEARCH
ACTION_CALL ACTION_ACTION_WEB_SEARCH
ACTION_SEND ACTION_FACTORY_TEST
  • ACTION_MAINACTION_EDIT을 가장 많이 사용

PDF 파일 보여주기

  • PDF 파일을 보여주는 인텐트는 암시적 인텐트를 사용할 예정

명시적, 암시적 인텐트

image

1. SamplePDFView 프로젝트 생성 및 activity_main.xml 작성

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24dp"
        android:hint="PDF 파일명을 입력하세요." />
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="열기"
        android:onClick="onButton1Clikced" />
</LinearLayout>

2. MainActivity.java 작성

package com.example.samplepdfview;

import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import java.io.File;

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);
    }

    public void onButton1Clicked(View v) {
        String filename = editText.getText().toString(); // 입력상자에 입력된 파일명 확인
        if (filename.length() > 0) {
            openPDF(filename.trim()); // openPDF() 메소드 호출
        } else {
            Toast.makeText(getApplicationContext(), "PDF 파일명을 입력하세요.", Toast.LENGTH_LONG).show();
        }
    }

    public void openPDF(String filename) { // PDF 파일 열기 기능을 정의한 메소드
        String sdcardFolder = Environment.getExternalStorageDirectory().getAbsolutePath();
        String filepath = sdcardFolder + File.separator + filename;
        File file = new File(filepath);

        if (file.exists()) {
            Uri uri = Uri.fromFile(file); // Uri 객체로 생성

            Intent intent = new Intent(Intent.ACTION_VIEW); // ACTION_VIEW 액션을 가지는 인텐트 객체 생성
            intent.setDataAndType(uri, "application/pdf"); // Uri 객체와 MIME 타입 지정

            try {
                startActivity(intent); // 액티비티 띄우기
            } catch (ActivityNotFoundException ex) {
                Toast.makeText(this, "PDF 파일을 보기 위한 뷰어 앱이 없습니다.", Toast.LENGTH_SHORT).show();
            }
        } else {
            Toast.makeText(this, "PDF 파일이 없습니다.", Toast.LENGTH_SHORT).show();
        }
    }
}
  • 사용자는 입력창에 PDF 파일명을 입력하고 버튼을 누름
  • 버튼을 누르면 openPDF() 메소드 호출
  • openPDF()는 인텐트 객체를 만들고 startActivity() 메소드 호출
  • 인텐트: 액션은 ACTION_VIEW, setDataAndType으로 URI와 MIME 타입 지정
  • Environment.getExternalStorageDirectory().getAbsolutePath();: SD 카드의 절대 경로를 가져오는 함수
  • SD 카드에 접근하려면 권한 필요
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

3. AndroidManifest.xml에서 SD카드 권한 추가

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.samplepdfview">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <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.VIEW" />
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

4. Gradle Scripts > build.gradle(Module: app)에서 target SDK 22로 맞추기

apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.samplepdfview"
        minSdkVersion 21
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
  • targetSdkVersion 22

5. SD카드가 들어있는 단말기에 연결하고 빌드하고 PDF 누르면 된다고 함

  • 귀찮으니 생략

03-4 액티비티를 위한 플래그와 부가 데이터

플래그

  • 다양한 액티비티가 쌓일 때 이를 조절해 줌
  • FLAG_ACTIVITY_SINGLE_TOP
  • FLAG_ACTIVITY_NO_HISTORY
  • FLAG_ACTIVITY_CLEAR_TOP

액티비티 스택

image

액티비티를 새로 생성하여 인텐트를 전달하는 것과 생성하지 않고 인텐트를 전달하는 방법 비교

image

  • public void onNewIntent(Intent intent)

각 플래그 옵션을 사용하지 않았을 때와 사용하였을 때의 비교

image

image

image


부가데이터

image

  • 인텐트를 생성할 때 데이터를 더 보내고 싶을 때 사용
  • Intent putExtra(String name, String value)
  • Intent putExtra(String name, int value)
  • Intent putExtra(String name, boolean value)
  • String getString(String name)
  • int getIntExtra(String name, int defaultValue)
  • boolean getBooleanExtra(String name, boolean defaultValue)

Parcelable 인터페이스

  • 자바의 Serializable과 유사하게 데이터를 직렬화하여 전송할 때 씀
  • public abstract int describeContents()
  • public abstract void writeToParcel(Parcel dest, int flags) 두 개의 메소드를 구현해야 함

1. SampleParcelable 프로젝트 생성

2. activity_main.xml에 버튼 하나 추가

3. app에 우측 마우스 클릭 후 New -> Activity -> Empty Activity 클릭 후 이름은 MenuActivity로 설정

4. app -> java -> com.example.sampleparcelable 폴더에 오른쪽 마우스 클릭 후 New -> Java Class 이름은 SimpleData

5. SimpleData.java 작성

package com.example.sampleparcelable;

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) {
        // Parcel 객체에서 읽기
        number = src.readInt();
        message = src.readString();
    }

    // SimpleData 생성자를 호출해 Parcel 객체에서 읽기
    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {

        public SimpleData createFromParcel(Parcel in) {
            return new SimpleData(in); // CREATOR 상수 정의
        }

        public SimpleData[] newArray(int size) {
            return new SimpleData[size];
        }
    };

    public int describeContents() {
        return 0;
    }

    // Parcel 객체로 쓰기
    public void writeToeParcel(Parcel dest, int flags) {
        dest.writeInt(number);
        dest.writeString(message);
    }
}

Comments