반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 받은 트랙백

글 보관함

[CookBook] Fragments 이해하기 -3-

2013. 9. 26. 19:15 | Posted by 솔웅


Adding and Removing Fragments with Device Orientation



fragments를 사용할 때 가장 큰 잇점 중의 하나는 디바이스를 가로 세로 방향으로 돌려서 볼 때 쉽게 fragment를 넣고 뺄 수 있어서 적당한 layout의 view를 사용자에게 보여질 수 있도록 하는 것입니다. 그럼 이렇게 만들기 위해 이전 글에서 다뤘던 소스들을 약간 수정해 보겠습니다. (안드로이드 전화기에서 세웠을 때는 한개의 fragment만 보여지도록요.)


activity_foreground_fragment.xml 에서 두번째 fragment 부분을 주석 처리 합니다.



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <fragment
        android:name="com.androidtablet.foregroundfragmentapp.Fragment1Activity"
        android:id="@+id/fragment1"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />
 <!--    <fragment
        android:name="com.androidtablet.foregroundfragmentapp.Fragment2Activity"
        android:id="@+id/fragment2"
        android:layout_weight="0"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" /> -->
</LinearLayout>




이렇게 하는 이유는 오직 1개의 fragment만 화면에 표시하기 위해서죠. 이 경우는 안드로이드 전화기가 세워졌을 때 뿐입니다. 눕혀졌을 때나 태블릿 일 경우에는 두개의 fragments들이 좌우로 배치되어야 하겠죠.
그러면 이런 경우를 위해서 어떤 작업을 해야 합니다. 우선 res 폴더 아래에 layout-land 폴더를 생성하세요. 그리고 그 폴더에 activity_foreground_fragment.app 을 복사해 넣으세요.



전화기가 세워졌다가 옆으로 뉘여졌을 때 이 activity는 layout-land 에 있는 layout xml 파일을 사용해서 화면에 표시해 줄 겁니다. 전화기가 다시 세워지면 layout 폴더 안에 있는 xml 을 사용해서 화면을 갱신시킬거구요.



layout_land 폴더에 있는 activity_foreground_fragment.xml 파일에서는 위에 있는 주석을 풀어 주시면 됩니다.



그 다음에는 Fragment1Activity.java 파일을 수정하시면 됩니다.



public class Fragment1Activity extends Fragment {
    OnOptionSelectedListener  myListener;
    boolean large, xlarge;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Context c = getActivity().getApplicationContext();
        View vw = inflater.inflate(R.layout.fragment1, container, false);
        String[] products={"Camera", "Laptop", "Watch",  "Smartphone", "Television"};
        large = ((getResources().getConfiguration().screenLayout  & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE);             
        xlarge =((getResources().getConfiguration().screenLayout  & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE);      
        ListView productsList = (ListView) vw.findViewById(R.id.products_list);
        ArrayAdapter<String> arrayAdpt= new ArrayAdapter<String> (c, R.layout.list_item, products);
        productsList.setAdapter(arrayAdpt);        
        productsList.setOnItemClickListener(new OnItemClickListener(){
            @Override
            public void onItemClick(AdapterView<?> parent, View v, int position, long id){                 
                if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE || large || xlarge){                                      
                    myListener.onOptionSelected(((TextView)  v).getText().toString());  
                 } else {
                    Intent intent = new Intent(getActivity().getApplicationContext(),  DisplayItemActivity.class);
                    intent.putExtra("item", ((TextView) v).getText().toString());
                    startActivity(intent);
                 }

            }
        });
        return vw;
    }
   
    public interface OnOptionSelectedListener {
        public void onOptionSelected(String message);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            myListener = (OnOptionSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + "  must implement OnItemClickListener");
        }
    }   
}



위의 굵은 글씨체가 추가된 부분입니다. 리스트뷰의 아이템이 클릭 됐을 때 onItemClick() 메소드를 보면  우선 device가 landscape 이거나 large 혹은 xlarge 즉 태블릿인 경우를 체크합니다. 만약에 그렇다면 fragment2를 보여줍니다. 즉 그럴 경우 onOptionSelected() 메소드가 호출되고 이때 선택된 아이템의 이름이 파라미터로 전달 되는 겁니다. 그러면 Fragment2에서 이 파라미터를 받아서 화면에 출력하겠죠.



위 경우가 아니라면 즉 디바이스가 전화기이고 세워진 상태라면 (Portrait orientation) fragment2는 호출 되지 않을 겁니다. 리스트뷰에 있는 아이템이 선택 됐으면 그 이름이 다른 화면에 표시 되어야 합니다. 다른 화면에 표시된다는 얘기는 다른 activity가 필요하다는 얘기입니다. 그리고 다른 activity로 옮길 경우에는 Intent를 사용해야 되구요. 이 경우 현재의 Context를 같이 보내 주어야 합니다. getActivity().getApplicationContext(). 그리고 불려질 Activity를 정해주고 (DisplayItemActivity.class) 그리고 추가적으로 데이터를 넣어서 이것을 전달합니다. 마지막으로 startActivity() 를 하면 다음 화면이 불려 오겠죠.



그러면 이제 할 일을 DisplayItemActivity.java 를 만들어야 겠네요.
소스 코드는 아래와 같습니다.



public class DisplayItemActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            finish();
            return;
        }
        setContentView(R.layout.fragment2);
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            String selectedItem = extras.getString("item");
            TextView selectedOpt = (TextView) findViewById(R.id.selectedopt);
            selectedOpt.setText("You have selected "+selectedItem);
        }
    }
}



onCreate() 만 있죠? 맨 처음에 이 기계가 Landscape 모드이면 그냥 끝내고 return 을 해 버립니다. 그리고 난 후 fragment2.xml 을 view로 세팅하죠.
그리고 나서 이전 파일에서 전달해 준 데이터를 체크합니다. getExtra()가 그 일을 합니다. 그리고 item이란 key 값을 이용해서 그 value 값을 selectedItem에 담구요. 그 값을 텍스트뷰에 넣어 줍니다.


이렇게 하면 완성된 겁니다.
그런데 한가지 더 해야 될 게 있는데요.
DisplayItemActivity 라는 Activity가 새로 생겼으면 이것을 AndroidManifest.xml 에 신고를 해야 합니다.
<activity android:name=".DisplayItemActivity" android:label="@string/app_name" /> 을 추가해야 하는데요.
혹시 Manifest 파일에 액티비티 추가하는 방법을 모르시면 관련 글을 보시고 참고하시기 바랍니다.



그리고 한가지 더 해야 할 일이 있는데요.
현재 layout과 layout-land 폴더에만 xml layout 파일이 있습니다. 이건 전화기에만 해당되는데요. 태블릿에도 적용시키기 위해서 두개의 폴더를 더 만듭니다. layout-sw600dp 와 layout-sw720dp 폴더를요. 그리고 이 앱에서는 전화기의 landscape 인 경우와 7인치 10인치 태블릿의 landscape, portrait orientation 모두 그 구성이 같습니다. 그러니까 lauout-land 에 있는 activity_foreground_fragment_app.xml 파일을 복사해서 위 두 폴더 밑에 넣습니다.



이제 이 앱을 돌리면 안드로이드 전화기인 경우에는 Portrait orientation인 경우 첫화면에는 리스트뷰만 표시되고 그 리스트 뷰의 아이템을 클릭했을 때 그 이름이 두번째 화면에서 표시되게 됩니다. landscape orientation으로 바뀌게 되면 태블릿과 마찬가지로 두개의 fragment들이 좌우로 배치되고 좌측의 리스트뷰 아이템을 클릭하면 그 이름이 우측의 텍스트 뷰에 출력되게 됩니다.

반응형

Comment

[CookBook] Fragments 이해하기 -2-

2013. 9. 26. 06:16 | Posted by 솔웅


Creating Foreground Fragments, and Knowing the Difference Between Foreground and Background Fragments



Foreground Fragments는 유저와 상호작용이 있는 fragment입니다. 입력을 받고 일을 진행하고 원하는 결과를 display 하는것 같은 상호 작용이 있게 됩니다. 이 Foreground Fragments는 view들의 조합을 갖고 있고 이 view들을 화면에 보여 줍니다. Background Fragments는  반대로 유저와 상호작용이 없이 백그라운드에서 activity내에서 어떤 일을 수행하는 경우에 사용 됩니다. 이 Background Fragment에는 어떤 User Interface(UI) 도 포함되지 않습니다. 그러니까 이 background fragments를 정의할 때 onCreateView()는 호출 될 필요가 없겠죠.



Foreground Fragments에 대한 개념을 이해하기 위해 ForegroundFragmentApp이라는 안드로이드 프로젝트를 생성하겠습니다. 여기에는 두개의 Fragment를 만들 겁니다. Fragment1은 selection 위젯, 리스트 뷰가 있게 될 겁니다. 여기에는 선택할 수 있는 몇가지 제품들이 들어갈 거구요. Fragment2에는 TextView가 세팅이 될 텐데요. 여기에는 Fragment1의 리스트 뷰에서 선택 된 제품이 표시 될 겁니다.



이 두개의 fragment들은 각각 개별적인 XML layout 파일을 사용해서 view들을 정의 합니다. 그래서 이 두개의 fragments들을 위해 fragment1.xml 과 fragment2.xml을 res/layout 폴더에 넣겠습니다.



첫번째 fragment에 리스트뷰를 정의하기 위해 아래와 같이 fragment1.xml을 만듭니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#0000FF" >
    <ListView
        android:id="@+id/products_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:drawSelectorOnTop="false"  />
 </LinearLayout>



보시면 리스트 뷰 선택 위젯이 정의된 것이 보이죠? id 는 products_list 입니다. 이 fragment의 배경색은 파란색으로 해서 나중에 두번째 fragment랑 구분이 될 수 있도록 만들었습니다.
아래는 fragment2.xml 입니다.



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView 
        android:id="@+id/selectedopt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=" Please select a product "
        android:textSize="@dimen/text_size"
        android:textStyle="bold"  />
</LinearLayout>



id가 selectedopt 인 TextView 가 보이실 겁니다. 텍스트가 이미 지정이 돼 있죠. 이 문자의 스타일은 bold 이고 사이즈는 dimen.xml 에 있는 사이즈를 채택할 겁니다. 그리고 화면에 보여주겠죠.

NOTE
dimens.xml 파일은 이미 values,values-sw600dp, values-sw720dp 폴더에 있는 상황을 염두에 둔 상황입니다. 그 파일 안에 각각 화면 크기에 맞게 text_size가 세팅 돼 있어야 합니다.



리스트 뷰의 디폴트 사이즈는 전화기에 알맞게 돼 있습니다. 그러니까 태블렛에는 약간 작겠죠. 리스트 뷰의 리스트 아이템을 디바이스에 맞게 resize 하려면 list_item.xml 이라는 XML 파일을 하나 더 만들어야 합니다.



<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="6dp"
      android:textSize="@dimen/text_size"
      android:textStyle="bold" />
     
     


보시면 리스트 뷰의 리스트 아이템은 6dp의 padding을 가지게 됩니다. 그리고 bold체이고 크기는 dimension 리소스에 정의된 text_size 를 가지게 됩니다.


각 fragment는 XML 파일에서 UI를 로드하는 별도의 Java 클래스를 갖고 있습니다. 그러니까 이 앱은 두개의 fragment와 두개의 자바 클래스를 가지게 되는 거죠. Fragment1Activity.java와 Fragment2Activity.java를 추가 하겠습니다.



Fragment1Activity.java



public class Fragment1Activity extends Fragment {
    OnOptionSelectedListener  myListener;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Context c = getActivity().getApplicationContext();
        View vw = inflater.inflate(R.layout.fragment1, container, false);
        String[] products={"Camera", "Laptop", "Watch",  "Smartphone", "Television"};
        ListView productsList = (ListView) vw.findViewById(R.id.products_list);
        ArrayAdapter<String> arrayAdpt= new ArrayAdapter<String> (c, R.layout.list_item, products);
        productsList.setAdapter(arrayAdpt);        
        productsList.setOnItemClickListener(new OnItemClickListener(){
            @Override
            public void onItemClick(AdapterView<?> parent, View v, int position, long id){                 
              myListener.onOptionSelected(((TextView)  v).getText().toString());  
            }
        });
        return vw;
    }
   
    public interface OnOptionSelectedListener {
        public void onOptionSelected(String message);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            myListener = (OnOptionSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + "  must implement OnItemClickListener");
        }
    }   
}



보시면 이 클래스가 Fragment 클래스를 extends 하신 걸 알게 될 겁니다. 이 fragment에 접근하고 UI를 그려넣으려면 onCreateView() 메소드를 오버라이드 해야 합니다. 이 onCreateView() 메소드 안에는 LayoutInflater object 가 있는데요. 이것은 fragment1.xml 에서 정의했던 리스트 뷰 UI를 inflate하기 위한 겁니다. 리스트뷰와 텍스트 뷰는 XML에서 정의한 것이죠. fragment1.xml 에는 리스트 뷰가 있고 이 리스트 뷰는 fragment1Activity.java에서 productList라는 객체를 만들어 가져다 씁니다. 텍스트 뷰는 fragment2.xml 에 정의 되어 있고 Fragment2Activity.java에서 selectedOpt라는 객체 담아서 쓰고요.


ArrayAdapter 에는 리스트의 아이템에 products 객체에 있는 배열을 세팅하고 있습니다. 그리고 이 각 아이템에 setOnItemClickListener를 답니다. onItemClick() 콜백 메소드를 오버라이드 해서 이 리스트 뷰 내의 한 아이템을 클릭하면 그 안의 text를 받아서 표시하는데요. OnOptionSelectedListener 인터페이스가 정의 돼 있는  것이 보이죠? 그 안에는 onOptionSelected() 메소드의 body 가 있습니다. 이렇게 하면 리스트 뷰의 어떤 아이템이 선택되면 onoptionSelected() 메소드가 호출이 되고 선택된 아이템 이름을 파라미터로 해서 이 메소드에 전달 되게 됩니다. 



두번째 파일인 fargment2.xml의 UI를 로드하기 위해서 아래 자바코드를 작성합니다.



Fragment2Activity.java



public class Fragment2Activity extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment2, container, false);
    }

    public void dispOption(String msg){
        TextView selectedOpt = (TextView) getActivity().findViewById(R.id.selectedopt);
        selectedOpt.setText("You have selected "+msg);  
    }
}



위에서 봤던 자바파일 처럼 Fragment 클래스를 extends 했습니다. 그리고 onCreateView() 메소드도 오버라이드 했구요. 여기서도 LayoutInflater object가 fragment2.xml 안에 정의 된 텍스트 뷰를 inflate 하기 위해 사용됩니다. dispOption() 메소드 안에는 텍스트 뷰 객체가 만들어 졌고 전달 받은 product 이름을 이 텍스트 뷰에 표시하도록 돼 있습니다.



NOTE
getActivity() 메소드가 Fragment1Activity.java 파일에서 사용이 됐는데요. 여기서는 현재 fragment와 연관이 있는 activity를 return 하게 됩니다. 이 메소드는 activity와 fragment의 상호작용을 가능하게 해 주는 메소드 입니다.



어플리케이션에 이 두개의 fragment들을 자리잡게 하려면 아래와 같은 layout xml이 있어야 합니다.


activity_foreground_fragment_app.xml



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <fragment
        android:name="com.androidtablet.foregroundfragmentapp.Fragment1Activity"
        android:id="@+id/fragment1"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />
    <fragment
        android:name="com.androidtablet.foregroundfragmentapp.Fragment2Activity"
        android:id="@+id/fragment2"
        android:layout_weight="0"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />
</LinearLayout>



여기를 보시면 두개의 <fragment> 태그가 있는 것이 보이시죠? 각 태그에는 android:name 속성을 사용해서 Fragment1Activity와 Fragment2Activity 가 세팅돼 있습니다.



이 layout xml을 사용할 자바 파일을 만들어야 합니다.


ForegroundFragmentAppActivity.java



public class ForegroundFragmentAppActivity extends Activity implements OnOptionSelectedListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_foreground_fragment_app);
    }
    public void onOptionSelected(String msg) {
        Fragment2Activity frag2 = (Fragment2Activity) getFragmentManager().findFragmentById(R.id.fragment2);
        frag2.dispOption(msg);  
    } 
}



onOptionSelected() 메소드는 리스트뷰에서 아이템을 선택했을 때 실행이 될 겁니다. 이 onOptionSelected() 메소드에 리스트뷰의 선택된 아이템의 이름이 파라미터로 전달 됩니다. 이 아이템 이름은 fragment2의 dispOption() 메소드를 호출해서 스크린에 표시하게 되구요. 이렇게 FragmentManager를 사용하시면 됩니다. fragment2 에 접근해서 그 안에 있는 dispOption() 메소드를 사용하기 위해 이 FragmentManager() 가 위와 같이 사용됩니다.


태블렛에서 어플리케이션을 실행하고 나면 Fragment1과 Fragment2에 정의된 UI가 왼쪽 오른쪽으로 display 될 겁니다. fragment1의 리스트뷰는 아이템 리스트를 표시하고 Fragment2는 Please select a product라는 텍스트를 텍스트 뷰에 표시할 겁니다. 왼쪽 리스트에서 제품이 선택되면 그 이름이 오른쪽 텍스트 뷰에 표시되게 됩니다.





안드로이드 전화기에서 landscape로 이 앱이 실행이 되도 이 두 fragment들은 태블릿이랑 똑 같이 화면에 나타날 겁니다. 왜냐하면 전화기라도 landscape 모드로 놓이게 되면 좌우로 공간이 충분하니까 이렇게 표시되도 별 문제는 없습니다. 하지만 문제는 전화기를 세웠을 때도(Portrait orientation) 똑같이 나타난다는 거죠. 전화기를 세우면 좌우 공간이 충분하지 않은데 두개의 fragment들을 좌우로 나란히 배열한다는게 좀 무리입니다.



만약 전화기가 세워졌을 경우에는 이 fragment 중 하나만 화면에 나타나도록 하면 더 좋겠죠. 그러니까 처음에는 리스트 뷰만 표시되면 좋을 겁니다. 그리고 그 리스트 뷰에서 한 아이템이 선택 되면 그 아이템의 이름이 표시되는 화면 즉 두번째 fragment로 화면이 넘어가구요.



이렇게 한번 만들어 보겠습니다.

반응형

Comment

[CookBook] Fragments 이해하기 -1-

2013. 9. 25. 05:58 | Posted by 솔웅


Fragments



이번에는 Fragments에 대해서 배워보겠습니다. 안드로이드 어플리케이션은 때때로 fragments로 나눠지기도 하는데요 좀 더 manageable 하다는 얘기죠. fragment들은 각각 독립적으로 유저 인터페이스를 진행하고 또 쉽게 어플리케이션에서 추가나 삭제도 가능하고 서로 다른 화면 크기에 맞게 세팅을 할 수도 있습니다. 바로 이런 작업을 하능하게 하는 fragment를 사용하는 법과 그 life cycle에 대해 배우겠습니다. 그리고 이 fragment가 실제 안드로이드 어플리케이션에는 어떻게 적용 되는지도 알아 보구요. 또 자바 코드에서 어떻게 이 fragment들이 dynamic하게 추가 되는지도 알아 봅니다. 그리고 두개의 fragment 사이에서 어떻게 data를 넘겨주고 받는지도 보구요. ListFragment, DialogFragment, PreferenceFragment 들에 대해서도 다룹니다.



이 기능은 타블렛을 위해 나온 기능 입니다. 화면이 큰 타블렛의 공간을 나눠서 활용하기 위해 이 개념이 나왔는데요. 그러니까 7인치나 10인치 타블렛이 이 기능을 적용하기에 적합합니다.



이 글에서 소개 되는 어플리케이션은 아래 구조를 따릅니다.



1. res/values 폴더 밑에 dimens.xml 이라는 이름으로 아래 내용을 넣습니다.
  
2. res 폴더 밑에 values-sw600dp 와 values-sw720dp 라는 폴더를 생성합니다. 그리고 1번의 dimens.xml을 이 폴더 밑에 복사해 넣습니다.


3. 이 dimens.xml 파일 안의 text_size를 values-600dp 안에 있는 것은 24sp 로 values-720dp 안에 있는 것은 32sp 로 수정합니다.



Introducing Fragments



Fragments 는 activity 를 encapsulated 된 재사용 가능한 모듈로 나눕니다. 각각은 각각의 user interface 를 가지고 있습니다. 어플리케이션을 서로 다른 화면 크기에도 적당하게 표시되도록 할 수 있습니다. 그리고 activity 에서 이 fragments를 추가하거나 삭제 할 수도 있습니다.



디바이스 방향이 portrait 에서 landscape 로 바뀔 때 높이와 너비가 바뀌게 됩니다. 너비가 더 넓어 져서 오른쪽에 더 많은 공간이 생기게 되죠. 높이는 더 짧아져서 아랫쪽에 있는 부분은 짤릴겁니다. 같은 tablet이라도 이렇게 가로방향이냐 세로 방향이냐에 따라서 화면 크기가 다릅니다.



어플리케이션을 개발할 때 유저가 가로방향으로 볼 때와 세로 방향으로 볼 때 모두 어색함이 없도록 그 화면을 구성하는 view들의 구성을 미리 다 마련해 놓아야 합니다. 이럴 때 fragments를 활용할 수 있습니다.


fragments는 activity와 layout 그리고 view들의 조합이 합쳐진 겁니다. 이 세가지가 합쳐져서 user interface를 가지게 되는 거죠. 예를 들어 디바이스의 가로 세로가 바뀔 때 빈부분을 채워주고 넘치는 부분을 제대로 자리잡게 하기 위해서 이 fragments를 activity 안에 넣어서 사용하는 겁니다. 또한 필요에 따라서는 dynamic 하게 remove 될 수도 있구요.



Fragment1, Fragment2 이렇게 두개의 Fragments를 가지고 있다고 가정합시다. 이 두개의 Fragments는 각각 나름대로의 view들의 조합구성을 가지고 있습니다. 디바이스가 portrait 모드 일 때 두개의 activity를 생성하고 각각 fragment 하나씩을 배치해 놓을 수 있습니다. 그리고 한번에 한가지 activity 를 표시하도록 할 수 있죠. 그리고 디바이스에 따라서 이 두 fragments를 동시에 표시할 수 있다면 (좀 더 큰 디바이스 이거나 세로 방향일 때) 그 화면을 가득 채우기 위해 이 두개의 Fragment들을 한개의 activity에 넣어서 화면을 꽉 채울 수 있죠.

각각의 fragment들은 또한 나름대로의 life cycle과 view hierarchy 를 가지고 있습니다.activity 가 실행되는 동안 이 fragment들을 추가하거나 삭제 할 수 있습니다. 이 fragment들은 activity 의 context 안에 존재합니다.



Understanding the Life Cycle of a Fragment



Fragment를 생성하려면 Fragment 클래스를 extend하고 몇개의 life cycle 콜백 메소드를 implement 해야 합니다. fragment의 라이프 사이클은 해당 activity의 라이프 사이클에 종속 됩니다. 그러니까 activity 가 paused 된 상태이면 그 안의 모든 fragment들도 paused 됩니다. 마찬가지로 activity 가 destroy 되면 그 안의 fragment들도 같이 destroy 됩니다. 이 fragment들의 라이프 사이클에는 몇가지 콜백 메소드들이 포함돼 있습니다.


- onAttach() - activity 에 fragment가 attach 될 때 호출 됨


- onCreate() - fragment를 생성할 때 호출 됨. 이 메소드는 fragment 가 paused 됐거나 stopped 된 후에 다시 resumed 될 때 계속 남아있기를 바라는 fragment의 item들을 초기화 할 때 사용합니다.


- onCreateView() - fragment의 view를 생성하기 위해 호출 합니다.


- onActivityCreated() - 액티비티의 onCreate() 메소드가 return 됐을 때 호출 됩니다.


- onStart() - fragment가 visible하게 됐을 때 호출 됩니다. 이 메소드는 액티비티의 onStart() 메소드와 연관이 있습니다.


- onResume() - fragment가 visible되고 running 할 때 호출 됩니다. 이 메소드는 activity의 onResume() 메소드와 관련이 있습니다.


- onPause() - fragment가 visible 이지만 focus를 받고 있지 않을 때 호출 됩니다. 이 메소드는 activity의 onPause() 메소드와 연관이 있습니다.


- onStop() - fragment가 visible이지 않을 때 호출 됩니다. 이 메소드는 액티비티의 onStop()메소드와 연관이 있습니다.


- onDestroyView() - fragment가 save 되거나 destroy 되어야 할 때 호출 됩니다. view hierarchy가 fragment로부터 remove 됩니다.


- onDestroy() - fragment가 더이상 사용되지 않을 때 호출 됩니다. fragment와 관련된 view hierarchy는 없지만 fragment는 계속 activity에 attach 돼 있습니다.


- onDetach() - fragment가 activity에서 detach될 때 호출 됩니다.  fragment에 할당된 리소스가 release 됩니다.

반응형

Comment


Using the AsyncTask Class

 

이번 글에서는 쓰레드를 생성하고 관리하기 위해 AsyncTask 클래스를 사용하는 방법을 알아보겠습니다.
 기본적으로 AsyncTask 클래스는 백그라운드에서 프로세스를 실행하기 위해 비동기 task 를 생성합니다. 이 AsyncTask 클래스는 프로그레스를 보여주고 태스크를 완료하기 위해 쓰레드와 동기화 하는 여러개의 이벤트 핸들러들을 제공합니다. AsyncTask 는 여러 태스크드을 실행할 수 있습니다. 네트워크에서 데이터를 받아오고 백그라운드로 데이터를 저장하고 하는 일들을요.
 

AsyncTask에는 약간의 design flaw들이 있는데요. 이것들은 핸들러와 pool에 근거하고 한번에 몇개의 쓰레드만을 핸들링 할 수 있습니다. 왜냐하면 AsyncTask는 compatibility library에 포함되어 있기 때문이죠.


AsyncTask를 초기화하기 위해 AsyncTask 클래스를 extend 하고 파라미터를 제공합니다. 그 파라미터 들은 Input Parameters, Progress Values, Result Values 가 됩니다. 이 파라미터들을 전달하고 싶지 않으면 그냥 void 를 사용하시면 됩니다. 이 subclass는 아래의 이벤트 핸들러들을 override 해야 합니다.

- doInBackground : 이 메소드는 백그라운드 쓰레드에서 실행합니다. user와 interact 가 필요 없는 코드를 이 핸들러에 넣습니다. Input Parameter 가 이 메소드에 전달 될 겁니다. 이 메소드에서 publishProgress() 메소드가 호출되고 onProgressUpdate() 메소드가 메인 쓰레드안에서 실행 될 겁니다. 이 publishProgress() 와 onProgressUpdate() 메소드들을 사용해서 이 백그라운드 쓰레드는 메인 쓰레드와 communicate를 해 작업의 progress를 보여주기 위해 UI 엘리먼트 들을 업데이트 합니다. 이 메소드에서 작동하는 코드는 별도의 백그라운드 쓰레드에서 실행되는 것을 기억해 두세요.


- onProgressUpdate() : 이 핸들러를 override 해서 task 내에서 작업의 progress를 가리키기 위해 UI를 업데이트 합니다. 이 핸들러는 publishProgress()로 전달된 파라미터 세트를 받습니다. 이 핸들러는 실행이 되면 쓰레드와 동기화 됩니다. 그러니까 안전하게 UI 엘리먼트를 수정할 수 있습니다.


- onPostExecute : 이름에서 알 수 있듯이 이 메소드는 doInBackground() 메소드가 완료된 후에 호출 됩니다. doInBackground() 메소드에 의해 return 된 Result Value들은 이 메소드에 전달 됩니다. 이 핸들러는 비동기 task 가 완료 됐을 때 inform 될 수 있습니다.

이 AsyncTask 클래스는 두개의 콜백 메소드를 제공합니다.


- onPreExecute : 이름을 보면 알 수 있듯이, 이 메소드는 doInBackground() 메소드가 호출되기 전에 호출 됩니다. 이 메소드는 메인 쓰레드에서 작동되고 setup 같은 작업을 여기서 하게 됩니다.


- onCancelled : 쓰레드의 cancellation을 관리 합니다. 이 메소드는 쓰레드의 실행을 interrupt하고 onPostExcute() 메소드의 실행을 막습니다.


NOTE : onPostExecute, onPreExecute, onProgressUpdate, onCancelled를 오버라이드 하는 것은 선택사항 입니다. 반드시 오버라이딩을 해야만 하는 것은 아닙니다.


백그라운드 쓰레드에서 1에서 10까지의 숫자를 순서대로 표시하는 어플리케이션을 만들겠습니다.
이름은 AsyncTasksApp 입니다. 일단 화면에 숫자를 표시하려면 TextView를 만들어야 겠죠.


아래 activity_async_tasks_app.xml 파일이 있습니다.


<LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/seqnums"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="@dimen/text_size"
        android:textStyle="bold"  />
</LinearLayout>


한 개의 TextView 만 있죠?


다음은 자바 파일을 보겠습니다.

AsyncTasksAppActivity.java


public class AsyncTasksAppActivity extends Activity {
    TextView seqNums;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_tasks_app);
        seqNums=(TextView) findViewById(R.id.seqnums);
        new PrintSequenceTask().execute(1);
    }

    private class PrintSequenceTask extends
        AsyncTask<Integer, Integer, Void> {
        @Override
        protected void onPreExecute() {
            seqNums.setText("Sequence numbers begins");
        }

        @Override
        protected Void doInBackground(Integer... args) {
            for (int i = args[0]; i <= 10; i++) {
                publishProgress(i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... args) {
           seqNums.setText(args[0].toString());
        }

        @Override
        protected void onPostExecute(Void result) {
           seqNums.setText("Sequence numbers over");
        }
    }
}




딱 보면 PrintSequenceTask 클래스가 AsyncTask를 extends 한 것을 보실 수 있으실 겁니다. onCreate() 메소드를 보시면 이 클래스를 호출할 때 파라미터를 1을 전달했는데요. 그건 숫자를 1 부터 표시하기 위해 전달 한 겁니다.  이 PrintSequenceTask 클래스가 호출 된 후 doInBackground() 메소드가 실행되기 전데 onPreExecute() 메소드가 실행되서 Sequence numbers bigins 라는 텍스트를 화면에 뿌려 줄 겁니다. 그 다음에 doInBackground() 메소드가 실행되서 for 루프가 실행되겠죠. 아까 onCreate() 에서 1을 파라미터로 받았으니까 int i 는 1 부터 실행 될 겁니다. 1에서 10까지 for 루프가 돌면서 그 값들을 publishProgress()에 할당 합니다. 이 publishProgress() 메소드가 호출 되면 메인 쓰레드에서 onProgressUpdate() 메소드가 실행됩니다. 이 때 publishProgress() 에 전달 된 값이 그대로 onProgressUpdate()에도 전달이 됩니다. 그러니까 여기서는 1부터 10까지의 숫자가 전달이 되겠죠.


onProgressUpdate() 메소드에서는 TextView에 전달된 값을 표시하는 로직이 있네요. 1에서 10까지 숫자가 표시될 때 각각 1초간의 쉬는 시간이 있습니다. 최종적으로 doInBackground() 안의 작업이 끝나게 되면 즉 모든 숫자들이 다 표시되고 난 후 onPostExecute() 메소드가 실행됩니다. 여기서는 Sequence numbers over 라는 텍스트가 화면에 뿌려지도록 했습니다.

그러니까 전체적으로 보면 어플리케이션이 실행되면 Sequence numbers begins라는 글자가 화면에 표시되고 1부터 10까지의 숫자가 1초 간격으로 차례대로 표시되다가 마지막에는 Sequence numbers over 라는 텍스트가 표시 될 겁니다.


Summary

이 챕터에서는 어플리케이션의 퍼포먼스를 더 좋게 만드는 것과 관련해서 몇가지 다뤄봤습니다. dual-core chips와 quad-core 프로세서가 어플리케이션 experience를 더 좋게 만든다는 것도 다뤘구요. garbage collection이 더 이상 사용되지 않는 메모리를 새로운 어플리케이션에 할당한다는 내용도 배웠습니다. 또한 어플리케이션의 task를 쓰레드를 사용해서 백그라운드로 돌리는 절차에 대해서도 보셨습니다. 마지막으로 백그라운드에서 비동기적으로 태스크를 생성화고 관리하기 위해 AsyncTask 클래스를 어떻게 사용하는지에 대해서도 배웠습니다.

반응형

Comment


Using Multiple Threads



이번에는 두개의 쓰레드를 실행시키는 것에 대해 알아 보겠습니다. 두개의 쓰레드를 통해서 어떻게 두개의 태스크가 동기적으로 실행되는지에 대해 보게 될 겁니다. 이 두개의 쓰레드에 대해 한개의 핸들러를 생성할 겁니다. 이 쓰레드들은 1에서부터 10까지의 순차적인 숫자를 표시합니다. 첫번째 쓰레드가 1초에 숫자 하나씩 1에서부터 10까지 차례대로 표시를 하고 두번째 쓰레드는 2초에 한번씩 1부터 10까지 차례대로 표시를 합니다.


이 두 쓰레드들은 동기적으로 task를 시작할 겁니다. 하지만 첫번째 쓰레드가 먼저 끝나겠죠. 두 번째 쓰레드가 끝나는 시간의 딱 반만 소요 될 겁니다.



handleMessage() 메소드는 이 순차적인 번호를 표시하기 위해 핸들어 안에서 오버라이드 될 겁니다. 순차적인 번호들은 Message 파라미터를 통해서 이 메소드에 넘겨집니다. 이 두 쓰레드의 Message object를 구별하기 위해서 Message object내의 calling 쓰레드 이름이 Bundle 에서 명명 됩니다. 



이 앱을 만들기 위해서 layout xml 에는 4개의 TextView 가 들어가게 됩니다. 두개는 쓰레드 이름이 표시될 거고 두개는 그 값이 표시될 겁니다. 




activity_multiple_threads_app.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MultipleThreadsAppActivity" >
    <TextView
        android:id="@+id/thread1" 
        android:text="Thread1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dip" 
        android:layout_marginLeft="20dip"
        android:textSize="@dimen/text_size"
        android:textStyle="bold" />
       <TextView
        android:id="@+id/thread2" 
        android:text="Thread2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dip" 
        android:layout_marginLeft="100dip"
        android:layout_toRightOf="@id/thread1"
        android:textSize="@dimen/text_size"
        android:textStyle="bold"/>
    <TextView
        android:id="@+id/seqnums1" 
        android:text="0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dip" 
        android:layout_marginLeft="30dip"
        android:layout_below="@id/thread1"
        android:textSize="@dimen/text_size"
        android:textStyle="bold" />
   <TextView
        android:id="@+id/seqnums2" 
        android:text="0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="15dip" 
        android:layout_marginLeft="150dip"
        android:layout_below="@id/thread2"
        android:layout_toRightOf="@id/seqnums1" 
        android:textSize="@dimen/text_size"
        android:textStyle="bold" />

</RelativeLayout>



각각 id를 갖고 있고 이 id로 나중에 자바 파일에서 각 TextView를 구분해서 활용할 겁니다.
그리고 textSize는 dimention 에서 정의된 text_size를 사용할 거구요.
이 값은 res/values 폴더 안에 dimens.xml 이라는 파일 안에 있습니다.



그 다음에 핸들러를 정의하고  이 두 쓰레드들의 메세지를 보내고 받기 위해 아래와 같이 자바파일을 만듭니다.


MultipleThreadsAppActivity.java



public class MultipleThreadsAppActivity extends Activity {
static    private TextView seqNums1, seqNums2;
       
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_multiple_threads_app);
        seqNums1 = (TextView)findViewById(R.id.seqnums1);
        seqNums2 = (TextView)findViewById(R.id.seqnums2);
    }
   
static    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg)        {
            String threadInvoked=msg.getData().getString("threadName");
            if (threadInvoked.equals("thread1"))
        seqNums1.setText(msg.obj.toString());
            if (threadInvoked.equals("thread2"))
                seqNums2.setText(msg.obj.toString());
        }
        };

        @Override
        protected void onStart() {
            super.onStart();
     Thread     thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
             try {
           for(int i=1;i<=10;i++){
                                Thread.sleep(1000);
                                Message msg = new Message(); 
                                Bundle bundle = new Bundle();
                                bundle.putString("threadName", "thread1");
                                msg.setData(bundle);
                                msg.obj=String.valueOf(i);
                                handler.sendMessage(msg);
       }
                            } catch (InterruptedException e) {
                               e.printStackTrace();
                            }   
        }
        });

 Thread thread2 = new Thread(new Runnable()        {
    @Override
        public void run() {
         try {
           for(int i=1;i<=10;i++){
                                 Message msg = new Message();
                                Thread.sleep(2000);
                                Bundle bundle = new Bundle();
                                bundle.putString("threadName", "thread2");
                                msg.setData(bundle);
                                msg.obj=String.valueOf(i);
                                handler.sendMessage(msg);
           }
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
        }
        });
 thread1.start();
 thread2.start();
    }
}



onCreate() 메소드를 보면 숫자가 들어갈 TextView의 객체들을 만들었습니다. 다음에 onStart() 메소드를 보면 Thread class를 사용하는 걸 보실 수 있습니다. 두개의 쓰레드를 만들어서 두개의 runnable object에 사용하고 있습니다. 1에서부터 10까지의 숫자를 표시하기 위해서 run() 메소드에서는 Message object를 생성했습니다. 그리고 Bundle 객체도 하나 생성해서 그 안에 쓰레드 이름을 key, value 한 쌍으로 넣었습니다. 그것을 Message에 setData()를 사용해서 세팅했습니다. 그리고 msg.obj에 숫자를 대입합니다. 그리고 마지막으로 이 메세지를 Handler object를 통해서 TextView에 전달을 하고 그 값이 화면에 뿌려지게 됩니다.
첫번째 쓰레드는 1초 간격으로 작업을 하고 두번째 쓰레드는 2초 간격으로 작업을 합니다.


이 어플리케이션을 실행하고 나면 두 쓰레드 모두 1서부터 숫자를 표시할 겁니다. 첫번째 쓰레드는 10까지 표시하는데 두번째 쓰레드보다 반 밖에 시간이 안 걸릴 겁니다.

반응형

Comment


Understanding Threads



액티비티, 서비스, 브로드캐시트 리시버 같은 모든 안드로이드 어플리케이션 컴포넌트들은 main application thread 안에서 실행됩니다. 다른 말로 하면 main application thread 는 모든 user-interface와 관련된 task들을 수행합니다. 어플리케이션의 효율성을 증가시키기 위해서 오랫동안 수행되는 혹은 비동기로 수행되는 태스크들과 user와 interact를 하지 않는 태스크들은 백그라운드 쓰레드 수행을 하도록 합니다.



또한 운영체제는 메인 쓰레드가 message에 responsive 하기를 기대하고 있기 때문에 이 메인 쓰레드를 block 할 수 있는 operation은 별도의 쓰레드에서 실행 됩니다. 그래서 메인 쓰레드는 다른 쓰레드들을 컨트롤 하고 User Interface (UI) element들을 업데이트 합니다.


백그라운드 쓰레드로 프로세스 태스크를 옮기는 방법에는 아래의 두가지가 있습니다.


- Handler class 사용하기
- AsyncTask class 사용하기



우선 Handler class 를 사용해서 백그라운드 쓰레드를 사용해서 태스크를 실행하는 방법을 알아 보겠습니다.



백그라운드 프로세스 태스크를 위해 우리가 만든 thread를 implement 하고 그것과 동기화 하기 위해 Handler class 를 사용할 겁니다. 핸들러 클래스는 그것을 생성한 쓰레드와 연결 될 겁니다. 그래서 핸들러 클래스가 초기화 된 그 쓰레드에 코딩을 하실 수 있습니다. 핸들러 클래스와 communicate 할 수 있는 두가지 방법은 messages와 runnable object를 통해서 하는 것입니다. 그 핸들러 클래스에서 object 를 생성한 후 그 object는 현재 쓰레드의 MessageQueue와 연결된 메세지와 runnable object들 처리합니다. 이 태스크는 현재의 쓰레드에 의해 실행되어야 하며 그 실행되기 까지 MessageQueue에서 기다리게 됩니다.



메세지를 처리하기 위해 handleMessage() 메소드를 오버라이드 합니다. runnable object를 처리하기 위해서는 post() 메소드를 사용합니다. 이 쓰레드는 sendMessage(Message msg) 메소드나 sendEmptyMessage() 메소드를 통해서 메세지를 post 할 수 있습니다.



핸들러 클래스를 사용하는 개념을 이해하기 위해 쓰레드를 사용해서 1부터 10까지 숫자를 순차적으로 display 하는 어플리케이션을 만들겠습니다. 이 앱의 이름은 TreadApp 이라고 하겠습니다. 여기에는 순차적인 숫자들만 표시합니다. 그러니까 레이아웃에는 TextView 만 필요하겠죠. 아래 layout xml을 참조하세요.



activity_thread_app.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <TextView
        android:id="@+id/seqnums"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="@dimen/text_size"
        android:textStyle="bold" />
</LinearLayout>


보시다시피 TextView 의 id는 seqnums 입니다. 자바 파일 안에서 이 id를 가지고 활용을 하게 될 겁니다. 폰트 사이즈는 dimension 리소스의 text_size를 사용해서 표시 됩니다. 쓰레드 핸들러를 정의해서 메세지를 보내고 받기 위해 아래와 같이 자바 코드를 만듭니다.


ThreadAppActivity.java



public class ThreadAppActivity extends Activity {
    static TextView seqNums;
   
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread_app);
        seqNums=(TextView)findViewById(R.id.seqnums);
    }

    static Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
        seqNums.setText(msg.obj.toString());       
        }
    };

    @Override
    protected void onStart() {
        super.onStart();
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                for(int i=1;i<=10;i++){
                    try {
                        Thread.sleep(1000);
                        Message msg = new Message();
                        msg.obj=String.valueOf(i);
                        handler.sendMessage(msg);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
    }
}


onCreate() 메소드를 보시면 위에서 만들었던 layout xml을 contentView로 세팅을 하고 그 안에 있는 TextView에 대한 객체도 만들었습니다.
onStart() 메소드를 보시면 여기서 Thread 클래스를 사용하는걸 보실 수 있을 겁니다. 바로 runnable object와 함께 쓰레드를 사용하는 방법입니다.  run() 메소드에서 이 쓰레드를 실행시킵니다.



TextView에 1에서 10까지의 순차적인 번호를 표시하기 위해 run() 메소드에서 Message object를 생성하고 그 순차적인 번호를 add 합니다. 이 값들이 obj에 add 되고 나서 이 메세지를 Handler object를 통해서 send 하게 됩니다.



handler 라고 하는 Handler object가 생성돼 있죠? 이 안에 handleMessage()라고 하는 콜백 메소드안에서 이 obj 값이 TextView에 표시되도록 하는 코드가 있습니다.



이 앱을 실행시키면 1초 간격으로 1부터 10까지 화면에 표시할 겁니다. 




반응형

Comment


Understanding the Utility of Garbage Collection



Garbage collection은 메모리 관리와 관련 된 건데요. dynamic 하게 할당하고 자동으로 recycle 하도록 하는 겁니다. 만약 프로그램에 의해 사용되어지는 메모리가 더 이상 사용되어 지지 않게 됐다면 시스템 적에서 자동적으로 recover 되는 겁니다. Garbage collection은 garbage collector 에 의해 수행 됩니다. 메모리 할당과 recycle을 위해 잘 보고 있다가 할당하고 있던 프로그램이 더 이상 사용하지 않게 되면 다른 어플리케이션에 그것을 할당하는 거죠. Garbage collector는 메모리 할당이 특정 한계점에 도달하면 자동으로 invoke 됩니다. 그래서 이 Garbage collector 는 개발자가 메모리 블럭의 할당 해제를 직접 하지 않아도 알아서 메모리 누수나 아니면 너무 일찍 할당 해제해 생기는 문제점들을 사전에 막아 줍니다.



안드로이드에서 메모리 관리는 Dalvik의 garbage collector가 처리 합니다. mark 하고 sweep 하는 방법으로 작동 합니다. 즉 메모리가 어떤 객체에 의해 사용되어 지만 mark 를 하고 그 mark 된 객체는 garbage collect되지 않도록 하는 식이죠) mark 되지 않은 객체들은 sweep 되서 다른 어플리케이션이 사용할 수 있도록 메모리를 free 시킵니다. garbage collecton task는 대개 100에서 200 ms 가 소요 됩니다. 그리고 garbage collection을 하는 동안 어플리케이션의 퍼포먼스는 떨어지게 됩니다. 실행되고 있는 어플리케이션은 이 marking 과 sweeping 작업이 진행되는 동안 suspend 될 겁니다. 작고 아주 짧게 살아있는 object들을 많이 가지고 있는 어플리케이션들은 이 garbage collector를 더욱 자주 실행시키는 결과를 초래할 겁니다. 이렇게 garbage collector 가 너무 자주 실행되는 것을 방지하기 위해 Android SDK 는 allocation tracker 라는 DDMS(Dalvik Debug Monitor service)의 한 부분인 아주 유용할 tool을 제공합니다.



Note
기술적으로 안드로이드에는 garbage collection 이 없습니다. 왜냐하면 안드로이드는 C 언어를 기반으로 한 Operating System 이기 때문이죠. 여기서는 Dalvik runtime내에서 사용되는 Garbage Collection에 대해 얘기하고 있는 겁니다.



DDMS는 안드로이드 SDK를 다운로드 하면 그 안에 딸려서 있는 아주 강력한 디버깅 툴 입니다. DDMS는 이클립스 IDE의 오른쪽 위에 있는 DDMS 아이큰을 선택하거나 Window - Open Perspective - DDMS 옵션을 선택하시면 실행할 수 있습니다.



DDMS를 실행할 때 자동적으로 연결된 Android device나 실행중인 에뮬레이터에 연결 됩니다.



아래는 DDMS를 실행 시켰을 때의 화면 입니다. 




DDMS 의 왼쪽 위에는 Devices 라는 탭이 있습니다. 이것은 컴퓨터에 연결된 디바이스들을 보여 줍니다. 에뮬레이터도 해당 되구요. 그 중 하나를 선택하면 오른쪽에 그에 대한 정보가 나옵니다. 이 Devices 탭 안에는 아래와 같은 아이콘들이 있습니다.

Debug - 디버깅을 하기 위해 사용


Update Hean - 해당 프로세스의 heap information을 볼 수 있도록 함. 이 아이콘을 클릭하고 난 후 오른쪽 pane 의 heap tab을 사용 해 heap 정보를 볼 수 있다.


Dump HPROF file - 메모리 누수를 감지할 수 있는 HPROF 파일을 보여준다.


Cause GC - Gabage collection procedure를 시작 시킨다.


Update Threads - 해당 프로세스의 쓰레드 정보를 보여 준다. 이 아이콘을 클릭 한 후 오른쪽 pane의 Thread 텝을 클릭하면 이 쓰레드 정보를 볼 수 있다.


Start Method Profiling - 다른 메소드들이 몇번 호출 됐는지를 알 수 있다. 그리고 각각이 얼마나 시간을 소비 했는지도 알 수 있다.


Stop Process - 해당 프로세스를 stop 시킨다.


Screen Cpature - 디바이스나 에뮬레이터의 screen 을 capture 한다.


Dump View Hierarchy for UI Automator - screen level trace를 capture 하기 위해 특정 파일을 prompt 한다. CPU frequency changes, CPU idle events, CPU load, Disk I/O 등등의 이벤트들을 Trace 한다. 그리고 이런 trace 는 파일로 저장된다.



그리고 오른쪽 pane 에는 아래와 같은 tab들이 있습니다.

Thread - 해당 프로세스에 있는 쓰레드에 대한 정보를 보여 준다.
Heap - 그 프로세스의 Heap 정보를 보여 준다. Cause GC 버튼을 선택하면 Garbage collection 프로세스가 시작한다. object type과 할당된 메모리의 크기 등이 표시된다. object type을 선택하면 bar graph가 표시 몇개의 object들이 할당됐고 얼마나 메모리를 차지하고 있는지 등에 대한 정보들을 보여 줄 것이다.



Allocation Tracker - 어플리케이션에 할당당된 object들을 추적한다. Start Tracking 버튼을 클릭하면 여러분이 분석하고자 하는 코드를 실행시키기 위해 어플리케이션과 interact 하게 될 것이다. Get Allocations 버튼을 클릭하면 어플리케이션에 할당된 object들의 리스트를 보게 된다. 이 리스트는 위쪽에 표시되는데 만약 그 object중에 하나를 선택하게 되면 그 할당을 이끌고 있는 track trace가 그 아래쪽에 표시될 것이다. 할당된 object의 타입 뿐만 아니라 쓰레드, 클래스, 파일, 그리고 어떤 라인인지에 대한 정보도 볼 수 있다. Stop Tracking 버튼을 클릭하면 데이터를 모두 clear 하고 restart 할 것이다.






반응형

Comment


Cores and Threads



안드로이드 어플리케이션을 빠르고 효율적으로 실행시키기 위해 dual-core chips 와 quad-core processor 들이 개발 되었고 안드로이드 디바이스에 적용되었습니다. 이런 multicore architecture들의 장점을 활용하기 위해 thread scheduler 같은 안드로이드 모듈에 관련된 기술이 만들어 졌습니다.  이 thread scheduler가 하는 일은 서로 다른 core들 사이에서 어플리케이션이 분할 되서 할당되어 그 성능을 최대치로 활용하는 겁니다.





어플리케이션을 효과적으로 운영하기 위해서는 메모리 관리도 중요한데요. garbage collectgor 를 가동하는 것은 메모리 recycling 의 아주 핵심적인 부분 입니다. 그리고 한가지 더 언급하자면 어플리케이션의 최적화 입니다. 바로 백그라운드 쓰레드에 의해서 서로 interactive 하지 않은 태스크를 갖는 겁니다. 쓰레드는 어플리케이션의 멀티 태스킹기능을 가능하도록 해 줍니다.



요 며칠간 multicore processor architecture를 활용하는 것과 메모리 관리에 있어서의 garbage의 역할 그리고 쓰레드와 AsyncTask 클래스를 사용해서 백그라운드로 어플리케이션의 task 들을 수행해 퍼포먼스를 개선시키는 것과 관련해서 다뤄 보겠습니다. 



Multicore Processor Architectures 의 활용 이해하기


당연히 멀티태스킹이 싱글 태스킹 보다 더 나은 겁니다. 싱글 태스킹에서는 한 task 가 작업을 수행할 때 다른 task들은 작업하는 task 가 수행을 끝낼때까지 stop 해 있어야 합니다. 반면에 멀티태스킹은 한가지 task 를 동시에 더 진행할 수 있습니다. 이 multi tasking 을 수행하도록 하기 위해 multicore processor architecture 가 사용됩니다. 안드로이드 3.0 이상의 버전에서 바로 이 single processor architecture나 multicore processor architecture를 활용할 수 있는 기능이 제공 되었습니다.



Multiple core들은 어플리케이션의 작업을 서로 다른 core에서 동시에 수행하도록 나눠 줌으로서 퍼포먼스 향상을 가져 옵니다. 이 multicore processor 는 thread scheduler에 의해 효율적으로 사용할 수 있습니다. multicore mobile processor의 잇점은 모든 core들에  소프트웨어의 task들이 할당되고 schedule 될 때 진정한 의미가 있는 것입니다. 만약 scheduling 이 제대로 작동하지 않으면 오히려 그 어플리케이션의 퍼포먼스는 안 좋아 질 수 있습니다.



많은 안드로이드 기계들이 dual-core chip들이나 quad-core processor 기능들을 가지고 있습니다. 이 dual- 그리고 quas-core 전화기를 최적화시키기 위해 몇가지 변화가 있었습니다. 그리고 그 중에 thread scheduler 는 이 새로운 quad-core 와 dual-core chip들에 대해 최고의 퍼포먼스를 보여줄 수 있도록 개선 됐습니다.


반응형

Comment


오늘은 Android Tablet Developer's Cook Book에 나오는 JSON 관련 예제를 공부할 겁니다.


Chapter 15에 나오는 예제인데요. ConsumeJSONWebserviceApp 이라는 프로젝트 입니다.


일단 SampleJSON.php 라는 파일이 아래 경로에 있습니다.


http://www.gosolkit.com/SampleJSON.php


이 php가 만들어낸 json 데이터는 아래와 같습니다.


[{"state":"Alabama","capital":"Montgomery","latitude":"32.361538","longitude":"-86.279118"},{"state":"Alaska","capital":"Juneau","latitude":"58.301935","longitude":"-134.419740"},{"state":"Arizona","capital":"Phoenix","latitude":"33.448457","longitude":"112.073844"},{"state":"California","capital":"Secramento","latitude":"36.448457","longitude":"112.073844"},{"state":"Gangwon","capital":"Chunchon","latitude":"33.448457","longitude":"112.073844"},{"state":"Rhode Island","capital":"Providence","latitude":"33.448457","longitude":"112.073844"},{"state":"New Jersey","capital":"Trenton","latitude":"33.448457","longitude":"112.073844"}]

위 URL은 책에 있는 URL이랑은 다릅니다.

제 개인 URL 을 사용했구요. json 데이터도 안에 그 내용을 조금 더 추가했습니다.


원격에 있는 php로 생성한 JSON 파일을 받아와서 안드로이드 앱에서 사용하는 예제 입니다.


우선 첫번째로 layout xml 파일을 보죠.


activity_consume_jsonwebservice_app.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"> 
    <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/state_name"
            android:hint="Enter State Name" 
                android:textSize="@dimen/text_size"   />
    <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/submit_btn"
            android:text="Submit"
                android:textSize="@dimen/text_size" />
    <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/response"
                android:textSize="@dimen/text_size" />
</LinearLayout>


LinearLayout으로 감쌌고 orientation은 vertical 입니다.

그 안에 EditText와 Button 그리고 TextView 이렇게 세개의 view 가 있습니다.



EditText에 주 이름을 넣고 Submit 버튼을 누르면 그 아래 TextView에 원격에서 받은 JSON 데이터를 파싱해서 뿌려 줄 건가 봅니다.


그러면 이제 자바 파일을 보죠.


ConsumeJSONWebserviceAppActivity.java


public class ConsumeJSONWebserviceAppActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_consume_jsonwebservice_app);
        Button submitButton = (Button)this.findViewById(R.id.submit_btn);
        submitButton.setOnClickListener(new Button.OnClickListener(){
             public void onClick(View v)  { 
                    new ReadJSONFeed().execute("http://www.gosolkit.com/SampleJSON.php");               
             }
        });
    }
     
    private class ReadJSONFeed extends AsyncTask<String, String, String> {
        protected void onPreExecute() {}

        @Override
        protected String doInBackground(String... urls) {
       HttpClient httpclient = new DefaultHttpClient();
       StringBuilder builder = new StringBuilder();
            HttpPost httppost = new HttpPost(urls[0]);
           try {
           HttpResponse response = httpclient.execute(httppost);
        StatusLine statusLine = response.getStatusLine();
       int statusCode = statusLine.getStatusCode();
       if (statusCode == 200) {
            HttpEntity entity = response.getEntity();
            InputStream content = entity.getContent();
            BufferedReader reader = new BufferedReader(new InputStreamReader(content));
            String line;
            while ((line = reader.readLine()) != null) {
                builder.append(line);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }     
    return builder.toString();
        }

        protected void onPostExecute(String result) {
            String state="";
            String stateInfo="";
            EditText stateName = (EditText) findViewById(R.id.state_name);
            String searchState=stateName.getText().toString();
            try{
             JSONArray countriesArray = new JSONArray(result);       
                   for (int i =0 ; i<countriesArray.length();i++) {
                       JSONObject jObject = countriesArray.getJSONObject(i);
                      state = jObject.getString("state");
                      if(searchState.equalsIgnoreCase(state))
                      {
                          stateInfo+="Capital: "+jObject.getString("capital")+"\n";
                          stateInfo+="Latitude: "+jObject.getString("latitude")+"\n";
                          stateInfo+="Longitude: "+jObject.getString("longitude")+"\n";
                      }
                   }
            }
             catch (JSONException e) {
                 e.printStackTrace();
                 }
            TextView resp = (TextView) findViewById(R.id.response);
            if(stateInfo.trim().length() >0 )
             resp.setText(stateInfo);   
            else
                resp.setText("Sorry no match found");
        }
    }
}



그렇게 복잡한거 같지는 않죠?


먼저 onCreate()를 보죠.

activity_consume_jsonwebservice_app.xml 을 Layout 으로 사용한다고 세팅했습니다.

바로 위에서 본 xml 입니다.


그리고 버튼에 대한 객체를 만들고 이 버튼에 onClickListener를 달았습니다.

이 버튼을 클릭하면 ReadJSONFeed() 라는 클래스를 호출하네요. 그리고 excute() 라는 메소드에 아까 소개했던 원격 URL 을 pass 합니다.


그러면 ReadJSONFeed() 클래스를 보겠습니다.

이 클래스는 AsyncTask 를 상속합니다.

여기를 보시면 해당 API를 보실 수 있습니다.


UI thread를 쉽게 사용할 수 있게 해 준다고 하네요. background 로 실행되게 해서 그 결과를 UI thread에 publish 하는 군요. 따로 thread나 handler들을 작성하지 않아도 된다고 하네요.

이 AsyncTask는 짧은 기간 동안 수행할 작업에 사용됩니다. 만약 긴 기간동안 수행할 작업을 쓰레드로 사용하시려면 Executor, ThreadPoolExecutor and FutureTask 같은 java.util.concurrent pacakge 를 사용 하셔야 합니다.

이 asynchronous task(비동기 작업)은 Params, Progress and Result 이렇게 3가지가 있고 4개의 step들이 있습니다.

onPreExecute, doInBackground, onProgressUpdate and onPostExecute


그러면 계속 ReadJSONFeed 클래스를 보겠습니다.

첫번째 줄에는 위에 봤든 onPreExecute() 메소드가 있네요. 그 안에는 아무것도 없구요.

그냥 넘어가도 되겠습니다.


그 다음에는 또 위에서 봤던 doInBackground() 메소드가 나옵니다.

이 안에 어떤 로직을 넣으면 Background 로 돌아가게 됩니다.


우선 HTTPClient와 HttpPost, StringBuilder 를 초기화 했습니다.

HttpResponse를 초기화 하면서 httpclient의 execute메소드를 통해 httppost를 pass 해 줍니다.

여기 httppost에는 아까 버튼 눌렀을 때 pass 했던 url이 들어 있습니다.

그러면 response 안에 그 결과값 즉 json 데이터들이 담기겠죠.

그 다음에는 StatusLine을 초기화 하면서 이 response의 line을 get 합니다.

즉 json 내용을 get 하는거죠.

그리고 이 statusLine 의 statusCode를 statusCode라는 변수에 담습니다.

이 statusCode에는 성공했는지 실패 했는지에 대한 정보가 담기겠죠.

다음 if 문에서는 만약에 이 작업이 성공했으면 HttpEntity 를 생성해서 여기에 response의 Entity를 가져 옵니다. 그리고 그 내용을 InputStrean에 담고요.

다음에 BufferedReader를 이용해서 그 내용을 읽습니다.


Java에서 파일 내용을 읽어 들일 때 사용하는 InputStream과 BufferedReader가 여기서도 사용되네요.

그 다음에 line이라는 String을 만들어서 모든 줄을 이 line에 넣습니다.


여기까지가 doInBackground() 메소드가 하는 일입니다.

간단하네요. 원격에 있는 정보를 가져와서 이것을 사람이 읽을 수 있는 정보로 line이라는 String에 담은 겁니다.


그 다음에 실행되는 메소드는 onPostExecute() 입니다.

한번 볼까요?


state, stateInfo 라는 String을 만들었습니다. 주 이름과 그 주에 대한 정보를 담을 변수들 인 것 같습니다.

그리고 layout xml에서 만든 EditText에 대한 변수를 만들었습니다.


그리고 searchState라는 String에 이 stateName의 내용을 담을 겁니다.


다음에 나오는 내용들은 JSON을 파싱하는 로직 입니다.

아까 doInBackGround에서는 line이라는 String에 이 JSON 데이터를 그냥 담았죠.

이것을 파싱해서 사람이 알아볼 수 있도록 화면에 뿌려줄 겁니다.


onPostExecute(String result) 라고 위에 돼 있죠. 여기서는 pass 받은 string을 result 라는 변수이름으로 사용할 겁니다.


보시면 이 result 를 countriesArray라는 JSONArray 에 담습니다.


그 다음 for 문을 돌려서 아까 만들었던 변수들에 해당 내용들을 담습니다.

state에는 주 이름이 그리고 searchState에 있는 내용들은 Capital, Latitude, Longitute 별로 따로 사람이 읽기 좋게 편집해서 stateInfo에 담습니다.


그 다음에는 try catch 문 밖을 보시면 되는데요.


TextView 객체를 만들었죠.

이 text 뷰에 위에 만들었던 stateInfo를 setText() 해서 화면에 표시합니다.


그러면 아래와 같은 화면을 보실 수 있습니다.





여기서의 주요 개념은 AsyncTask 를 이용해서 쉽게 Background 작업을 하도록 한다는 것과

원격의 데이터를 받아 오는 방법 여기서는 HttpClient, StringBuilder, HttpPost, HttpResponse 등이 사용됐습니다. 그리고 이 데이터를 바이너리 형태로 돼 있을 텐데 이것을 InputStream과 BufferedReader를 이용해서 사람이 읽을 수 있는 문자로 파싱하는 방법을 배웠습니다.

그리고 그 파싱된 JSON 타입을 다시 key 값을 이용해서 value를 가져와서 필요에 따라 편집하는 방법도 배웠습니다.


아주 중요한 개념들이 많네요.


안드로이드 앱과 웹 서비스와의 네트워킹도 아주 쉽게 사용할 수 있도록 만들었군요.




반응형

Comment



그러면 이번시간에는 TweeningAnimAppActivity.java 파일을 분석해 보겠습니다.


public class TweeningAnimAppActivity extends Activity {
    ImageView imgView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tweening_anim_app);
        Button alphaButton = (Button) findViewById(R.id.alpha_button);
        Button rotateButton = (Button) findViewById(R.id.rotate_button);
        Button scaleButton = (Button) findViewById(R.id.scale_button);
        Button translateButton = (Button) findViewById(R.id.translate_button);
        imgView = (ImageView)findViewById(R.id.imgview);
        rotateButton.setOnClickListener(new View.OnClickListener() {  
             @Override
             public void onClick(View v) {
                 RotateAnimation animation = new RotateAnimation(0,180, Animation.RELATIVE_TO_SELF,0.0f, Animation.ABSOLUTE, 0.2f);
                 animation.setDuration(3000);
                 imgView.setAnimation(animation);
                 animation.start();
             }
         });

        alphaButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Animation animation = new AlphaAnimation(1.0f, 0.1f);
                animation.setDuration(3000);
                imgView.setAnimation(animation);
                animation.start();
            }
        });

        scaleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AnimationSet set = new AnimationSet(true);
                Animation animation1 = new ScaleAnimation(1.0f, 2.0f,1.0f, 2.0f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                animation1.setDuration(3000);
                set.addAnimation(animation1);
                Animation animation2 = new ScaleAnimation(1.0f, 0.5f, 1.0f, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
                animation2.setDuration(3000);
                animation2.setStartOffset(3000);
                set.addAnimation(animation2);
                imgView.startAnimation(set);
            }
        });
        translateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                AnimationSet set = new AnimationSet(true);
                Animation animation1 = new TranslateAnimation(0,-150,0,0);
                animation1.setDuration(3000);
                animation1.setFillAfter(true);
                set.addAnimation(animation1);
                Animation animation2 = new TranslateAnimation(0,0,0,200);
                animation2.setDuration(3000);
                animation2.setStartOffset(3000);
                animation2.setFillAfter(true);
                set.addAnimation(animation2);
                imgView.startAnimation(set);
            }
        });
    }
}




우선 첫 줄에는 ImageView 를 초기화 시켰습니다.

이 ImageView에 애니메이션 효과를 적용할 겁니다.

그 다음 onCreate() 메소드를 봐야겠죠.
이전 글에서 다뤘던 activity_tweening_anim_app.xml 을 ContentView 로 세팅했습니다.
버튼 4개와 1개의 이미지뷰로 구성된 RelativeLayout 이었죠.

그 다음에는 버튼 4개에 대한 객체를 만들었습니다.
xml 에 있는 이런 view를 객체로 만들때는 findViewById()를 사용하죠.
그리고 아까 초기화 했던 imgView 객체도 만들구요.

그 다음에는 4개의 버튼들에 리스너를 달아주는 부분입니다.

rotateButton을 클릭했을 때는 어제 봤던 RotateAnimation 클래스를 실행시켜 줍니다.
애니메이션 시간은 3초로 하고 이 애니메이션은 imgView에 적용합니다.
마지막 animation.start()를 호출해서 실제 애니메이션을 실행합니다.

alphaButton에는 AlphaAnimation()클래스를 호출하는 부분을 넣었습니다.
그 이후는 rotateButton과 같습니다.

scaleButton에는 위와 다른 부분이 있네요.
처음에 AnimationSet()에 대한 객체를 만들었습니다.
그리고 ScaleAnimation()를 3초간 실행한다는 것을 AnimationSet에 add를 하구요.
그 다음에 다시 ScaleAnimation() 을 3초간 실행하고 3초간 StartOffset()을 실행한다는 것을 AnimationSet 에 add를 했습니다.
마지막에는 이 AnimationSet를 start 했구요.

setStartOffset()은 해당 시간만큼 지연해서 애니메이션을 시작한다는 겁니다.
그러니까 여기서는 첫번째 ScaleAnimation()이 3초간 이뤄지니까 그 3초가 지난다음에 두번째 ScaleAnimation()이 시작된다는 얘기죠.

transateButton에도 AnimationSet 가 적용이 됐는데요. 여기는 또 다른 setFillAfter(true) 가 있습니다.
이것을 true로 하게 되면 애니메이션이 끝난 다음에 다시 원래 위치로 돌아가지 않고 종료된 위치에 정지해 있는 겁니다.
여기서는 첫번째 TranslateAnimation() 은 왼쪽으로 150 만큼 3초간 가고 그 다음은 아래로 200만큼 3초간 움직일 것 같네요.
첫번째 애니메이션이 끝난 후 그 끝난 위치에서 이 ImageView가 아래로 움직일 겁니다.
다시 원위치로 가서 아래로 움직이는게 아니라요.
ㄱ 자를 거울로 본 모양으로 ImageView가 움직이겠네요.

이 앱을 실행하면 아래와 같은 화면이 나옵니다.



처음에 Alpha 버튼을 누르면 그림이 3초간 점점 흐려졌다가 다시 원래로 돌아옵니다.
new AlphaAnimation(1.0f, 0.1f)
이게 그렇게 만든건데요.
1.0 에서 0.1 까지 투명도가 변한다는 겁니다.
이건 간단하게 이해할 수 있습니다.
숫자를 반대로 주면 흐린상태에서 점점 뚜렷한 상태로 변하게 되겠죠.
앱 시작할 때 로고를 이렇게 흐리게 했다가 다시 진하게 하는 효과를 줄 수도 있을 것 같네요.

두번째 Rotate 는 이미지를 가운데를 중심으로 바람개비 돌듯이 한바퀴 돌리는 겁니다.
예전에 제가 Spin the Bottle 앱을 만들 때 이 애니메이션으로 구현하려고 막 노력한적이 있었습니다.
막 for문 써서 계속 돌아가다가 일정 시간 지나면 천천히 돌다가 멈추도록 하는거요.

그러다가 이 애니메이션가지고는 Reality 를 살리지 못하겠다고 판단해서 다른 방법을 찾았습니다.
저는 SurfaceView를 사용해서 Spin the Bottle 에서 병 돌아가는 효과를 주었습니다.

어쨌든 이 RotateAnimation() 도 필요할 때가 있을 텐데요.
RotateAnimation animation = new RotateAnimation(0,360, Animation.RELATIVE_TO_SELF,0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
파라미터를 보면요.
처음 두 파라미터인 0도에서 360도 돈다는 것은 쉽게 이해할 수 있습니다.
세번째 파라미터는 해당 object가 돌아가는 중심이라고 했습니다. pivotXType인데요. 여기는 정수형이 들어갑니다.
이 값이 0이면 왼쪽을 나타냅니다.
여기에 들어가는 값은 Animation.ABSOLUTE와 Animation.RELATIVE_TO_SELF, Animation.RELATIVE_TO_PARENT. 등이 있습니다.
이 소스에는 Animation.RELATIVE_TO_SELF 가 적용됐는데요. 그 값은 1 입니다.
이 1이 적용됐을 때 그 객체는 자신의 가운데 포인트를 중심으로 Rotate 하게 됩니다. 바람개비처럼요.
이것을 Animation.ABSOLUTE로 바꿔주면 이미지 왼쪽을 중심으로 이미지가 한바퀴 빙 돌게 됩니다.
이 Animation.ABSOLUTE의 값이 0입니다.
다음에 나오는 Animation.RELATIVE_TO_PARENT 의 값은 2 입니다.
이 값을 여기에 넣으면 이미지가 아니라 화면의 오른쪽 끝을 중심으로 크게 원을 도네요.
여기에 Integer가 들어갈 수 있으니까 제가 한번 3도 넣어보고 4도 넣어 봤거든요.
그랬더니 0 (Animation.ABSOLUTE) 일때와 같은 결과가 나오네요.


그 다음 파라미터는 pivotXvalue 인데 여기서는 0.5f가 설정됐습니다.
이것을 0.9로 한번 바꾸면 어떻게 될까요?
그러면 바람개비 처럼 돌던것이 이미지 오른쪽을 중심으로 도네요.
아까 pivotXType을 Animation.ABSOLUTE로 했을 때 이미지 왼쪽을 중심으로 도는것과 딴 반대의 경우입니다.
1.5로 하면 이미지의 더 오른쪽을 중심으로 돌구요.
그 이후에 나오는 Parameter 인 pivotYType과 pivotYvalue 도 x,y 축만 바꿔서 비슷한 효과를 줍니다.
이 값들을 이것 저것 여러개 바꿔봤는데요.

이 애니메이션을 가지고 flipping 효과를 줄 수는 없겠네요.
이 방법은 다른데서 찾아봐야겠습니다.

그리고 에뮬레이터로 실행하니까 시간이 오래 걸려서 다양한 시도는 못 해 보겠습니다.

다음으로 ScaleAnimation()을 보죠.

ScaleAnimation(1.0f, 2.0f,1.0f, 2.0f,Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
처음부터 4번째까지의 파라미터들은 fromX,toX,fromY,toY 입니다.
그러니까 전체적으로 이미지가 2배 커지게 되겠네요.
그 다음 4개는 위 RotateAnimation 과 마찬가지로 pivotXType,pivotXvalue,pivotYType,pivotYvalue 입니다.
이것도 Scaling의 중심부분을 잡아주는 파라미터들입니다.

그 다음에 나오는 translate 애니메이션을 보죠.
TranslateAnimation(0,-150,0,0);
fromXDelta,toXDelta,fromYDelta,toYDelta 입니다.
이미지의 출발과 종료 지점을 나타내는 겁니다.
위의 값은 toXDelta만 -150이니까 왼쪽으로 150만큼 이동한다는 의미입니다.

flipping 애니메이션 효과를 찾으려고 하다가 보게 된 Tweening Animation 예제였었는데요.
어쨌던 이 4가지 안드로이드에서 제공하는 애니메이션 클래스에 대해 자세하게 배운것 같습니다.

이건 여기서 만족하고 책이나 인터넷 써칭을 통해서 flipping 효과를 어떻게 낼지에 대해서 더 research 해 봐야 겠습니다.



반응형

Comment

이전 1 2 3 4 5 6 7 ··· 18 다음