콘텐츠 대표 이미지 - 안드로이드 앱 개발: 액티비티와 프래그먼트의 구조와 생명주기 이해하기
☕ Java · Android Development

안드로이드 앱 개발: 액티비티와 프래그먼트의 구조와 생명주기 이해하기

Activity & Fragment — 앱의 뼈대를 이루는 두 핵심 컴포넌트 🤝

Activity 화면 전체를 담당 Fragment A 상단 콘텐츠 영역 Fragment B 하단 콘텐츠 영역 Activity 앱의 단일 화면을 나타내는 가장 기본적인 컴포넌트 Fragment Activity 안에서 동작하는 재사용 가능한 UI 조각 Java / Kotlin Android SDK UI 컴포넌트 생명주기 관리

🚀 안드로이드 앱, 뭐로 만들어지는 거야?

안드로이드 앱을 처음 배우기 시작하면 가장 먼저 마주치는 두 녀석이 있어. 바로 Activity(액티비티)와 Fragment(프래그먼트)야. 이 둘을 제대로 이해하지 못하면 앱 개발이 계속 헷갈리고 막히게 돼. 근데 반대로 이 둘을 확실히 이해하면? 앱 개발이 갑자기 엄청 재밌어지거든! 😄

쉽게 비유하자면, 액티비티는 건물 한 층이고, 프래그먼트는 그 층 안에 있는 방들이라고 생각하면 돼. 건물(앱) 전체를 돌아다니려면 층(액티비티)을 이동해야 하고, 한 층 안에서 여러 공간(프래그먼트)을 유연하게 활용할 수 있는 거지.

💡 알아두면 좋은 점: 안드로이드는 Java와 Kotlin 두 언어로 개발할 수 있어. 이 글에서는 Java 기반으로 설명하지만, 개념 자체는 Kotlin에서도 완전히 동일하게 적용돼!

📱 Activity(액티비티) — 앱의 얼굴이자 무대

액티비티는 안드로이드 앱에서 사용자가 직접 보고 상호작용하는 단일 화면을 의미해. 예를 들어 카카오톡을 켜면 채팅 목록 화면이 나오잖아? 그게 하나의 액티비티야. 거기서 특정 채팅방을 누르면 채팅 화면으로 넘어가는데, 그것도 또 다른 액티비티야.

Java에서 액티비티를 만들 때는 AppCompatActivity 클래스를 상속받아서 만들어. 가장 기본적인 액티비티 코드를 한번 볼게:

// MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 이 액티비티가 사용할 레이아웃 XML 파일을 연결
        setContentView(R.layout.activity_main);
    }
}

딱 봐도 심플하지? 핵심은 onCreate() 메서드야. 액티비티가 처음 생성될 때 호출되는 메서드인데, 여기서 setContentView()로 어떤 XML 레이아웃을 화면에 보여줄지 연결해주는 거야.

🔄 액티비티 생명주기 (Activity Lifecycle)

액티비티에서 가장 중요한 개념이 바로 생명주기(Lifecycle)야. 액티비티는 생성되고, 화면에 보이고, 포커스를 받고, 백그라운드로 가고, 소멸되는 일련의 상태 변화를 겪어. 이 흐름을 이해해야 앱이 예상치 못하게 꺼지거나 데이터가 날아가는 버그를 막을 수 있어.

액티비티 생명주기 (Activity Lifecycle) onCreate() 액티비티 생성, 초기화 onStart() 화면에 보이기 시작 onResume() 포커스 획득, 사용자 상호작용 ▶ 실행 중 상태 onPause() 포커스 잃음, 일부 보임 onStop() 화면에서 완전히 사라짐 onDestroy() 액티비티 소멸 onResume 으로 복귀 onRestart() → onStart 핵심 상태! 여기서 UI 업데이트

생명주기 콜백 메서드들을 하나씩 살펴볼게:

📌 onCreate()

액티비티가 처음 생성될 때 딱 한 번 호출돼. 뷰 초기화, 데이터 바인딩, 변수 설정 등 초기 설정 작업을 여기서 해. savedInstanceState로 이전에 저장된 상태를 복원할 수도 있어.

📌 onStart()

액티비티가 사용자에게 보이기 시작할 때 호출돼. 아직 포커스는 없는 상태야.

📌 onResume()

액티비티가 포커스를 얻고 사용자와 상호작용할 수 있는 상태가 됐을 때 호출돼. 애니메이션 시작, 카메라 접근 등 리소스 집약적인 작업을 여기서 시작해.

📌 onPause()

다른 액티비티가 포커스를 가져갈 때 호출돼. 여기서 진행 중인 작업을 일시 중지하고, 중요한 데이터를 저장해야 해. 이 메서드는 매우 빠르게 실행되어야 해!

📌 onStop()

액티비티가 화면에서 완전히 사라질 때 호출돼. 여기서 불필요한 리소스를 해제하고, 데이터베이스에 데이터를 저장하는 등의 작업을 해.

📌 onDestroy()

액티비티가 완전히 소멸될 때 호출돼. 남은 리소스를 모두 해제해야 해. finish()가 호출되거나 시스템이 메모리를 확보하기 위해 강제 종료할 때 발생해.

⚠️ 주의! onPause()에서 너무 무거운 작업을 하면 다음 액티비티 전환이 느려져. 가볍고 빠른 작업만 여기서 처리하고, 무거운 저장 작업은 onStop()에서 해!

🔀 액티비티 간 이동 — Intent

액티비티 간 이동은 Intent(인텐트)를 사용해. 인텐트는 "나 이 액티비티로 가고 싶어!"라는 의도를 담은 메시지 객체야.

// 현재 액티비티에서 SecondActivity로 이동
Intent intent = new Intent(MainActivity.this, SecondActivity.class);

// 데이터를 함께 전달하고 싶을 때
intent.putExtra("userName", "홍길동");
intent.putExtra("userAge", 25);

// 액티비티 시작!
startActivity(intent);

// -----------------------------------------------
// SecondActivity에서 데이터 받기
String name = getIntent().getStringExtra("userName");
int age = getIntent().getIntExtra("userAge", 0);

인텐트에는 두 종류가 있어:

🎯 명시적 인텐트 (Explicit Intent)

이동할 액티비티 클래스를 직접 지정해. 앱 내부 화면 이동에 사용해. 위 예시가 바로 명시적 인텐트야!

🌐 암시적 인텐트 (Implicit Intent)

특정 작업(전화걸기, 웹 열기 등)을 할 수 있는 앱을 시스템이 찾아서 실행해. 외부 앱과 연동할 때 사용해.

🧩 Fragment(프래그먼트) — 재사용 가능한 UI 조각

프래그먼트는 안드로이드 3.0(허니콤)에서 처음 도입됐어. 태블릿처럼 큰 화면에서 하나의 액티비티 안에 여러 UI 영역을 동시에 보여주기 위해 만들어진 개념이야. 지금은 스마트폰에서도 필수적으로 사용되는 핵심 컴포넌트가 됐지.

프래그먼트의 가장 큰 장점은 재사용성이야. 한 번 만든 프래그먼트를 여러 액티비티에서 가져다 쓸 수 있어. 마치 레고 블록처럼!

// MyFragment.java
import androidx.fragment.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class MyFragment extends Fragment {

    // 프래그먼트의 뷰를 생성하는 핵심 메서드
    @Override
    public View onCreateView(LayoutInflater inflater,
                             ViewGroup container,
                             Bundle savedInstanceState) {
        // fragment_my.xml 레이아웃을 inflate해서 View 반환
        return inflater.inflate(R.layout.fragment_my,
                                container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 뷰가 생성된 후 UI 초기화 작업은 여기서!
        // (액티비티의 onCreate와 비슷한 역할)
    }
}
💡 꿀팁! 프래그먼트에서 뷰 초기화는 onCreateView()가 아닌 onViewCreated()에서 하는 게 좋아. onCreateView()는 뷰를 반환하는 역할에만 집중하고, 뷰 관련 작업은 onViewCreated()에서 처리하는 게 클린 코드 관점에서 훨씬 좋거든!

🔄 프래그먼트 생명주기

프래그먼트도 액티비티처럼 생명주기가 있어. 근데 액티비티보다 좀 더 복잡해. 왜냐면 프래그먼트는 액티비티에 종속되어 있어서, 액티비티의 생명주기에도 영향을 받거든.

프래그먼트 생명주기 (Fragment Lifecycle) 🟢 생성 단계 onAttach() onCreate() onCreateView() onViewCreated() onStart() onResume() 🔴 소멸 단계 onPause() onStop() onDestroyView() onDestroy() onDetach() 실행 중 onResume 상태 💡 액티비티와의 차이점 프래그먼트는 onAttach/onDetach로 액티비티와 연결/분리되고, onCreateView/onDestroyView로 뷰의 생성과 소멸을 별도로 관리해! 뷰 생성 분리 액티비티에 종속 재사용 가능

🔧 프래그먼트 추가하기 — FragmentManager

프래그먼트를 액티비티에 추가하는 방법은 두 가지야. XML에 정적으로 추가하거나, Java 코드로 동적으로 추가하는 방법이야. 실무에서는 동적 추가를 훨씬 많이 써!

// 액티비티에서 프래그먼트를 동적으로 추가하기
// activity_main.xml에 id="fragment_container"인 FrameLayout이 있다고 가정

MyFragment myFragment = new MyFragment();

// FragmentManager와 FragmentTransaction 사용
getSupportFragmentManager()
    .beginTransaction()
    .replace(R.id.fragment_container, myFragment)  // 컨테이너에 프래그먼트 교체
    .addToBackStack(null)  // 백스택에 추가 (뒤로가기 지원)
    .commit();  // 트랜잭션 실행!

여기서 핵심 메서드들을 정리해볼게:

add() vs replace()

add()는 기존 프래그먼트 위에 새 프래그먼트를 쌓아. replace()는 기존 프래그먼트를 제거하고 새 것으로 교체해. 보통 replace()를 더 많이 써.

addToBackStack()

이걸 호출하면 사용자가 뒤로가기 버튼을 눌렀을 때 이전 프래그먼트로 돌아갈 수 있어. 안 쓰면 뒤로가기 시 액티비티 자체가 종료돼!

📨 프래그먼트 간 데이터 전달

프래그먼트에 데이터를 전달할 때는 Bundle을 사용하거나, 최신 방식인 ViewModel을 사용해. Bundle 방식부터 볼게:

// 프래그먼트 생성 시 데이터 전달 (Bundle 방식)
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString("title", "안드로이드 개발");
args.putInt("count", 42);
fragment.setArguments(args);  // 프래그먼트에 번들 설정

// MyFragment 내부에서 데이터 받기
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        String title = getArguments().getString("title");
        int count = getArguments().getInt("count");
    }
}
💡 현대적인 방법: 요즘은 ViewModel과 LiveData를 사용해서 프래그먼트 간 데이터를 공유하는 게 훨씬 권장돼. 같은 액티비티에 속한 프래그먼트들이 하나의 ViewModel을 공유하면 데이터 전달이 훨씬 깔끔해지거든!

⚖️ Activity vs Fragment — 언제 뭘 써야 해?

이게 진짜 많이들 헷갈려하는 부분이야. 결론부터 말하면 요즘 트렌드는 Single Activity Architecture야. 하나의 액티비티에 여러 프래그먼트를 조합해서 앱을 구성하는 방식이지. Google도 이 방식을 공식적으로 권장하고 있어.

📱 Activity 사용이 적합한 경우
앱의 진입점 (런처 액티비티)
완전히 독립적인 화면 전환
다른 앱에서 호출되는 화면
딥링크 처리가 필요한 화면
전체 화면 다이얼로그
🧩 Fragment 사용이 적합한 경우
탭 기반 네비게이션
재사용 가능한 UI 컴포넌트
화면 분할 레이아웃 (태블릿)
뒤로가기 스택 관리가 필요한 경우
애니메이션 전환이 많은 화면

🗺️ Navigation Component와 함께 쓰기

현대 안드로이드 개발에서는 Jetpack Navigation Component를 사용해서 프래그먼트 간 이동을 관리해. 이걸 쓰면 프래그먼트 전환, 백스택 관리, 데이터 전달이 훨씬 쉬워져.

// Navigation Component 사용 예시
// build.gradle에 의존성 추가 후

// NavController로 프래그먼트 이동
NavController navController = Navigation.findNavController(view);

// 단순 이동
navController.navigate(R.id.action_homeFragment_to_detailFragment);

// 데이터와 함께 이동 (Safe Args 사용)
HomeFragmentDirections.ActionHomeToDetail action =
    HomeFragmentDirections.actionHomeToDetail("전달할 데이터");
navController.navigate(action);

Navigation Component를 쓰면 nav_graph.xml이라는 파일에 화면 간 이동 경로를 시각적으로 관리할 수 있어서 앱의 흐름을 한눈에 파악할 수 있어. 진짜 편해! 😍

🛠️ 실전에서 꼭 알아야 할 팁들

1
프래그먼트에서 getActivity() 사용 주의

프래그먼트가 액티비티에서 분리된 상태에서 getActivity()를 호출하면 null이 반환돼서 NullPointerException이 발생해. 항상 null 체크를 하거나, requireActivity()를 사용해서 명시적으로 예외를 던지게 해.

2
ViewBinding 사용하기

findViewById()는 타입 안전성이 없어서 런타임 에러가 날 수 있어. ViewBinding을 사용하면 XML의 모든 뷰에 대한 타입 안전한 참조를 자동으로 생성해줘. 훨씬 안전하고 깔끔해!

// ViewBinding 사용 예시 (Fragment)
public class MyFragment extends Fragment {
    private FragmentMyBinding binding;  // 자동 생성된 바인딩 클래스

    @Override
    public View onCreateView(LayoutInflater inflater,
                             ViewGroup container,
                             Bundle savedInstanceState) {
        binding = FragmentMyBinding.inflate(inflater, container, false);
        return binding.getRoot();
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        // 이제 binding.textView, binding.button 이런 식으로 접근!
        binding.textView.setText("Hello Fragment!");
        binding.button.setOnClickListener(v -> {
            // 버튼 클릭 처리
        });
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        binding = null;  // 메모리 누수 방지! 반드시 null로 설정
    }
}
3
onDestroyView()에서 binding = null 처리

프래그먼트는 뷰가 소멸되어도 프래그먼트 인스턴스 자체는 살아있을 수 있어. 이때 binding을 null로 설정하지 않으면 메모리 누수가 발생해. 위 코드처럼 반드시 처리해줘!

4
백스택 관리 전략

모든 프래그먼트 전환에 addToBackStack()을 무조건 쓰면 안 돼. 탭 전환 같은 경우엔 백스택에 추가하지 않는 게 더 자연스러운 UX를 만들어. 상황에 맞게 선택적으로 사용해!

5
ViewModel로 데이터 공유

같은 액티비티에 속한 프래그먼트들이 데이터를 공유해야 할 때는 ViewModel을 사용해. 프래그먼트 간 직접 통신보다 훨씬 깔끔하고 생명주기에도 안전해.

// SharedViewModel.java - 공유 ViewModel
public class SharedViewModel extends ViewModel {
    private MutableLiveData<String> selectedItem = new MutableLiveData<>();

    public void selectItem(String item) {
        selectedItem.setValue(item);
    }

    public LiveData<String> getSelectedItem() {
        return selectedItem;
    }
}

// FragmentA에서 데이터 설정
SharedViewModel model = new ViewModelProvider(requireActivity())
    .get(SharedViewModel.class);
model.selectItem("선택된 아이템");

// FragmentB에서 데이터 관찰
SharedViewModel model = new ViewModelProvider(requireActivity())
    .get(SharedViewModel.class);
model.getSelectedItem().observe(getViewLifecycleOwner(), item -> {
    // item이 변경될 때마다 자동으로 호출됨!
    textView.setText(item);
});

📚 AndroidManifest.xml — 액티비티 등록은 필수!

액티비티를 만들었으면 반드시 AndroidManifest.xml에 등록해야 해. 등록하지 않으면 앱이 크래시 나거든! 프래그먼트는 등록 안 해도 돼 — 이게 둘의 또 다른 차이점이야.

<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <!-- 메인 액티비티 (앱 진입점) -->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- 다른 액티비티들도 모두 등록해야 함 -->
        <activity
            android:name=".SecondActivity"
            android:exported="false" />
    </application>
</manifest>
⚠️ Android 12 이상 주의! android:exported 속성을 명시적으로 설정하지 않으면 빌드 에러가 발생해. 외부에서 접근 가능한 액티비티는 true, 내부에서만 사용하는 건 false로 설정해!

🎨 프래그먼트 전환 애니메이션

프래그먼트 전환 시 애니메이션을 추가하면 훨씬 세련된 UX를 만들 수 있어:

// 프래그먼트 전환 애니메이션 추가
getSupportFragmentManager()
    .beginTransaction()
    .setCustomAnimations(
        R.anim.slide_in_right,   // 새 프래그먼트 진입 애니메이션
        R.anim.slide_out_left,   // 기존 프래그먼트 퇴장 애니메이션
        R.anim.slide_in_left,    // 뒤로가기 시 진입 애니메이션
        R.anim.slide_out_right   // 뒤로가기 시 퇴장 애니메이션
    )
    .replace(R.id.fragment_container, newFragment)
    .addToBackStack(null)
    .commit();

또는 Navigation Component를 쓴다면 nav_graph.xml에서 action에 직접 애니메이션을 지정할 수 있어서 훨씬 편해!

🎯 핵심 정리 — 이것만 기억해!

✅ Activity 핵심

AppCompatActivity 상속 Manifest 등록 필수 Intent로 이동 생명주기 7단계 setContentView로 레이아웃 연결

✅ Fragment 핵심

Fragment 상속 Manifest 등록 불필요 FragmentManager로 관리 onCreateView로 뷰 생성 Bundle로 데이터 전달

🌟 재능넷에서 안드로이드 개발 관련 강의나 프로젝트 도움을 찾고 있다면, 다양한 개발자들이 자신의 재능을 공유하고 있으니 한번 둘러봐! 액티비티와 프래그먼트 개념을 실제 프로젝트에 적용하는 데 도움받을 수 있을 거야. 😊

액티비티와 프래그먼트는 안드로이드 앱 개발의 근본 중의 근본이야. 처음엔 생명주기가 복잡해 보이고 헷갈릴 수 있지만, 실제로 앱을 만들면서 몇 번 겪다 보면 자연스럽게 몸에 익어. 특히 요즘은 Jetpack 라이브러리(ViewModel, LiveData, Navigation, ViewBinding)와 함께 사용하는 게 표준이 됐으니, 이 개념들도 함께 공부하면 훨씬 현대적인 안드로이드 개발자가 될 수 있어!

그리고 Single Activity Architecture 패턴을 지향하면서 프래그먼트를 적극 활용하는 방향으로 공부하면, 나중에 Jetpack Compose로 넘어갈 때도 개념적으로 훨씬 수월해질 거야. 안드로이드 개발, 생각보다 재밌으니까 포기하지 말고 계속 도전해봐! 💪

마지막으로 재능넷처럼 다양한 개발 지식을 공유하는 플랫폼을 활용하면서 커뮤니티와 함께 성장하는 것도 정말 좋은 방법이야. 혼자 공부하는 것보다 훨씬 빠르게 실력이 늘거든! 🚀

댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0
댓글을 불러오는 중입니다.

아직 댓글이 없습니다.