Fragment 파헤치기 – 3. FragmentManager, FragmentTransaction에 대해서

저번 시간에는 Fragment LifeCycle(기억이 안나시는 분은 다시보고오자.) 에 대해서 알아 보았고, 이번 시간에는 Fragment를 실제 어떻게 Activity에서 작동 시킬 것인지에 대해서 써볼까 한다.

Fragment는 레이아웃 xml에서 바로 Add가 가능하고, 코드를 통해서는 FragmentTransaction으로 Add, Remove, Replace를 할 수있다.

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

레이아웃 xml을 통해 아주 간단하게 Fragment를 Add할 수 있다. 한번 Add된 Fragment를 다른 Fragment로 변경 해야 되는 경우가 있는데, 이때는 FragmentTransaction을 이용해서 Action을 해야한다.

FragmentManager fragmentManager = getFragmentManager(); 

TestFragment frament = new TestFragment();  
Bundle bundle = new Bundle();   
frament.setArguments(bundle);   

FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();   
fragmentTransaction.add(R.id.container, fragment); // Activity 레이아웃의 View ID    
fragmentTransaction.commit();

FragmentTransaction의 add Method를 통해서 commit할 Container View의 ID와 Fragment를 준면 된다. add는 Fragment가 계속 쌓이게 되며, replace는 하나의 Fragment가 존재하며 바꿔치기 한다. add로 Fragment를 추가 하게되면 View가 계속 쌓인 만큼 성능 또한 느려지는 점 주의하자.

Container View ID가 아닌 TAG를 통해서 UI가 없는 Background작업을 하는 Fragment를 만들 수도 있다.  또한 Dialog를 Fragment에서도 구현가능 하다.

– Activity에서의 화면 전환 처럼 setCustomAnimations()를 통해서 애니메이션이 가능하다.

findFragmentByIdfindFragmentByTag를 통해서 Commit된 Fragment를 얻어 올 수있다.

– 몇몇 상태를 제외하고 Fragment에 전달되는 이벤트들은 Atcivity에서 받은후 Fragment에 전달 되는 형태로 구성가능하다.

– Activity에서와 동일하게  onSaveInstanceState()에서 bundle에 state를 저장 후 onCreate(), onCreateView(), onActivityCreated()에서 restore 가능하다.

 

이처럼 Fragment는 활용이 아주 다양하다.

Fragment를 생성시 데이터를 넘겨줘야 하는 경우가 있는데, Fragment의 생성자로 데이터를 넘기는 경우 언제 사라질지 모르기때문에 반드시 setArguments를 통해서 사용해야된다.  

TestFragment frament = new TestFragment();  
Bundle bundle = new Bundle();   
bundle.putInt("id", 1);
frament.setArguments(bundle);

저장한 bundle을 가져올때

Bundle extra = getArguments();
int id = extra.getInt("id");

commit()은 Activity의 onSaveInstanceState() 하기전에 수행되어야 하며, 이를 어길시 java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 예외가 발생된다.

예를 들어 화면전환을 했다가 돌아 왔을때 Fragment갱신을 해야 하는 경우를 들어 보자.

Activity1에서 onActivityCreated() → commit() 후 다른 Activity2로 전환전 Activity1의 onSaveInstanceState()가 호출 된다.  그런뒤 Activity2를 띄우고 Activity2가 닫긴후 Activity1의 onRestart() → commit()을 하면 예외가 발생한다.

onSaveInstanceState()가 된이후에 commit()을 했기때문이다.

이렇게 화면갱신이 필요한 경우에는 commit()을 쓰지 말고 commitAllowingStateLoss()를 호출 해서 onSaveInstanceState()와 무관하게 commit를 할 수 있다.

 

또한 사용하다가 주의해야 할 점은..

commit은 바로 실행 되지 않고 메인쓰레드에 의해 스케쥴처리 되기때문에 Activity는 종료 되었는데, Fragment가 생성되면서 ApplicationContext에서 NullPointException이 발생 할 수 있다.

이처럼 Fragment를 Activity에 commit하는 방법과 주의할 점에 대해서 살펴 보았다. 다음 시간에는 popBackStack과  Activity와 Fragment간의 통신방법에대해서 설명 하겠다.

Fragment 파헤치기 – 2. Fragment Lifecycle(생명주기)

Fragment의 가장 기본인 Lifecycle을 알아보자. 모든 것이든 기본이 가장 중요한 만큼 하나하나 꼼꼼하게 분석해보자. Fragment는 Activity와 비슷한 Lifecycle 구조를 가졌다. 이전 Fragment 개념에 대해 간단히 언급 했듯이, Fragment는 Activity에서 작동하는 구조라고 소개했다.

Activity보다 좀더 복잡하지만, 개념만 이해 한다면 쉽게 사용 할것 이다. FragmentTransaction으로 Fragment를 add, replace 한다. 이외 레이아웃에서 바로 add하는 경우도 있다. 이때 add, replace할때 부터 Lifecycle이 시작된다.

 

최초 생성 Lifecycle

1) onAttach()
Fragment가 Activity에 붙을때 호출 된다.

2) onCreate()
Activity에서의 onCreate()와 비슷하나, ui관련 작업은 할 수 없다.

3) onCreateView()
Layout을 inflater을하여 View작업을 하는곳이다.

4) onActivityCreated()
Activity에서 Fragment를 모두 생성하고 난다음 호출 된다. Activity의 onCreate()에서 setContentView()한 다음이라고 생각 하면 쉽게 이해 될것 같다. 여기서 부터는 ui변경작업이 가능하다.

5) onStart()
Fragment가 화면에 표시될때 호출된다. 사용자의 Action과 상호 작용 할 수 없다.

6) onResume()
Fragment가 화면에 완전히 그렸으며, 사용자의 Action과 상호 작용이 가능하다.

다른 Fragment가 add

1) onPause()
Fragment가 사용자의 Action과 상호 작용을 중지한다.

2) onStop()
Fragment가 화면에서 더이상 보여지지 않게 되며, Fragment기능이 중지 되었을때 호출 된다.

3) onDestoryView()
View 리소스를 해제 할수 있도록 호출된다. backstack을 사용 했다면 Fragment를 다시 돌아 갈때 onCreateView()가 호출 된다.

Replace or backward로 removed되는 경우

4) onDestory()
Fragment상태를 완전히 종료 할 수 있도록 호출 한다.

5) onDetach()
Fragment가 Activity와 연결이 완전히 끊기기 직전에 호출 된다.

그외 Callbacks Method

onSaveInstanceState()

Activity와 동일하게 Fragment가 사라질떄 호출되며 상태를 Bundle로 저장할수 있도록 호출 된다. 그럼 간단한 테스트 코드를 통해 Lifecycle에 맞게 작동 되는지 확인 해보도록 하자.

Activity

package com.test;

import com.example.fragmenttest.R;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.AttributeSet;
import android.view.View;

public class FragmentTestActivity extends FragmentActivity {
    

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

        setContentView(R.layout.main);
        
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        
        TestFragment newtf = TestFragment.newInstance(1);
        
        ft.replace(R.id.embedded, newtf);
        ft.commit();
        
    }

    @Override
    public void onAttachFragment(Fragment fragment) {
        super.onAttachFragment(fragment);
    }
    
    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return super.onCreateView(name, context, attrs);
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
    }
    
}

Fragment

package com.test;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.fragmenttest.R;

public class TestFragment extends Fragment {
    
    int mIdx;
     
    public static TestFragment newInstance(int index) {
        
        TestFragment fragment = new TestFragment();
        
        Bundle args = new Bundle();
        args.putInt("index", index);
        fragment.setArguments(args);

        return fragment;
    }
    
    
    @Override
    public void onAttach(Activity activity) {
        Log.d(this.getClass().getSimpleName(), "onAttach()");
        
        super.onAttach(activity);
    }
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onCreate()");
        
        super.onCreate(savedInstanceState);
        
        Bundle args = getArguments();
        if (args != null) {
            mIdx = args.getInt("index", 0);
        }
        
    }
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onCreateView()");
        return inflater.inflate(R.layout.layout_contents, null);
        
    }
    
    @Override
    public void onInflate(Activity activity, AttributeSet attrs,
            Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onInflate()");
        super.onInflate(activity, attrs, savedInstanceState);
    }
    
    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onViewCreated()");
        super.onViewCreated(view, savedInstanceState);
    }
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        Log.d(this.getClass().getSimpleName(), "onActivityCreated()");
        super.onActivityCreated(savedInstanceState);
    }

    @Override
    public void onStart() {
        Log.d(this.getClass().getSimpleName(), "onStart()");
        super.onStart();
    }

    @Override
    public void onResume() {
        Log.d(this.getClass().getSimpleName(), "onResume()");
        super.onResume();
    }
    
    @Override
    public void onPause() {
        Log.d(this.getClass().getSimpleName(), "onPause()");
        super.onPause();
    }
    
    
    
    
    @Override
    public void onStop() {
        Log.d(this.getClass().getSimpleName(), "onStop()");
        super.onStop();
    }
    
    @Override
    public void onDestroyView() {
        Log.d(this.getClass().getSimpleName(), "onDestroyView()");
        super.onDestroyView();
    }
    
    @Override
    public void onDestroy() {
        Log.d(this.getClass().getSimpleName(), "onDestroy()");
        super.onDestroy();
    }

    @Override
    public void onDetach() {
        Log.d(this.getClass().getSimpleName(), "onDetach()");
        super.onDetach();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        Log.d(this.getClass().getSimpleName(), "onSaveInstanceState()");
        super.onSaveInstanceState(outState);
    }

    
    
}


Fragment실행시 Log를 찍어 보았다. Lifecycle과 동일하게 작동 되는 것을 확인 할 수 있다.

Fragment종료시 Log를 찍어 보았다. Lifecycle과 동일하게 작동 되는 것을 확인 할 수 있다.

Activity에 비해 많은 Callbacks메소드로 인해 복잡한건 사실이다. 하지만 자세히 하나씩 보면 Activity와 별반 다르지 않다는 것을 알 것이다. 아래 그림을 첨부 했으니 Activity와 비교 해보면 좋을것 같다.

 

 여기 까지 Fragment를 살펴 보았다. 다음 시간에는 Fragment를 관리 하는 FragmentManager와 FragmentTransaction에 대해서 상세히 알아 보도록 하자.

Fragment 파헤치기 – 1. Fragment 개념

Android Activity의 기본 개념은 한 화면에 보이는 모든것을 관리 하는 개념이다. 다양한 테블릿 디바이스, 다이나믹한 어플레이케이션 개발을 위해서 Activity는 개념에 맞지 않아 등장 한것이 Frgament라는 개념이다.

기존 Activity는 하나의 화면에 여러개 사용 할수 없게 설계되어 있는 반면 Fragment는 Activity와 비슷한  Lifecycle을 가지면서 여러가지 화면을 넣을 수 있는 방법을 지원해준다. 이 Fragment는 Android 3.0(허니컴)부터 API를 지원해 왔으며 그 이하 버전은 Support.v4(/sdk/extras/android/support/v4/android-support-v4.jar)의 FragmentActivity를 사용한다면 동일하게 사용가능 하다.

특징

Fragment는 Activity와 비슷한 LifeCycle을 가진다.
Fragment는 하나의 Activity에서 다수의 Fragment를 사용 할 수 있다.
Fragment는 Activity에서만 존재하며 단독으로 실행 될 수 없는 구조이다.
Fragment는 Activity와 마찬가지로 Back Stack을 사용 할 수 있으나, Activity처럼 다양한 Stack방식을 지원하지 않는다.
Fragment는 Activity와 위에서만 존재 하기때문에 다수의 Fragment를 동시에 뛰울때 메모리가 문제가 될 수 있으므로 너무 복잡한 구조는 지양해야 한다.

 

테블릿의 경우 Activity를 사용하면 많은 공간이 낭비 된다. 또한 이런 공간 낭비를 막기위해 View를 구성하게 되지만, 이런 관리들이 너무 복잡하게 되며 사용자들의 혼란을 초래 할 수있다.

폰의 경우 ViewPager을 이용하여 작은 화면을 사용자들이 좀 더 다이나믹하게 사용 할수 있는 용도로 바꿀수 있다.

이 처럼 Fragemnt는 테블릿과 좀더 다이나믹한 어플리케이션 개발에 필수로 사용되므로 안드로이드 개발 자라면 반드시 익혀 두길 권한다.